Version 0.65beta includes detailed poll results table for every minter card, extensive code cleanup on JS files, and removal of the ability for already-existing-minters to publish a minter card.
This commit is contained in:
parent
e99929876f
commit
cfccfab99a
@ -755,6 +755,30 @@ body {
|
|||||||
color: #dddddd;
|
color: #dddddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.minter-card-results button {
|
||||||
|
color: white;
|
||||||
|
background-color: rgb(48, 60, 63);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: groove;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 0.6vh 1.2vh;
|
||||||
|
margin: 0.15vh;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minter-card-results button:hover {
|
||||||
|
color: rgb(112, 113, 100);
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: inset;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 0.6vh 1.2vh;
|
||||||
|
margin: 0.15vh;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.minter-card .info {
|
.minter-card .info {
|
||||||
background-color: #2a2a2a; /* Very dark grey background */
|
background-color: #2a2a2a; /* Very dark grey background */
|
||||||
border: 1px solid #d3d3d3; /* Light grey border */
|
border: 1px solid #d3d3d3; /* Light grey border */
|
||||||
|
@ -162,11 +162,11 @@ const loadOrFetchAdminGroupsData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractCardsMinterName = (cardIdentifier) => {
|
const extractEncryptedCardsMinterName = (cardIdentifier) => {
|
||||||
// Ensure the identifier starts with the prefix
|
// Ensure the identifier starts with the prefix
|
||||||
if (!cardIdentifier.startsWith(`${encryptedCardIdentifierPrefix}-`)) {
|
// if (!cardIdentifier.startsWith(`${encryptedCardIdentifierPrefix}-`)) {
|
||||||
throw new Error('Invalid identifier format or prefix mismatch');
|
// throw new Error('Invalid identifier format or prefix mismatch');
|
||||||
}
|
// }
|
||||||
// Split the identifier into parts
|
// Split the identifier into parts
|
||||||
const parts = cardIdentifier.split('-');
|
const parts = cardIdentifier.split('-');
|
||||||
// Ensure the format has at least 3 parts
|
// Ensure the format has at least 3 parts
|
||||||
@ -176,7 +176,7 @@ const extractCardsMinterName = (cardIdentifier) => {
|
|||||||
|
|
||||||
if (parts.slice(2, -1).join('-') === 'TOPIC') {
|
if (parts.slice(2, -1).join('-') === 'TOPIC') {
|
||||||
console.log(`TOPIC found in identifier: ${cardIdentifier} - not including in duplicatesList`)
|
console.log(`TOPIC found in identifier: ${cardIdentifier} - not including in duplicatesList`)
|
||||||
return 'topic'
|
return
|
||||||
}
|
}
|
||||||
// Extract minterName (everything from the second part to the second-to-last part)
|
// Extract minterName (everything from the second part to the second-to-last part)
|
||||||
const minterName = parts.slice(2, -1).join('-');
|
const minterName = parts.slice(2, -1).join('-');
|
||||||
@ -243,6 +243,7 @@ const fetchAllEncryptedCards = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const finalCards = await processCards(validEncryptedCards)
|
const finalCards = await processCards(validEncryptedCards)
|
||||||
|
|
||||||
console.log(`finalCards:`,finalCards)
|
console.log(`finalCards:`,finalCards)
|
||||||
// Display skeleton cards immediately
|
// Display skeleton cards immediately
|
||||||
encryptedCardsContainer.innerHTML = "";
|
encryptedCardsContainer.innerHTML = "";
|
||||||
@ -254,6 +255,8 @@ const fetchAllEncryptedCards = async () => {
|
|||||||
// Fetch and update each card
|
// Fetch and update each card
|
||||||
finalCards.forEach(async card => {
|
finalCards.forEach(async card => {
|
||||||
try {
|
try {
|
||||||
|
const hasMinterName = await extractEncryptedCardsMinterName(card.identifier)
|
||||||
|
if (hasMinterName) existingCardMinterNames.push(hasMinterName)
|
||||||
const cardDataResponse = await qortalRequest({
|
const cardDataResponse = await qortalRequest({
|
||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
name: card.name,
|
name: card.name,
|
||||||
@ -349,7 +352,7 @@ const fetchExistingEncryptedCard = async (minterName) => {
|
|||||||
// })
|
// })
|
||||||
|
|
||||||
//CHANGED to searchSimple to speed up search results.
|
//CHANGED to searchSimple to speed up search results.
|
||||||
const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0)
|
const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, minterName, 0)
|
||||||
|
|
||||||
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
|
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
|
||||||
|
|
||||||
@ -442,7 +445,7 @@ const validateMinterName = async(minterName) => {
|
|||||||
|
|
||||||
const publishEncryptedCard = async (isTopicModePassed = false) => {
|
const publishEncryptedCard = async (isTopicModePassed = false) => {
|
||||||
// If the user wants it to be a topic, we set global isTopic = true, else false
|
// If the user wants it to be a topic, we set global isTopic = true, else false
|
||||||
isTopic = isTopicModePassed;
|
isTopic = isTopicModePassed || isTopic
|
||||||
|
|
||||||
const minterNameInput = document.getElementById("minter-name-input").value.trim();
|
const minterNameInput = document.getElementById("minter-name-input").value.trim();
|
||||||
const header = document.getElementById("card-header").value.trim();
|
const header = document.getElementById("card-header").value.trim();
|
||||||
@ -461,23 +464,23 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
|
|
||||||
// If not topic mode, validate the user actually entered a valid Minter name
|
// If not topic mode, validate the user actually entered a valid Minter name
|
||||||
if (!isTopic) {
|
if (!isTopic) {
|
||||||
publishedMinterName = await validateMinterName(minterNameInput);
|
publishedMinterName = await validateMinterName(minterNameInput)
|
||||||
if (!publishedMinterName) {
|
if (!publishedMinterName) {
|
||||||
alert(`"${minterNameInput}" doesn't seem to be a valid Minter name. Please check or use topic mode.`);
|
alert(`"${minterNameInput}" doesn't seem to be a valid Minter name. Please check or use topic mode.`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
// Also check for existing card if not topic
|
// Also check for existing card if not topic
|
||||||
if (!isExistingEncryptedCard && existingCardMinterNames.includes(publishedMinterName)) {
|
if (!isExistingEncryptedCard && existingCardMinterNames.includes(publishedMinterName)) {
|
||||||
const updateCard = confirm(
|
const updateCard = confirm(
|
||||||
`Minter Name: ${publishedMinterName} already has a card. Update or Cancel?`
|
`Minter Name: ${publishedMinterName} already has a card. Update or Cancel?`
|
||||||
);
|
)
|
||||||
if (updateCard) {
|
if (updateCard) {
|
||||||
await fetchExistingEncryptedCard(publishedMinterName);
|
await fetchExistingEncryptedCard(publishedMinterName)
|
||||||
await loadEncryptedCardIntoForm();
|
await loadEncryptedCardIntoForm()
|
||||||
isExistingEncryptedCard = true;
|
isExistingEncryptedCard = true
|
||||||
return;
|
return
|
||||||
} else {
|
} else {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,12 +488,12 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
// Determine final card identifier
|
// Determine final card identifier
|
||||||
const newCardIdentifier = isTopic
|
const newCardIdentifier = isTopic
|
||||||
? `${encryptedCardIdentifierPrefix}-TOPIC-${await uid()}`
|
? `${encryptedCardIdentifierPrefix}-TOPIC-${await uid()}`
|
||||||
: `${encryptedCardIdentifierPrefix}-${publishedMinterName}-${await uid()}`;
|
: `${encryptedCardIdentifierPrefix}-${publishedMinterName}-${await uid()}`
|
||||||
|
|
||||||
const cardIdentifier = isExistingEncryptedCard ? existingEncryptedCardIdentifier : newCardIdentifier;
|
const cardIdentifier = isExistingEncryptedCard ? existingEncryptedCardIdentifier : newCardIdentifier
|
||||||
|
|
||||||
// Build cardData
|
// Build cardData
|
||||||
const pollName = `${cardIdentifier}-poll`;
|
const pollName = `${cardIdentifier}-poll`
|
||||||
const cardData = {
|
const cardData = {
|
||||||
minterName: publishedMinterName,
|
minterName: publishedMinterName,
|
||||||
header,
|
header,
|
||||||
@ -500,7 +503,7 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
poll: pollName,
|
poll: pollName,
|
||||||
topicMode: isTopic
|
topicMode: isTopic
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert to base64 or fallback
|
// Convert to base64 or fallback
|
||||||
@ -523,7 +526,6 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verifiedAdminPublicKeys = loadedAdminKeys
|
verifiedAdminPublicKeys = loadedAdminKeys
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
@ -534,7 +536,7 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
data64: base64CardData,
|
data64: base64CardData,
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
publicKeys: verifiedAdminPublicKeys
|
publicKeys: verifiedAdminPublicKeys
|
||||||
});
|
})
|
||||||
|
|
||||||
// Possibly create a poll if it’s a brand new card
|
// Possibly create a poll if it’s a brand new card
|
||||||
if (!isExistingEncryptedCard) {
|
if (!isExistingEncryptedCard) {
|
||||||
@ -544,18 +546,20 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
pollDescription: `Admin Board Poll Published By ${userState.accountName}`,
|
pollDescription: `Admin Board Poll Published By ${userState.accountName}`,
|
||||||
pollOptions: ["Yes, No"],
|
pollOptions: ["Yes, No"],
|
||||||
pollOwnerAddress: userState.accountAddress
|
pollOwnerAddress: userState.accountAddress
|
||||||
});
|
})
|
||||||
alert("Card and poll published successfully!");
|
alert("Card and poll published successfully!")
|
||||||
|
|
||||||
// If it’s a real Minter name, store it so we know we have a card for them
|
// If it’s a real Minter name, store it so we know we have a card for them
|
||||||
if (!isTopic) {
|
if (!isTopic) {
|
||||||
existingCardMinterNames.push(publishedMinterName);
|
if (!existingCardMinterNames.contains(publishedMinterName)) {
|
||||||
|
existingCardMinterNames.push(publishedMinterName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert("Card updated successfully! (No poll updates possible currently...)");
|
alert("Card updated successfully! (No poll updates possible currently...)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup UI
|
|
||||||
document.getElementById("publish-card-form").reset();
|
document.getElementById("publish-card-form").reset();
|
||||||
document.getElementById("publish-card-view").style.display = "none";
|
document.getElementById("publish-card-view").style.display = "none";
|
||||||
document.getElementById("encrypted-cards-container").style.display = "flex";
|
document.getElementById("encrypted-cards-container").style.display = "flex";
|
||||||
@ -569,40 +573,31 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
|
|
||||||
const getEncryptedCommentCount = async (cardIdentifier) => {
|
const getEncryptedCommentCount = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
// const response = await qortalRequest({
|
|
||||||
// action: 'SEARCH_QDN_RESOURCES',
|
|
||||||
// service: 'MAIL_PRIVATE',
|
|
||||||
// query: `comment-${cardIdentifier}`,
|
|
||||||
// mode: "ALL"
|
|
||||||
// })
|
|
||||||
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
||||||
// Just return the count; no need to decrypt each comment here
|
|
||||||
return Array.isArray(response) ? response.length : 0
|
return Array.isArray(response) ? response.length : 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching comment count for ${cardIdentifier}:`, error)
|
console.error(`Error fetching comment count for ${cardIdentifier}:`, error)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Post a comment on a card. ---------------------------------
|
// Post a comment on a card. ---------------------------------
|
||||||
const postEncryptedComment = async (cardIdentifier) => {
|
const postEncryptedComment = async (cardIdentifier) => {
|
||||||
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`);
|
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`)
|
||||||
const commentText = commentInput.value.trim();
|
const commentText = commentInput.value.trim()
|
||||||
|
|
||||||
if (!commentText) {
|
if (!commentText) {
|
||||||
alert('Comment cannot be empty!');
|
alert('Comment cannot be empty!')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const postTimestamp = Date.now()
|
const postTimestamp = Date.now()
|
||||||
console.log(`timestmp to be posted: ${postTimestamp}`)
|
|
||||||
|
|
||||||
const commentData = {
|
const commentData = {
|
||||||
content: commentText,
|
content: commentText,
|
||||||
creator: userState.accountName,
|
creator: userState.accountName,
|
||||||
timestamp: postTimestamp,
|
timestamp: postTimestamp,
|
||||||
};
|
}
|
||||||
|
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
||||||
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`;
|
|
||||||
|
|
||||||
if (!Array.isArray(adminPublicKeys) || (adminPublicKeys.length === 0) || (!adminPublicKeys)) {
|
if (!Array.isArray(adminPublicKeys) || (adminPublicKeys.length === 0) || (!adminPublicKeys)) {
|
||||||
console.log('adminPpublicKeys variable failed checks, calling for admin public keys from API (comment)',adminPublicKeys)
|
console.log('adminPpublicKeys variable failed checks, calling for admin public keys from API (comment)',adminPublicKeys)
|
||||||
@ -611,10 +606,10 @@ const postEncryptedComment = async (cardIdentifier) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const base64CommentData = await objectToBase64(commentData);
|
const base64CommentData = await objectToBase64(commentData)
|
||||||
if (!base64CommentData) {
|
if (!base64CommentData) {
|
||||||
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`);
|
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
|
||||||
base64CommentData = btoa(JSON.stringify(commentData));
|
base64CommentData = btoa(JSON.stringify(commentData))
|
||||||
}
|
}
|
||||||
|
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
@ -625,45 +620,35 @@ const postEncryptedComment = async (cardIdentifier) => {
|
|||||||
data64: base64CommentData,
|
data64: base64CommentData,
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
publicKeys: adminPublicKeys
|
publicKeys: adminPublicKeys
|
||||||
});
|
})
|
||||||
|
alert('Comment posted successfully!')
|
||||||
|
commentInput.value = ''
|
||||||
|
|
||||||
alert('Comment posted successfully!');
|
|
||||||
commentInput.value = ''; // Clear input
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error posting comment:', error);
|
console.error('Error posting comment:', error)
|
||||||
alert('Failed to post comment.');
|
alert('Failed to post comment.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
//Fetch the comments for a card with passed card identifier ----------------------------
|
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||||
const fetchEncryptedComments = async (cardIdentifier) => {
|
const fetchEncryptedComments = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
// const response = await qortalRequest({
|
|
||||||
// action: 'SEARCH_QDN_RESOURCES',
|
|
||||||
// service: 'MAIL_PRIVATE',
|
|
||||||
// query: `comment-${cardIdentifier}`,
|
|
||||||
// mode: "ALL"
|
|
||||||
// })
|
|
||||||
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching comments for ${cardIdentifier}:`, error);
|
console.error(`Error fetching comments for ${cardIdentifier}:`, error)
|
||||||
return [];
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// display the comments on the card, with passed cardIdentifier to identify the card --------------
|
// display the comments on the card, with passed cardIdentifier to identify the card --------------
|
||||||
const displayEncryptedComments = async (cardIdentifier) => {
|
const displayEncryptedComments = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
|
const comments = await fetchEncryptedComments(cardIdentifier)
|
||||||
|
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`)
|
||||||
|
|
||||||
const comments = await fetchEncryptedComments(cardIdentifier);
|
|
||||||
|
|
||||||
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`);
|
|
||||||
|
|
||||||
// Fetch and display each comment
|
|
||||||
for (const comment of comments) {
|
for (const comment of comments) {
|
||||||
const commentDataResponse = await qortalRequest({
|
const commentDataResponse = await qortalRequest({
|
||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
@ -671,13 +656,11 @@ const displayEncryptedComments = async (cardIdentifier) => {
|
|||||||
service: "MAIL_PRIVATE",
|
service: "MAIL_PRIVATE",
|
||||||
identifier: comment.identifier,
|
identifier: comment.identifier,
|
||||||
encoding: "base64"
|
encoding: "base64"
|
||||||
});
|
})
|
||||||
|
|
||||||
const decryptedCommentData = await decryptAndParseObject(commentDataResponse)
|
const decryptedCommentData = await decryptAndParseObject(commentDataResponse)
|
||||||
|
|
||||||
const timestampCheck = comment.updated || comment.created || 0
|
const timestampCheck = comment.updated || comment.created || 0
|
||||||
const timestamp = await timestampToHumanReadableDate(timestampCheck);
|
const timestamp = await timestampToHumanReadableDate(timestampCheck)
|
||||||
|
|
||||||
//TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section.
|
//TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section.
|
||||||
const commentHTML = `
|
const commentHTML = `
|
||||||
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
||||||
@ -685,201 +668,120 @@ const displayEncryptedComments = async (cardIdentifier) => {
|
|||||||
<p>${decryptedCommentData.content}</p>
|
<p>${decryptedCommentData.content}</p>
|
||||||
<p><i>${timestamp}</p></i>
|
<p><i>${timestamp}</p></i>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
commentsContainer.insertAdjacentHTML('beforeend', commentHTML);
|
commentsContainer.insertAdjacentHTML('beforeend', commentHTML)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error);
|
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error)
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateAdminBoardPollResults = async (pollData, minterGroupMembers, minterAdmins) => {
|
|
||||||
// 1) Validate pollData structure
|
|
||||||
if (!pollData || !Array.isArray(pollData.voteWeights) || !Array.isArray(pollData.votes)) {
|
|
||||||
console.warn("Poll data is missing or invalid. pollData:", pollData);
|
|
||||||
return {
|
|
||||||
adminYes: 0,
|
|
||||||
adminNo: 0,
|
|
||||||
minterYes: 0,
|
|
||||||
minterNo: 0,
|
|
||||||
totalYes: 0,
|
|
||||||
totalNo: 0,
|
|
||||||
totalYesWeight: 0,
|
|
||||||
totalNoWeight: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Prepare admin & minter addresses
|
|
||||||
const memberAddresses = minterGroupMembers.map(member => member.member);
|
|
||||||
const minterAdminAddresses = minterAdmins.map(member => member.member);
|
|
||||||
const adminGroupsMembers = await fetchAllAdminGroupsMembers();
|
|
||||||
const groupAdminAddresses = adminGroupsMembers.map(member => member.member);
|
|
||||||
const adminAddresses = [];
|
|
||||||
adminAddresses.push(...minterAdminAddresses, ...groupAdminAddresses);
|
|
||||||
|
|
||||||
let adminYes = 0, adminNo = 0;
|
|
||||||
let minterYes = 0, minterNo = 0;
|
|
||||||
let yesWeight = 0, noWeight = 0;
|
|
||||||
|
|
||||||
// 3) Process voteWeights
|
|
||||||
pollData.voteWeights.forEach(weightData => {
|
|
||||||
if (weightData.optionName === 'Yes') {
|
|
||||||
yesWeight = weightData.voteWeight;
|
|
||||||
} else if (weightData.optionName === 'No') {
|
|
||||||
noWeight = weightData.voteWeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4) Process votes
|
|
||||||
for (const vote of pollData.votes) {
|
|
||||||
const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey);
|
|
||||||
// console.log(`voter address: ${voterAddress}, optionIndex: ${vote.optionIndex}`);
|
|
||||||
|
|
||||||
if (vote.optionIndex === 0) {
|
|
||||||
if (adminAddresses.includes(voterAddress)) {
|
|
||||||
adminYes++;
|
|
||||||
} else if (memberAddresses.includes(voterAddress)) {
|
|
||||||
minterYes++;
|
|
||||||
} else {
|
|
||||||
console.log(`voter ${voterAddress} is not a minter nor an admin... Not including results...`);
|
|
||||||
}
|
|
||||||
} else if (vote.optionIndex === 1) {
|
|
||||||
if (adminAddresses.includes(voterAddress)) {
|
|
||||||
adminNo++;
|
|
||||||
} else if (memberAddresses.includes(voterAddress)) {
|
|
||||||
minterNo++;
|
|
||||||
} else {
|
|
||||||
console.log(`voter ${voterAddress} is not a minter nor an admin... Not including results...`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 5) Summaries
|
|
||||||
const totalYesWeight = yesWeight;
|
|
||||||
const totalNoWeight = noWeight;
|
|
||||||
const totalYes = adminYes + minterYes;
|
|
||||||
const totalNo = adminNo + minterNo;
|
|
||||||
|
|
||||||
return {
|
|
||||||
adminYes,
|
|
||||||
adminNo,
|
|
||||||
minterYes,
|
|
||||||
minterNo,
|
|
||||||
totalYes,
|
|
||||||
totalNo,
|
|
||||||
totalYesWeight,
|
|
||||||
totalNoWeight
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleEncryptedComments = async (cardIdentifier) => {
|
const toggleEncryptedComments = async (cardIdentifier) => {
|
||||||
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
|
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
|
||||||
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
|
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
|
||||||
|
|
||||||
if (!commentsSection || !commentButton) return;
|
if (!commentsSection || !commentButton) return
|
||||||
|
|
||||||
const count = commentButton.dataset.commentCount;
|
const count = commentButton.dataset.commentCount;
|
||||||
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display);
|
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display)
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
// Show comments
|
// Show comments
|
||||||
commentButton.textContent = "LOADING...";
|
commentButton.textContent = "LOADING..."
|
||||||
await displayEncryptedComments(cardIdentifier);
|
await displayEncryptedComments(cardIdentifier)
|
||||||
commentsSection.style.display = 'block';
|
commentsSection.style.display = 'block'
|
||||||
// Change the button text to 'HIDE COMMENTS'
|
// Change the button text to 'HIDE COMMENTS'
|
||||||
commentButton.textContent = 'HIDE COMMENTS';
|
commentButton.textContent = 'HIDE COMMENTS'
|
||||||
} else {
|
} else {
|
||||||
// Hide comments
|
// Hide comments
|
||||||
commentsSection.style.display = 'none';
|
commentsSection.style.display = 'none'
|
||||||
commentButton.textContent = `COMMENTS (${count})`;
|
commentButton.textContent = `COMMENTS (${count})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const createLinkDisplayModal = async () => {
|
const createLinkDisplayModal = async () => {
|
||||||
const modalHTML = `
|
const modalHTML = `
|
||||||
<div id="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 1000;">
|
<div id="links-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 1000;">
|
||||||
<div style="position: relative; margin: 10% auto; width: 95%; height: 80%; background: white; border-radius: 10px; overflow: hidden;">
|
<div style="position: relative; margin: 10% auto; width: 95%; height: 80%; background: white; border-radius: 10px; overflow: hidden;">
|
||||||
<iframe id="modalContent" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
<iframe id="links-modalContent" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
||||||
<button onclick="closeLinkDisplayModal()" style="position: absolute; top: 10px; right: 10px; background: red; color: white; border: none; padding: 5px 10px; border-radius: 5px;">Close</button>
|
<button onclick="closeLinkDisplayModal()" style="position: absolute; top: 10px; right: 10px; background: red; color: white; border: none; padding: 5px 10px; border-radius: 5px;">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
document.body.insertAdjacentHTML('beforeend', modalHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to open the modal
|
// Function to open the modal
|
||||||
const openLinkDisplayModal = async (link) => {
|
const openLinkDisplayModal = async (link) => {
|
||||||
const processedLink = await processQortalLinkForRendering(link) // Process the link to replace `qortal://` for rendering in modal
|
const processedLink = await processQortalLinkForRendering(link) // Process the link to replace `qortal://` for rendering in modal
|
||||||
const modal = document.getElementById('modal');
|
const modal = document.getElementById('links-modal');
|
||||||
const modalContent = document.getElementById('modalContent');
|
const modalContent = document.getElementById('links-modalContent');
|
||||||
modalContent.src = processedLink; // Set the iframe source to the link
|
modalContent.src = processedLink; // Set the iframe source to the link
|
||||||
modal.style.display = 'block'; // Show the modal
|
modal.style.display = 'block'; // Show the modal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to close the modal
|
// Function to close the modal
|
||||||
const closeLinkDisplayModal = async () => {
|
const closeLinkDisplayModal = async () => {
|
||||||
const modal = document.getElementById('modal');
|
const modal = document.getElementById('links-modal');
|
||||||
const modalContent = document.getElementById('modalContent');
|
const modalContent = document.getElementById('links-modalContent');
|
||||||
modal.style.display = 'none'; // Hide the modal
|
modal.style.display = 'none'; // Hide the modal
|
||||||
modalContent.src = ''; // Clear the iframe source
|
modalContent.src = ''; // Clear the iframe source
|
||||||
}
|
}
|
||||||
|
|
||||||
// const processQortalLinkForRendering = async (link) => {
|
|
||||||
// if (link.startsWith('qortal://')) {
|
|
||||||
// const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/);
|
|
||||||
// if (match) {
|
|
||||||
// const firstParam = match[1].toUpperCase(); // Convert to uppercase
|
|
||||||
// const remainingPath = match[2] || ""; // Rest of the URL
|
|
||||||
// // Perform any asynchronous operation if necessary
|
|
||||||
// await new Promise(resolve => setTimeout(resolve, 10)); // Simulating async operation
|
|
||||||
// return `/render/${firstParam}${remainingPath}`;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return link; // Return unchanged if not a Qortal link
|
|
||||||
// }
|
|
||||||
|
|
||||||
const processQortalLinkForRendering = async (link) => {
|
const processQortalLinkForRendering = async (link) => {
|
||||||
if (link.startsWith('qortal://')) {
|
if (link.startsWith('qortal://')) {
|
||||||
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/);
|
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/)
|
||||||
if (match) {
|
if (match) {
|
||||||
const firstParam = match[1].toUpperCase();
|
const firstParam = match[1].toUpperCase();
|
||||||
const remainingPath = match[2] || "";
|
const remainingPath = match[2] || ""
|
||||||
const themeColor = window._qdnTheme || 'default'; // Fallback to 'default' if undefined
|
const themeColor = window._qdnTheme || 'default' // Fallback to 'default' if undefined
|
||||||
|
|
||||||
// Simulating async operation if needed
|
// Simulating async operation if needed
|
||||||
await new Promise(resolve => setTimeout(resolve, 10));
|
await new Promise(resolve => setTimeout(resolve, 10))
|
||||||
|
|
||||||
// Append theme as a query parameter
|
return `/render/${firstParam}${remainingPath}?theme=${themeColor}`
|
||||||
return `/render/${firstParam}${remainingPath}?theme=${themeColor}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return link;
|
return link
|
||||||
};
|
}
|
||||||
|
|
||||||
async function getMinterAvatar(minterName) {
|
|
||||||
const avatarUrl = `/arbitrary/THUMBNAIL/${minterName}/qortal_avatar`;
|
|
||||||
|
|
||||||
|
const getMinterAvatar = async (minterName) => {
|
||||||
|
const avatarUrl = `/arbitrary/THUMBNAIL/${minterName}/qortal_avatar`
|
||||||
try {
|
try {
|
||||||
const response = await fetch(avatarUrl, { method: 'HEAD' });
|
const response = await fetch(avatarUrl, { method: 'HEAD' })
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Avatar exists, return the image HTML
|
return `<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; align-self: center;">`
|
||||||
return `<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; align-self: center;">`;
|
|
||||||
} else {
|
} else {
|
||||||
// Avatar not found or no permission
|
return ''
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking avatar availability:', error);
|
console.error('Error checking avatar availability:', error)
|
||||||
return '';
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const togglePollDetails = (cardIdentifier) => {
|
||||||
|
// const detailsDiv = document.getElementById(`poll-details-${cardIdentifier}`)
|
||||||
|
// const modal = document.getElementById(`poll-details-modal`)
|
||||||
|
// const modalContent = document.getElementById(`poll-details-modalContent`)
|
||||||
|
|
||||||
|
// if (!detailsDiv || !modal || !modalContent) return
|
||||||
|
|
||||||
|
// modalContent.appendChild(detailsDiv)
|
||||||
|
// modal.style.display = 'block'
|
||||||
|
// window.onclick = (event) => {
|
||||||
|
// if (event.target === modal) {
|
||||||
|
// modal.style.display = 'none'
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Create the overall Minter Card HTML -----------------------------------------------
|
// Create the overall Minter Card HTML -----------------------------------------------
|
||||||
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
|
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
|
||||||
const { minterName, header, content, links, creator, timestamp, poll, topicMode } = cardData
|
const { minterName, header, content, links, creator, timestamp, poll, topicMode } = cardData
|
||||||
const formattedDate = new Date(timestamp).toLocaleString()
|
const formattedDate = new Date(timestamp).toLocaleString()
|
||||||
const minterAvatar = !topicMode ? await getMinterAvatar(minterName) : null
|
const minterAvatar = !topicMode ? await getMinterAvatar(minterName) : null
|
||||||
// const creatorAvatar = `/arbitrary/THUMBNAIL/${creator}/qortal_avatar`;
|
|
||||||
const creatorAvatar = await getMinterAvatar(creator)
|
const creatorAvatar = await getMinterAvatar(creator)
|
||||||
const linksHTML = links.map((link, index) => `
|
const linksHTML = links.map((link, index) => `
|
||||||
<button onclick="openLinkDisplayModal('${link}')">
|
<button onclick="openLinkDisplayModal('${link}')">
|
||||||
@ -890,15 +792,14 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
const isUndefinedUser = (minterName === 'undefined')
|
const isUndefinedUser = (minterName === 'undefined')
|
||||||
|
|
||||||
const hasTopicMode = Object.prototype.hasOwnProperty.call(cardData, 'topicMode')
|
const hasTopicMode = Object.prototype.hasOwnProperty.call(cardData, 'topicMode')
|
||||||
// 2) Decide if this card is showing as "Topic" or "Name"
|
|
||||||
let showTopic = false
|
let showTopic = false
|
||||||
|
|
||||||
if (hasTopicMode) {
|
if (hasTopicMode) {
|
||||||
// If present, see if it's actually "true" or true
|
const modeVal = cardData.topicMode
|
||||||
const modeVal = cardData.topicMode;
|
|
||||||
showTopic = (modeVal === true || modeVal === 'true')
|
showTopic = (modeVal === true || modeVal === 'true')
|
||||||
} else {
|
} else {
|
||||||
if (!isUndefinedUser) {
|
if (!isUndefinedUser) {
|
||||||
// No topicMode => older card => default to Name
|
|
||||||
showTopic = false
|
showTopic = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -915,8 +816,9 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
|
|
||||||
const minterGroupMembers = await fetchMinterGroupMembers()
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
const minterAdmins = await fetchMinterGroupAdmins()
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0 } = await calculateAdminBoardPollResults(pollResults, minterGroupMembers, minterAdmins)
|
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0, detailsHtml } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator)
|
||||||
await createModal()
|
createModal('links')
|
||||||
|
createModal('poll-details')
|
||||||
return `
|
return `
|
||||||
<div class="admin-card" style="background-color: ${cardColorCode}">
|
<div class="admin-card" style="background-color: ${cardColorCode}">
|
||||||
<div class="minter-card-header">
|
<div class="minter-card-header">
|
||||||
@ -935,6 +837,10 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
</div>
|
</div>
|
||||||
<div class="results-header support-header"><h5>CURRENT RESULTS</h5></div>
|
<div class="results-header support-header"><h5>CURRENT RESULTS</h5></div>
|
||||||
<div class="minter-card-results">
|
<div class="minter-card-results">
|
||||||
|
<button onclick="togglePollDetails('${cardIdentifier}')">Display Poll Details</button>
|
||||||
|
<div id="poll-details-${cardIdentifier}" style="display: none;">
|
||||||
|
${detailsHtml}
|
||||||
|
</div>
|
||||||
<div class="admin-results">
|
<div class="admin-results">
|
||||||
<span class="admin-yes">Admin Support: ${adminYes}</span>
|
<span class="admin-yes">Admin Support: ${adminYes}</span>
|
||||||
<span class="admin-no">Admin Against: ${adminNo}</span>
|
<span class="admin-no">Admin Against: ${adminNo}</span>
|
||||||
|
@ -32,7 +32,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
<label for="card-header">Header:</label>
|
<label for="card-header">Header:</label>
|
||||||
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
||||||
<label for="card-content">Content:</label>
|
<label for="card-content">Content:</label>
|
||||||
<textarea id="card-content" placeholder="Enter detailed information about why you deserve to be a minter..." required></textarea>
|
<textarea id="card-content" placeholder="Enter detailed information about why you would like to be a minter... the more the better, and links to things you have published on QDN will help a lot! Give the Minter Admins things to make decisions by!" required></textarea>
|
||||||
<label for="card-links">Links (qortal://...):</label>
|
<label for="card-links">Links (qortal://...):</label>
|
||||||
<div id="links-container">
|
<div id="links-container">
|
||||||
<input type="text" class="card-link" placeholder="Enter QDN link">
|
<input type="text" class="card-link" placeholder="Enter QDN link">
|
||||||
@ -333,12 +333,12 @@ const fetchExistingCard = async () => {
|
|||||||
// Changed to searchSimple to improve load times.
|
// Changed to searchSimple to improve load times.
|
||||||
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0)
|
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0)
|
||||||
|
|
||||||
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
|
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`)
|
||||||
|
|
||||||
// Step 2: Check if the response is an array and not empty
|
// Step 2: Check if the response is an array and not empty
|
||||||
if (!response || !Array.isArray(response) || response.length === 0) {
|
if (!response || !Array.isArray(response) || response.length === 0) {
|
||||||
console.log("No cards found for the current user.");
|
console.log("No cards found for the current user.")
|
||||||
return null;
|
return null
|
||||||
} else if (response.length === 1) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it.
|
} else if (response.length === 1) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it.
|
||||||
return response[0]
|
return response[0]
|
||||||
}
|
}
|
||||||
@ -346,17 +346,17 @@ const fetchExistingCard = async () => {
|
|||||||
// Validate cards asynchronously, check that they are not comments, etc.
|
// Validate cards asynchronously, check that they are not comments, etc.
|
||||||
const validatedCards = await Promise.all(
|
const validatedCards = await Promise.all(
|
||||||
response.map(async card => {
|
response.map(async card => {
|
||||||
const isValid = await validateCardStructure(card);
|
const isValid = await validateCardStructure(card)
|
||||||
return isValid ? card : null;
|
return isValid ? card : null
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Filter out invalid cards
|
// Filter out invalid cards
|
||||||
const validCards = validatedCards.filter(card => card !== null);
|
const validCards = validatedCards.filter(card => card !== null)
|
||||||
|
|
||||||
if (validCards.length > 0) {
|
if (validCards.length > 0) {
|
||||||
// Sort by most recent timestamp
|
// Sort by most recent timestamp
|
||||||
const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0];
|
const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0]
|
||||||
|
|
||||||
// Fetch full card data
|
// Fetch full card data
|
||||||
const cardDataResponse = await qortalRequest({
|
const cardDataResponse = await qortalRequest({
|
||||||
@ -364,23 +364,23 @@ const fetchExistingCard = async () => {
|
|||||||
name: userState.accountName, // User's account name
|
name: userState.accountName, // User's account name
|
||||||
service: "BLOG_POST",
|
service: "BLOG_POST",
|
||||||
identifier: mostRecentCard.identifier
|
identifier: mostRecentCard.identifier
|
||||||
});
|
})
|
||||||
|
|
||||||
existingCardIdentifier = mostRecentCard.identifier;
|
existingCardIdentifier = mostRecentCard.identifier
|
||||||
existingCardData = cardDataResponse;
|
existingCardData = cardDataResponse
|
||||||
|
|
||||||
console.log("Full card data fetched successfully:", cardDataResponse);
|
console.log("Full card data fetched successfully:", cardDataResponse)
|
||||||
|
|
||||||
return cardDataResponse;
|
return cardDataResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("No valid cards found.");
|
console.log("No valid cards found.")
|
||||||
return null;
|
return null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching existing card:", error);
|
console.error("Error fetching existing card:", error)
|
||||||
return null;
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Validate that a card is indeed a card and not a comment. -------------------------------------
|
// Validate that a card is indeed a card and not a comment. -------------------------------------
|
||||||
const validateCardStructure = async (card) => {
|
const validateCardStructure = async (card) => {
|
||||||
@ -390,42 +390,52 @@ const validateCardStructure = async (card) => {
|
|||||||
card.service === "BLOG_POST" &&
|
card.service === "BLOG_POST" &&
|
||||||
card.identifier && !card.identifier.includes("comment") &&
|
card.identifier && !card.identifier.includes("comment") &&
|
||||||
card.created
|
card.created
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load existing card data passed, into the form for editing -------------------------------------
|
// Load existing card data passed, into the form for editing -------------------------------------
|
||||||
const loadCardIntoForm = async (cardData) => {
|
const loadCardIntoForm = async (cardData) => {
|
||||||
console.log("Loading existing card data:", cardData);
|
console.log("Loading existing card data:", cardData)
|
||||||
document.getElementById("card-header").value = cardData.header;
|
document.getElementById("card-header").value = cardData.header
|
||||||
document.getElementById("card-content").value = cardData.content;
|
document.getElementById("card-content").value = cardData.content
|
||||||
|
|
||||||
const linksContainer = document.getElementById("links-container");
|
const linksContainer = document.getElementById("links-container")
|
||||||
linksContainer.innerHTML = ""; // Clear previous links
|
linksContainer.innerHTML = ""
|
||||||
cardData.links.forEach(link => {
|
cardData.links.forEach(link => {
|
||||||
const linkInput = document.createElement("input");
|
const linkInput = document.createElement("input")
|
||||||
linkInput.type = "text";
|
linkInput.type = "text"
|
||||||
linkInput.className = "card-link";
|
linkInput.className = "card-link"
|
||||||
linkInput.value = link;
|
linkInput.value = link;
|
||||||
linksContainer.appendChild(linkInput);
|
linksContainer.appendChild(linkInput);
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main function to publish a new Minter Card -----------------------------------------------
|
// Main function to publish a new Minter Card -----------------------------------------------
|
||||||
const publishCard = async () => {
|
const publishCard = async () => {
|
||||||
|
|
||||||
|
const minterGroupData = await fetchMinterGroupMembers();
|
||||||
|
const minterGroupAddresses = minterGroupData.map(m => m.member); // array of addresses
|
||||||
|
|
||||||
|
// 2) check if user is a minter
|
||||||
|
const userAddress = userState.accountAddress;
|
||||||
|
if (minterGroupAddresses.includes(userAddress)) {
|
||||||
|
alert("You are already a Minter and cannot publish a new card!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const header = document.getElementById("card-header").value.trim();
|
const header = document.getElementById("card-header").value.trim();
|
||||||
const content = document.getElementById("card-content").value.trim();
|
const content = document.getElementById("card-content").value.trim();
|
||||||
const links = Array.from(document.querySelectorAll(".card-link"))
|
const links = Array.from(document.querySelectorAll(".card-link"))
|
||||||
.map(input => input.value.trim())
|
.map(input => input.value.trim())
|
||||||
.filter(link => link.startsWith("qortal://"));
|
.filter(link => link.startsWith("qortal://"))
|
||||||
|
|
||||||
if (!header || !content) {
|
if (!header || !content) {
|
||||||
alert("Header and content are required!");
|
alert("Header and content are required!")
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardIdentifier = isExistingCard ? existingCardIdentifier : `${cardIdentifierPrefix}-${await uid()}`;
|
const cardIdentifier = isExistingCard ? existingCardIdentifier : `${cardIdentifierPrefix}-${await uid()}`
|
||||||
const pollName = `${cardIdentifier}-poll`;
|
const pollName = `${cardIdentifier}-poll`
|
||||||
const pollDescription = `Mintership Board Poll for ${userState.accountName}`;
|
const pollDescription = `Mintership Board Poll for ${userState.accountName}`
|
||||||
|
|
||||||
const cardData = {
|
const cardData = {
|
||||||
header,
|
header,
|
||||||
@ -434,14 +444,13 @@ const publishCard = async () => {
|
|||||||
creator: userState.accountName,
|
creator: userState.accountName,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
poll: pollName,
|
poll: pollName,
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let base64CardData = await objectToBase64(cardData)
|
||||||
let base64CardData = await objectToBase64(cardData);
|
|
||||||
if (!base64CardData) {
|
if (!base64CardData) {
|
||||||
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`);
|
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
|
||||||
base64CardData = btoa(JSON.stringify(cardData));
|
base64CardData = btoa(JSON.stringify(cardData))
|
||||||
}
|
}
|
||||||
|
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
@ -450,7 +459,8 @@ const publishCard = async () => {
|
|||||||
service: "BLOG_POST",
|
service: "BLOG_POST",
|
||||||
identifier: cardIdentifier,
|
identifier: cardIdentifier,
|
||||||
data64: base64CardData,
|
data64: base64CardData,
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!isExistingCard){
|
if (!isExistingCard){
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: "CREATE_POLL",
|
action: "CREATE_POLL",
|
||||||
@ -458,116 +468,266 @@ const publishCard = async () => {
|
|||||||
pollDescription,
|
pollDescription,
|
||||||
pollOptions: ['Yes, No'],
|
pollOptions: ['Yes, No'],
|
||||||
pollOwnerAddress: userState.accountAddress,
|
pollOwnerAddress: userState.accountAddress,
|
||||||
});
|
})
|
||||||
|
alert("Card and poll published successfully!")
|
||||||
alert("Card and poll published successfully!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExistingCard){
|
if (isExistingCard){
|
||||||
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
|
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
|
||||||
}
|
}
|
||||||
document.getElementById("publish-card-form").reset();
|
|
||||||
document.getElementById("publish-card-view").style.display = "none";
|
document.getElementById("publish-card-form").reset()
|
||||||
document.getElementById("cards-container").style.display = "flex";
|
document.getElementById("publish-card-view").style.display = "none"
|
||||||
await loadCards();
|
document.getElementById("cards-container").style.display = "flex"
|
||||||
|
await loadCards()
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error publishing card or poll:", error);
|
|
||||||
alert("Failed to publish card and poll.");
|
console.error("Error publishing card or poll:", error)
|
||||||
|
alert("Failed to publish card and poll.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Calculate the poll results passed from other functions with minterGroupMembers and minterAdmins ---------------------------
|
const processPollData= async (pollData, minterGroupMembers, minterAdmins, creator) => {
|
||||||
const calculatePollResults = async (pollData, minterGroupMembers, minterAdmins) => {
|
if (!pollData || !Array.isArray(pollData.voteWeights) || !Array.isArray(pollData.votes)) {
|
||||||
const memberAddresses = minterGroupMembers.map(member => member.member)
|
console.warn("Poll data is missing or invalid. pollData:", pollData)
|
||||||
const minterAdminAddresses = minterAdmins.map(member => member.member)
|
return {
|
||||||
|
adminYes: 0,
|
||||||
|
adminNo: 0,
|
||||||
|
minterYes: 0,
|
||||||
|
minterNo: 0,
|
||||||
|
totalYes: 0,
|
||||||
|
totalNo: 0,
|
||||||
|
totalYesWeight: 0,
|
||||||
|
totalNoWeight: 0,
|
||||||
|
detailsHtml: `<p>Poll data is invalid or missing.</p>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const memberAddresses = minterGroupMembers.map(m => m.member)
|
||||||
|
const minterAdminAddresses = minterAdmins.map(m => m.member)
|
||||||
const adminGroupsMembers = await fetchAllAdminGroupsMembers()
|
const adminGroupsMembers = await fetchAllAdminGroupsMembers()
|
||||||
const groupAdminAddresses = adminGroupsMembers.map(member => member.member)
|
const groupAdminAddresses = adminGroupsMembers.map(m => m.member)
|
||||||
const adminAddresses = [];
|
const adminAddresses = [...minterAdminAddresses, ...groupAdminAddresses]
|
||||||
adminAddresses.push(...minterAdminAddresses,...groupAdminAddresses);
|
let adminYes = 0, adminNo = 0
|
||||||
|
let minterYes = 0, minterNo = 0
|
||||||
|
let yesWeight = 0, noWeight = 0
|
||||||
|
|
||||||
let adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, yesWeight = 0 , noWeight = 0
|
for (const w of pollData.voteWeights) {
|
||||||
|
if (w.optionName.toLowerCase() === 'yes') {
|
||||||
|
yesWeight = w.voteWeight
|
||||||
|
} else if (w.optionName.toLowerCase() === 'no') {
|
||||||
|
noWeight = w.voteWeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pollData.voteWeights.forEach(weightData => {
|
const voterPromises = pollData.votes.map(async (vote) => {
|
||||||
if (weightData.optionName === 'Yes') {
|
const optionIndex = vote.optionIndex; // 0 => yes, 1 => no
|
||||||
yesWeight = weightData.voteWeight
|
const voterPublicKey = vote.voterPublicKey
|
||||||
} else if (weightData.optionName === 'No') {
|
const voterAddress = await getAddressFromPublicKey(voterPublicKey)
|
||||||
noWeight = weightData.voteWeight
|
|
||||||
|
if (optionIndex === 0) {
|
||||||
|
if (adminAddresses.includes(voterAddress)) {
|
||||||
|
adminYes++
|
||||||
|
} else if (memberAddresses.includes(voterAddress)) {
|
||||||
|
minterYes++
|
||||||
|
} else {
|
||||||
|
console.log(`voter ${voterAddress} is not a minter nor an admin... Not included in aggregates.`)
|
||||||
|
}
|
||||||
|
} else if (optionIndex === 1) {
|
||||||
|
if (adminAddresses.includes(voterAddress)) {
|
||||||
|
adminNo++
|
||||||
|
} else if (memberAddresses.includes(voterAddress)) {
|
||||||
|
minterNo++
|
||||||
|
} else {
|
||||||
|
console.log(`voter ${voterAddress} is not a minter nor an admin... Not included in aggregates.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let voterName = ''
|
||||||
|
try {
|
||||||
|
const nameInfo = await getNameFromAddress(voterAddress)
|
||||||
|
if (nameInfo) {
|
||||||
|
voterName = nameInfo
|
||||||
|
if (nameInfo === voterAddress) voterName = ''
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`No name for address ${voterAddress}`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
let blocksMinted = 0
|
||||||
|
try {
|
||||||
|
const addressInfo = await getAddressInfo(voterAddress)
|
||||||
|
blocksMinted = addressInfo?.blocksMinted || 0
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Failed to get addressInfo for ${voterAddress}`, e)
|
||||||
|
}
|
||||||
|
const isAdmin = adminAddresses.includes(voterAddress)
|
||||||
|
const isMinter = memberAddresses.includes(voterAddress)
|
||||||
|
|
||||||
|
return {
|
||||||
|
optionIndex,
|
||||||
|
voterPublicKey,
|
||||||
|
voterAddress,
|
||||||
|
voterName,
|
||||||
|
isAdmin,
|
||||||
|
isMinter,
|
||||||
|
blocksMinted
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const vote of pollData.votes) {
|
const allVoters = await Promise.all(voterPromises)
|
||||||
const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey)
|
const yesVoters = []
|
||||||
// console.log(`voter address: ${voterAddress}`)
|
const noVoters = []
|
||||||
|
let totalMinterAndAdminYesWeight = 0
|
||||||
|
let totalMinterAndAdminNoWeight = 0
|
||||||
|
|
||||||
if (vote.optionIndex === 0) {
|
for (const v of allVoters) {
|
||||||
adminAddresses.includes(voterAddress) ? adminYes++ : memberAddresses.includes(voterAddress) ? minterYes++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`)
|
if (v.optionIndex === 0) {
|
||||||
} else if (vote.optionIndex === 1) {
|
yesVoters.push(v)
|
||||||
adminAddresses.includes(voterAddress) ? adminNo++ : memberAddresses.includes(voterAddress) ? minterNo++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`)
|
totalMinterAndAdminYesWeight+=v.blocksMinted
|
||||||
|
} else if (v.optionIndex === 1) {
|
||||||
|
noVoters.push(v)
|
||||||
|
totalMinterAndAdminNoWeight+=v.blocksMinted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - create a new function to calculate the weights of each voting MINTER only.
|
yesVoters.sort((a,b) => b.blocksMinted - a.blocksMinted);
|
||||||
// This will give ALL weight whether voter is in minter group or not...
|
noVoters.sort((a,b) => b.blocksMinted - a.blocksMinted);
|
||||||
// until that is changed on the core we must calculate manually.
|
const yesTableHtml = buildVotersTableHtml(yesVoters, /* tableColor= */ "green")
|
||||||
const totalYesWeight = yesWeight
|
const noTableHtml = buildVotersTableHtml(noVoters, /* tableColor= */ "red")
|
||||||
const totalNoWeight = noWeight
|
const detailsHtml = `
|
||||||
|
<div class="poll-details-container" id'"${creator}-poll-details">
|
||||||
|
<h1 style ="color:rgb(123, 123, 85); text-align: center; font-size: 2.0rem">${creator}'s</h1><h3 style="color: white; text-align: center; font-size: 1.8rem"> Support Poll Result Details</h3>
|
||||||
|
<h4 style="color: green; text-align: center;">Yes Vote Details</h4>
|
||||||
|
${yesTableHtml}
|
||||||
|
<h4 style="color: red; text-align: center; margin-top: 2em;">No Vote Details</h4>
|
||||||
|
${noTableHtml}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
const totalYes = adminYes + minterYes
|
const totalYes = adminYes + minterYes
|
||||||
const totalNo = adminNo + minterNo
|
const totalNo = adminNo + minterNo
|
||||||
|
|
||||||
return { adminYes, adminNo, minterYes, minterNo, totalYes, totalNo, totalYesWeight, totalNoWeight }
|
return {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
minterYes,
|
||||||
|
minterNo,
|
||||||
|
totalYes,
|
||||||
|
totalNo,
|
||||||
|
totalYesWeight: totalMinterAndAdminYesWeight,
|
||||||
|
totalNoWeight: totalMinterAndAdminNoWeight,
|
||||||
|
detailsHtml
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildVotersTableHtml = (voters, tableColor) => {
|
||||||
|
if (!voters.length) {
|
||||||
|
return `<p>No voters here.</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide extremely dark background for the <tbody>
|
||||||
|
let bodyBackground;
|
||||||
|
if (tableColor === "green") {
|
||||||
|
bodyBackground = "rgba(0, 18, 0, 0.8)" // near-black green
|
||||||
|
} else if (tableColor === "red") {
|
||||||
|
bodyBackground = "rgba(30, 0, 0, 0.8)" // near-black red
|
||||||
|
} else {
|
||||||
|
// fallback color if needed
|
||||||
|
bodyBackground = "rgba(40, 20, 10, 0.8)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// tableColor is used for the <thead>, bodyBackground for the <tbody>
|
||||||
|
const minterColor = 'rgb(98, 122, 167)'
|
||||||
|
const adminColor = 'rgb(44, 209, 151)'
|
||||||
|
const userColor = 'rgb(102, 102, 102)'
|
||||||
|
return `
|
||||||
|
<table style="
|
||||||
|
width: 100%;
|
||||||
|
border-style: dotted;
|
||||||
|
border-width: 0.15rem;
|
||||||
|
border-color: #576b6f;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
border-collapse: collapse;
|
||||||
|
">
|
||||||
|
<thead style="background: ${tableColor}; color:rgb(238, 238, 238) ;">
|
||||||
|
<tr style="font-size: 1.5rem;">
|
||||||
|
<th style="padding: 0.1rem; text-align: center;">Voter Name/Address</th>
|
||||||
|
<th style="padding: 0.1rem; text-align: center;">Voter Type</th>
|
||||||
|
<th style="padding: 0.1rem; text-align: center;">Voter Weight(=BlocksMinted)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<!-- Tbody with extremely dark green or red -->
|
||||||
|
<tbody style="background-color: ${bodyBackground}; color: #c6c6c6;">
|
||||||
|
${voters
|
||||||
|
.map(v => {
|
||||||
|
const userType = v.isAdmin ? "Admin" : v.isMinter ? "Minter" : "User";
|
||||||
|
const pollName = v.pollName
|
||||||
|
const displayName =
|
||||||
|
v.voterName
|
||||||
|
? v.voterName
|
||||||
|
: v.voterAddress
|
||||||
|
return `
|
||||||
|
<tr style="font-size: 1.2rem; border-width: 0.1rem; border-style: dotted; border-color: lightgrey; font-weight: bold;">
|
||||||
|
<td style="padding: 1.2rem; border-width: 0.1rem; border-style: dotted; border-color: lightgrey; text-align: center;
|
||||||
|
color:${userType === 'Admin' ? adminColor : v.isMinter? minterColor : userColor };">${displayName}</td>
|
||||||
|
<td style="padding: 1.2rem; border-width: 0.1rem; border-style: dotted; border-color: lightgrey; text-align: center;
|
||||||
|
color:${userType === 'Admin' ? adminColor : v.isMinter? minterColor : userColor };">${userType}</td>
|
||||||
|
<td style="padding: 1.2rem; border-width: 0.1rem; border-style: dotted; border-color: lightgrey; text-align: center;
|
||||||
|
color:${userType === 'Admin' ? adminColor : v.isMinter? minterColor : userColor };">${v.blocksMinted}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
.join("")}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Post a comment on a card. ---------------------------------
|
// Post a comment on a card. ---------------------------------
|
||||||
const postComment = async (cardIdentifier) => {
|
const postComment = async (cardIdentifier) => {
|
||||||
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`);
|
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`)
|
||||||
const commentText = commentInput.value.trim();
|
const commentText = commentInput.value.trim()
|
||||||
if (!commentText) {
|
|
||||||
alert('Comment cannot be empty!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!commentText) {
|
||||||
|
alert('Comment cannot be empty!')
|
||||||
|
return
|
||||||
|
}
|
||||||
const commentData = {
|
const commentData = {
|
||||||
content: commentText,
|
content: commentText,
|
||||||
creator: userState.accountName,
|
creator: userState.accountName,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
}
|
||||||
|
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
||||||
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const base64CommentData = await objectToBase64(commentData);
|
const base64CommentData = await objectToBase64(commentData)
|
||||||
if (!base64CommentData) {
|
if (!base64CommentData) {
|
||||||
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`);
|
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
|
||||||
base64CommentData = btoa(JSON.stringify(commentData));
|
base64CommentData = btoa(JSON.stringify(commentData))
|
||||||
}
|
}
|
||||||
|
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: 'PUBLISH_QDN_RESOURCE',
|
action: 'PUBLISH_QDN_RESOURCE',
|
||||||
name: userState.accountName,
|
name: userState.accountName,
|
||||||
service: 'BLOG_POST',
|
service: 'BLOG_POST',
|
||||||
identifier: commentIdentifier,
|
identifier: commentIdentifier,
|
||||||
data64: base64CommentData,
|
data64: base64CommentData,
|
||||||
});
|
})
|
||||||
|
|
||||||
alert('Comment posted successfully!');
|
alert('Comment posted successfully!')
|
||||||
commentInput.value = ''; // Clear input
|
commentInput.value = ''
|
||||||
// await displayComments(cardIdentifier); // Refresh comments - We don't need to do this as comments will be displayed only after confirmation.
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error posting comment:', error);
|
console.error('Error posting comment:', error)
|
||||||
alert('Failed to post comment.');
|
alert('Failed to post comment.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
//Fetch the comments for a card with passed card identifier ----------------------------
|
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||||
const fetchCommentsForCard = async (cardIdentifier) => {
|
const fetchCommentsForCard = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
// const response = await qortalRequest({
|
const response = await searchSimple('BLOG_POST',`comment-${cardIdentifier}`, '', 0, 0, '', 'false')
|
||||||
// action: 'SEARCH_QDN_RESOURCES',
|
|
||||||
// service: 'BLOG_POST',
|
|
||||||
// query: `comment-${cardIdentifier}`,
|
|
||||||
// mode: "ALL"
|
|
||||||
// })
|
|
||||||
const response = await searchSimple('BLOG_POST',`comment-${cardIdentifier}`, '', 0)
|
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching comments for ${cardIdentifier}:`, error)
|
console.error(`Error fetching comments for ${cardIdentifier}:`, error)
|
||||||
@ -579,172 +739,218 @@ const fetchCommentsForCard = async (cardIdentifier) => {
|
|||||||
const displayComments = async (cardIdentifier) => {
|
const displayComments = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
const comments = await fetchCommentsForCard(cardIdentifier);
|
const comments = await fetchCommentsForCard(cardIdentifier);
|
||||||
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`);
|
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`)
|
||||||
|
|
||||||
// Fetch and display each comment
|
|
||||||
for (const comment of comments) {
|
for (const comment of comments) {
|
||||||
const commentDataResponse = await qortalRequest({
|
const commentDataResponse = await qortalRequest({
|
||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
name: comment.name,
|
name: comment.name,
|
||||||
service: "BLOG_POST",
|
service: "BLOG_POST",
|
||||||
identifier: comment.identifier,
|
identifier: comment.identifier,
|
||||||
});
|
})
|
||||||
const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp);
|
const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp)
|
||||||
//TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section.
|
|
||||||
const commentHTML = `
|
const commentHTML = `
|
||||||
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
||||||
<p><strong><u>${commentDataResponse.creator}</strong>:</p></u>
|
<p><strong><u>${commentDataResponse.creator}</strong>:</p></u>
|
||||||
<p>${commentDataResponse.content}</p>
|
<p>${commentDataResponse.content}</p>
|
||||||
<p><i>${timestamp}</p></i>
|
<p><i>${timestamp}</p></i>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
commentsContainer.insertAdjacentHTML('beforeend', commentHTML);
|
commentsContainer.insertAdjacentHTML('beforeend', commentHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error);
|
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
|
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
|
||||||
const toggleComments = async (cardIdentifier) => {
|
const toggleComments = async (cardIdentifier) => {
|
||||||
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`);
|
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
|
||||||
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
|
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
|
||||||
|
|
||||||
if (!commentsSection || !commentButton) return;
|
if (!commentsSection || !commentButton) return
|
||||||
const count = commentButton.dataset.commentCount;
|
const count = commentButton.dataset.commentCount
|
||||||
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display);
|
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display)
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
// Show comments
|
// Show comments
|
||||||
commentButton.textContent = "LOADING...";
|
commentButton.textContent = "LOADING..."
|
||||||
await displayComments(cardIdentifier);
|
await displayComments(cardIdentifier);
|
||||||
commentsSection.style.display = 'block';
|
commentsSection.style.display = 'block'
|
||||||
// Change the button text to 'HIDE COMMENTS'
|
// Change the button text to 'HIDE COMMENTS'
|
||||||
commentButton.textContent = 'HIDE COMMENTS';
|
commentButton.textContent = 'HIDE COMMENTS'
|
||||||
} else {
|
} else {
|
||||||
// Hide comments
|
// Hide comments
|
||||||
commentsSection.style.display = 'none';
|
commentsSection.style.display = 'none'
|
||||||
commentButton.textContent = `COMMENTS (${count})`;
|
commentButton.textContent = `COMMENTS (${count})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const countComments = async (cardIdentifier) => {
|
const countComments = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
// const response = await qortalRequest({
|
const response = await searchSimple('BLOG_POST', `comment-${cardIdentifier}`, '', 0, 0, '', 'false')
|
||||||
// action: 'SEARCH_QDN_RESOURCES',
|
return Array.isArray(response) ? response.length : 0
|
||||||
// service: 'BLOG_POST',
|
|
||||||
// query: `comment-${cardIdentifier}`,
|
|
||||||
// mode: "ALL"
|
|
||||||
// })
|
|
||||||
// Changed to searchSimple to hopefully improve load times...
|
|
||||||
const response = await searchSimple('BLOG_POST', `comment-${cardIdentifier}`, '', 0)
|
|
||||||
// Just return the count; no need to decrypt each comment here
|
|
||||||
return Array.isArray(response) ? response.length : 0;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching comment count for ${cardIdentifier}:`, error);
|
console.error(`Error fetching comment count for ${cardIdentifier}:`, error)
|
||||||
return 0;
|
return 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const createModal = async () => {
|
const createModal = (modalType='') => {
|
||||||
|
if (document.getElementById(`${modalType}-modal`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isIframe = (modalType === 'links')
|
||||||
|
|
||||||
const modalHTML = `
|
const modalHTML = `
|
||||||
<div id="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 1000;">
|
<div id="${modalType}-modal"
|
||||||
<div style="position: relative; margin: 10% auto; width: 95%; height: 80%; background: white; border-radius: 10px; overflow: hidden;">
|
style="display: none;
|
||||||
<iframe id="modalContent" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
position: fixed;
|
||||||
<button onclick="closeModal()" style="position: absolute; top: 10px; right: 10px; background: red; color: white; border: none; padding: 5px 10px; border-radius: 5px;">Close</button>
|
top: 0; left: 0;
|
||||||
</div>
|
width: 100%; height: 100%;
|
||||||
</div>
|
background: rgba(0, 0, 0, 0.50);
|
||||||
`;
|
z-index: 10000;">
|
||||||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
<div id="${modalType}-modalContainer"
|
||||||
|
style="position: relative;
|
||||||
|
margin: 10% auto;
|
||||||
|
width: 80%;
|
||||||
|
height: 70%;
|
||||||
|
background:rgba(0, 0, 0, 0.80) ;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;">
|
||||||
|
${
|
||||||
|
isIframe
|
||||||
|
? `<iframe id="${modalType}-modalContent"
|
||||||
|
src=""
|
||||||
|
style="width: 100%; height: 100%; border: none;">
|
||||||
|
</iframe>`
|
||||||
|
: `<div id="${modalType}-modalContent"
|
||||||
|
style="width: 100%; height: 100%; overflow: auto;">
|
||||||
|
</div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to open the modal
|
<button onclick="closeModal('${modalType}')"
|
||||||
const openModal = async (link) => {
|
style="position: absolute; top: 0.2rem; right: 0.2rem;
|
||||||
const processedLink = await processLink(link) // Process the link to replace `qortal://` for rendering in modal
|
background:rgba(0, 0, 0, 0.66); color: white; border: none;
|
||||||
const modal = document.getElementById('modal');
|
font-size: 2.2rem;
|
||||||
const modalContent = document.getElementById('modalContent');
|
padding: 0.4rem 1rem;
|
||||||
modalContent.src = processedLink; // Set the iframe source to the link
|
border-radius: 0.33rem;
|
||||||
modal.style.display = 'block'; // Show the modal
|
border-style: dashed;
|
||||||
|
border-color:rgb(213, 224, 225);
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(73, 7, 7) '"
|
||||||
|
onmouseout="this.style.backgroundColor='rgba(5, 14, 11, 0.63) '">
|
||||||
|
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
document.body.insertAdjacentHTML('beforeend', modalHTML)
|
||||||
|
|
||||||
|
const modal = document.getElementById(`${modalType}-modal`)
|
||||||
|
window.addEventListener('click', (event) => {
|
||||||
|
if (event.target === modal) {
|
||||||
|
closeModal(modalType)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to close the modal
|
|
||||||
const closeModal = async () => {
|
const openLinksModal = async (link) => {
|
||||||
const modal = document.getElementById('modal');
|
const processedLink = await processLink(link)
|
||||||
const modalContent = document.getElementById('modalContent');
|
const modal = document.getElementById('links-modal')
|
||||||
modal.style.display = 'none'; // Hide the modal
|
const modalContent = document.getElementById('links-modalContent')
|
||||||
modalContent.src = ''; // Clear the iframe source
|
modalContent.src = processedLink
|
||||||
|
modal.style.display = 'block'
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = async (modalType='links') => {
|
||||||
|
const modal = document.getElementById(`${modalType}-modal`)
|
||||||
|
const modalContent = document.getElementById(`${modalType}-modalContent`)
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none'
|
||||||
|
}
|
||||||
|
if (modalContent) {
|
||||||
|
modalContent.src = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const processLink = async (link) => {
|
const processLink = async (link) => {
|
||||||
if (link.startsWith('qortal://')) {
|
if (link.startsWith('qortal://')) {
|
||||||
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/);
|
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/)
|
||||||
if (match) {
|
if (match) {
|
||||||
const firstParam = match[1].toUpperCase();
|
const firstParam = match[1].toUpperCase()
|
||||||
const remainingPath = match[2] || "";
|
const remainingPath = match[2] || ""
|
||||||
const themeColor = window._qdnTheme || 'default'; // Fallback to 'default' if undefined
|
const themeColor = window._qdnTheme || 'default'
|
||||||
|
|
||||||
// Simulating async operation if needed
|
await new Promise(resolve => setTimeout(resolve, 10))
|
||||||
await new Promise(resolve => setTimeout(resolve, 10));
|
|
||||||
|
|
||||||
// Append theme as a query parameter
|
return `/render/${firstParam}${remainingPath}?theme=${themeColor}`
|
||||||
return `/render/${firstParam}${remainingPath}?theme=${themeColor}`;
|
}
|
||||||
|
}
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
const togglePollDetails = (cardIdentifier) => {
|
||||||
|
const detailsDiv = document.getElementById(`poll-details-${cardIdentifier}`)
|
||||||
|
const modal = document.getElementById(`poll-details-modal`)
|
||||||
|
const modalContent = document.getElementById(`poll-details-modalContent`)
|
||||||
|
|
||||||
|
if (!detailsDiv || !modal || !modalContent) return
|
||||||
|
|
||||||
|
// modalContent.appendChild(detailsDiv)
|
||||||
|
modalContent.innerHTML = detailsDiv.innerHTML
|
||||||
|
modal.style.display = 'block'
|
||||||
|
window.onclick = (event) => {
|
||||||
|
if (event.target === modal) {
|
||||||
|
modal.style.display = 'none'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return link;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hash the name and map it to a dark pastel color
|
|
||||||
const generateDarkPastelBackgroundBy = (name) => {
|
const generateDarkPastelBackgroundBy = (name) => {
|
||||||
// 1) Basic string hashing
|
let hash = 0
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < name.length; i++) {
|
for (let i = 0; i < name.length; i++) {
|
||||||
hash = (hash << 5) - hash + name.charCodeAt(i);
|
hash = (hash << 5) - hash + name.charCodeAt(i)
|
||||||
hash |= 0; // Convert to 32-bit integer
|
hash |= 0
|
||||||
}
|
}
|
||||||
const safeHash = Math.abs(hash);
|
const safeHash = Math.abs(hash)
|
||||||
|
const hueSteps = 69.69
|
||||||
// 2) Restrict hue to a 'blue-ish' range (150..270 = 120 degrees total)
|
const hueIndex = safeHash % hueSteps
|
||||||
|
const hueRange = 288
|
||||||
const hueSteps = 69.69;
|
const hue = 140 + (hueIndex * (hueRange / hueSteps))
|
||||||
const hueIndex = safeHash % hueSteps;
|
|
||||||
|
|
||||||
const hueRange = 288;
|
|
||||||
const hue = 140 + (hueIndex * (hueRange / hueSteps));
|
|
||||||
|
|
||||||
|
|
||||||
// 3) Saturation:
|
|
||||||
const satSteps = 13.69;
|
|
||||||
const satIndex = safeHash % satSteps;
|
|
||||||
const saturation = 18 + (satIndex * 1.333);
|
|
||||||
|
|
||||||
// 4) Lightness:
|
|
||||||
const lightSteps = 3.69;
|
|
||||||
const lightIndex = safeHash % lightSteps;
|
|
||||||
const lightness = 7 + lightIndex;
|
|
||||||
|
|
||||||
// 5) Return the HSL color string
|
|
||||||
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const satSteps = 13.69
|
||||||
|
const satIndex = safeHash % satSteps
|
||||||
|
const saturation = 18 + (satIndex * 1.333)
|
||||||
|
|
||||||
|
const lightSteps = 3.69
|
||||||
|
const lightIndex = safeHash % lightSteps
|
||||||
|
const lightness = 7 + lightIndex
|
||||||
|
|
||||||
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
||||||
|
}
|
||||||
|
|
||||||
// Create the overall Minter Card HTML -----------------------------------------------
|
// Create the overall Minter Card HTML -----------------------------------------------
|
||||||
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, BgColor) => {
|
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, BgColor) => {
|
||||||
const { header, content, links, creator, timestamp, poll } = cardData;
|
const { header, content, links, creator, timestamp, poll } = cardData;
|
||||||
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
||||||
// const avatarUrl = `/arbitrary/THUMBNAIL/${creator}/qortal_avatar`;
|
|
||||||
const avatarHtml = await getMinterAvatar(creator)
|
const avatarHtml = await getMinterAvatar(creator)
|
||||||
const linksHTML = links.map((link, index) => `
|
const linksHTML = links.map((link, index) => `
|
||||||
<button onclick="openModal('${link}')">
|
<button onclick="openLinksModal('${link}')">
|
||||||
${`Link ${index + 1} - ${link}`}
|
${`Link ${index + 1} - ${link}`}
|
||||||
</button>
|
</button>
|
||||||
`).join("");
|
`).join("")
|
||||||
|
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0, detailsHtml } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator)
|
||||||
|
createModal('links')
|
||||||
|
createModal('poll-details')
|
||||||
|
|
||||||
const minterGroupMembers = await fetchMinterGroupMembers();
|
|
||||||
const minterAdmins = await fetchMinterGroupAdmins();
|
|
||||||
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0 } = await calculatePollResults(pollResults, minterGroupMembers, minterAdmins)
|
|
||||||
await createModal()
|
|
||||||
return `
|
return `
|
||||||
<div class="minter-card" style="background-color: ${BgColor}">
|
<div class="minter-card" style="background-color: ${BgColor}">
|
||||||
<div class="minter-card-header">
|
<div class="minter-card-header">
|
||||||
@ -762,6 +968,10 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
</div>
|
</div>
|
||||||
<div class="results-header support-header"><h5>CURRENT RESULTS</h5></div>
|
<div class="results-header support-header"><h5>CURRENT RESULTS</h5></div>
|
||||||
<div class="minter-card-results">
|
<div class="minter-card-results">
|
||||||
|
<button onclick="togglePollDetails('${cardIdentifier}')">Display Poll Details</button>
|
||||||
|
<div id="poll-details-${cardIdentifier}" style="display: none;">
|
||||||
|
${detailsHtml}
|
||||||
|
</div>
|
||||||
<div class="admin-results">
|
<div class="admin-results">
|
||||||
<span class="admin-yes">Admin Yes: ${adminYes}</span>
|
<span class="admin-yes">Admin Yes: ${adminYes}</span>
|
||||||
<span class="admin-no">Admin No: ${adminNo}</span>
|
<span class="admin-no">Admin No: ${adminNo}</span>
|
||||||
|
@ -147,6 +147,32 @@ const getUserAddress = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAddressInfo = async (address) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch (`${baseUrl}/addresses/${address}`, {
|
||||||
|
headers: { 'Accept': 'application/json' },
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
const addressData = await response.json()
|
||||||
|
console.log(`address data:`,addressData)
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: addressData.address,
|
||||||
|
reference: addressData.reference,
|
||||||
|
publicKey: addressData.publicKey,
|
||||||
|
defaultGroupId: addressData.defaultGroupId,
|
||||||
|
flags: addressData.flags,
|
||||||
|
level: addressData.level,
|
||||||
|
blocksMinted: addressData.blocksMinted,
|
||||||
|
blocksMintedAdjustment: addressData.blocksMintedAdjustment,
|
||||||
|
blocksMintedPenalty: addressData.blocksMintedPenalty
|
||||||
|
}
|
||||||
|
} catch(error){
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchOwnerAddressFromName = async (name) => {
|
const fetchOwnerAddressFromName = async (name) => {
|
||||||
console.log('fetchOwnerAddressFromName called')
|
console.log('fetchOwnerAddressFromName called')
|
||||||
console.log('name:', name)
|
console.log('name:', name)
|
||||||
@ -324,7 +350,7 @@ const login = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNamesFromAddress = async (address) => {
|
const getNameFromAddress = async (address) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, {
|
const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
21
index.html
21
index.html
@ -68,7 +68,7 @@
|
|||||||
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
|
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.64b<br></a></span>
|
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.65b<br></a></span>
|
||||||
</div>
|
</div>
|
||||||
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
|
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
|
||||||
|
|
||||||
@ -197,6 +197,23 @@
|
|||||||
|
|
||||||
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-7 card">
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<h2 class="mbr-section-title mbr-fonts-style display-2">
|
||||||
|
New Version + Features 12-28-2024</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-5 card">
|
||||||
|
<div class="text-wrapper">
|
||||||
|
<p class="mbr-text mbr-fonts-style display-7">
|
||||||
|
New Version now includes full POLL RESULT DETAILS for every card. This includes each vote, the voter name, their weight (blocksMinted), and much more. Many additional code-cleanup changes were made as well. Also... The ARBITRARY REBUILD BOOTSTRAP should now be available, IF YOU ARE HAVING ISSUES SEEING DATA, PLEASE BOOTSTRAP YOUR NODE TO OBTAIN A REBUILT DATABASE. Thank you!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-lg-7 card">
|
<div class="col-12 col-lg-7 card">
|
||||||
@ -366,7 +383,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="link-wrap" href="#">
|
<a class="link-wrap" href="#">
|
||||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.64beta</p>
|
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.65beta</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
|
Loading…
Reference in New Issue
Block a user