Merge pull request 'Show level, adjustment / penalty, user vote & sort by name, newest comments, least or most votes on minter & admin cards, & fix forum date / time format' (#5) from Quick_Mythril/Q-Mintership-Alpha:testing-20250122 into main
Reviewed-on: https://gitea.qortal.link/crowetic/Q-Mintership-Alpha/pulls/5 Reviewed-by: crowetic <jason@crowetic.com>
This commit is contained in:
commit
4aa119cb73
@ -76,6 +76,13 @@ const loadAdminBoardPage = async () => {
|
|||||||
<p> More functionality will be added over time. One of the first features will be the ability to output the existing card data 'decisions', to a json formatted list in order to allow crowetic to run his script easily until the final Mintership proposal changes are completed, and the MINTER group is transferred to 'null'.</p>
|
<p> More functionality will be added over time. One of the first features will be the ability to output the existing card data 'decisions', to a json formatted list in order to allow crowetic to run his script easily until the final Mintership proposal changes are completed, and the MINTER group is transferred to 'null'.</p>
|
||||||
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px;">Publish Encrypted Card</button>
|
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px;">Publish Encrypted Card</button>
|
||||||
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
|
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
|
||||||
|
<select id="sort-select" style="margin-left: 10px; padding: 5px;">
|
||||||
|
<option value="newest" selected>Sort by Date</option>
|
||||||
|
<option value="name">Sort by Name</option>
|
||||||
|
<option value="recent-comments">Newest Comments</option>
|
||||||
|
<option value="least-votes">Least Votes</option>
|
||||||
|
<option value="most-votes">Most Votes</option>
|
||||||
|
</select>
|
||||||
<div class="show-card-checkbox" style="margin-top: 1em;">
|
<div class="show-card-checkbox" style="margin-top: 1em;">
|
||||||
<input type="checkbox" id="admin-show-hidden-checkbox" name="adminHidden" />
|
<input type="checkbox" id="admin-show-hidden-checkbox" name="adminHidden" />
|
||||||
<label for="admin-show-hidden-checkbox">Show User-Hidden Cards?</label>
|
<label for="admin-show-hidden-checkbox">Show User-Hidden Cards?</label>
|
||||||
@ -173,6 +180,11 @@ const loadAdminBoardPage = async () => {
|
|||||||
await publishEncryptedCard(isTopicChecked)
|
await publishEncryptedCard(isTopicChecked)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.getElementById("sort-select").addEventListener("change", async () => {
|
||||||
|
// Re-load the cards whenever user chooses a new sort option.
|
||||||
|
await fetchAllEncryptedCards()
|
||||||
|
})
|
||||||
|
|
||||||
createScrollToTopButton()
|
createScrollToTopButton()
|
||||||
// await fetchAndValidateAllAdminCards()
|
// await fetchAndValidateAllAdminCards()
|
||||||
await updateOrSaveAdminGroupsDataLocally()
|
await updateOrSaveAdminGroupsDataLocally()
|
||||||
@ -404,12 +416,125 @@ const fetchAllEncryptedCards = async (isRefresh = false) => {
|
|||||||
// Convert the map into an array of final cards
|
// Convert the map into an array of final cards
|
||||||
const finalCards = Array.from(mostRecentCardsMap.values());
|
const finalCards = Array.from(mostRecentCardsMap.values());
|
||||||
|
|
||||||
// Sort cards by timestamp (most recent first)
|
let selectedSort = 'newest'
|
||||||
finalCards.sort((a, b) => {
|
const sortSelect = document.getElementById('sort-select')
|
||||||
const timestampA = a.card.updated || a.card.created || 0
|
if (sortSelect) {
|
||||||
const timestampB = b.card.updated || b.card.created || 0
|
selectedSort = sortSelect.value
|
||||||
return timestampB - timestampA;
|
}
|
||||||
})
|
|
||||||
|
if (selectedSort === 'name') {
|
||||||
|
// Sort alphabetically by the minter's name
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
const nameA = a.decryptedCardData.minterName?.toLowerCase() || ''
|
||||||
|
const nameB = b.decryptedCardData.minterName?.toLowerCase() || ''
|
||||||
|
return nameA.localeCompare(nameB)
|
||||||
|
})
|
||||||
|
} else if (selectedSort === 'recent-comments') {
|
||||||
|
// We need each card's newest comment timestamp for sorting
|
||||||
|
for (let card of finalCards) {
|
||||||
|
card.newestCommentTimestamp = await getNewestAdminCommentTimestamp(card.card.identifier)
|
||||||
|
}
|
||||||
|
// Then sort descending by newest comment
|
||||||
|
finalCards.sort((a, b) =>
|
||||||
|
(b.newestCommentTimestamp || 0) - (a.newestCommentTimestamp || 0)
|
||||||
|
)
|
||||||
|
} else if (selectedSort === 'least-votes') {
|
||||||
|
// TODO: Add the logic to sort by LEAST total ADMIN votes, then totalYesWeight
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
for (const finalCard of finalCards) {
|
||||||
|
try {
|
||||||
|
const pollName = finalCard.decryptedCardData.poll
|
||||||
|
// If card or poll is missing, default to zero
|
||||||
|
if (!pollName) {
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const pollResults = await fetchPollResults(pollName)
|
||||||
|
if (!pollResults || pollResults.error) {
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Pull only the adminYes/adminNo/totalYesWeight from processPollData
|
||||||
|
const {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
totalYesWeight
|
||||||
|
} = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
finalCard.decryptedCardData.creator,
|
||||||
|
finalCard.card.identifier
|
||||||
|
)
|
||||||
|
finalCard._adminTotalVotes = adminYes + adminNo
|
||||||
|
finalCard._yesWeight = totalYesWeight
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching or processing poll for card ${finalCard.card.identifier}:`, error)
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort ascending by (adminYes + adminNo), then descending by totalYesWeight
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
const diffAdminTotal = a._adminTotalVotes - b._adminTotalVotes
|
||||||
|
if (diffAdminTotal !== 0) return diffAdminTotal
|
||||||
|
// If there's a tie, show the card with higher yesWeight first
|
||||||
|
return b._yesWeight - a._yesWeight
|
||||||
|
})
|
||||||
|
} else if (selectedSort === 'most-votes') {
|
||||||
|
// TODO: Add the logic to sort by MOST total ADMIN votes, then totalYesWeight
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
for (const finalCard of finalCards) {
|
||||||
|
try {
|
||||||
|
const pollName = finalCard.decryptedCardData.poll
|
||||||
|
if (!pollName) {
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const pollResults = await fetchPollResults(pollName)
|
||||||
|
if (!pollResults || pollResults.error) {
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
totalYesWeight
|
||||||
|
} = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
finalCard.decryptedCardData.creator,
|
||||||
|
finalCard.card.identifier
|
||||||
|
)
|
||||||
|
finalCard._adminTotalVotes = adminYes + adminNo
|
||||||
|
finalCard._yesWeight = totalYesWeight
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching or processing poll for card ${finalCard.card.identifier}:`, error)
|
||||||
|
finalCard._adminTotalVotes = 0
|
||||||
|
finalCard._yesWeight = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort descending by (adminYes + adminNo), then descending by totalYesWeight
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
const diffAdminTotal = b._adminTotalVotes - a._adminTotalVotes
|
||||||
|
if (diffAdminTotal !== 0) return diffAdminTotal
|
||||||
|
return b._yesWeight - a._yesWeight
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Sort cards by timestamp (most recent first)
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
const timestampA = a.card.updated || a.card.created || 0
|
||||||
|
const timestampB = b.card.updated || b.card.created || 0
|
||||||
|
return timestampB - timestampA;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
encryptedCardsContainer.innerHTML = ""
|
encryptedCardsContainer.innerHTML = ""
|
||||||
|
|
||||||
@ -675,7 +800,7 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
|||||||
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 (!isUpdateCard) {
|
if (!isUpdateCard) {
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: "CREATE_POLL",
|
action: "CREATE_POLL",
|
||||||
@ -1080,6 +1205,23 @@ const handleBanMinter = async (minterName) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNewestAdminCommentTimestamp = async (cardIdentifier) => {
|
||||||
|
try {
|
||||||
|
const comments = await fetchEncryptedComments(cardIdentifier)
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const newestTimestamp = comments.reduce((acc, comment) => {
|
||||||
|
const cTime = comment.updated || comment.created || 0
|
||||||
|
return cTime > acc ? cTime : acc
|
||||||
|
}, 0)
|
||||||
|
return newestTimestamp
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get newest comment timestamp:', err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -1113,29 +1255,41 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
|
|
||||||
const minterOrTopicHtml = ((showTopic) || (isUndefinedUser)) ? `
|
const minterOrTopicHtml = ((showTopic) || (isUndefinedUser)) ? `
|
||||||
<div class="support-header"><h5> REGARDING (Topic): </h5></div>
|
<div class="support-header"><h5> REGARDING (Topic): </h5></div>
|
||||||
<h3>${minterName}</h3>` :
|
<h3>${minterName}` :
|
||||||
`
|
`
|
||||||
<div class="support-header"><h5> REGARDING (Name): </h5></div>
|
<div class="support-header"><h5> REGARDING (Name): </h5></div>
|
||||||
${minterAvatar}
|
${minterAvatar}
|
||||||
<h3>${minterName}</h3>`
|
<h3>${minterName}`
|
||||||
|
|
||||||
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, detailsHtml } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator, cardIdentifier)
|
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0, detailsHtml, userVote = null } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator, cardIdentifier)
|
||||||
|
|
||||||
createModal('links')
|
createModal('links')
|
||||||
createModal('poll-details')
|
createModal('poll-details')
|
||||||
|
|
||||||
let showRemoveHtml
|
let showRemoveHtml
|
||||||
let altText
|
let altText = ''
|
||||||
|
let penaltyText = ''
|
||||||
|
let adjustmentText = ''
|
||||||
const verifiedName = await validateMinterName(minterName)
|
const verifiedName = await validateMinterName(minterName)
|
||||||
|
let levelText = '</h3>'
|
||||||
|
|
||||||
if (verifiedName) {
|
if (verifiedName) {
|
||||||
const accountInfo = await getNameInfo(verifiedName)
|
const accountInfo = await getNameInfo(verifiedName)
|
||||||
const accountAddress = accountInfo.owner
|
const accountAddress = accountInfo.owner
|
||||||
|
const addressInfo = await getAddressInfo(accountAddress)
|
||||||
|
levelText = ` - Level ${addressInfo.level}</h3>`
|
||||||
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
||||||
|
penaltyText = addressInfo.blocksMintedPenalty == 0 ? '' : '<p>(has Blocks Penalty)<p>'
|
||||||
|
adjustmentText = addressInfo.blocksMintedAdjustment == 0 ? '' : '<p>(has Blocks Adjustment)<p>'
|
||||||
const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
||||||
showRemoveHtml = removeActionsHtml
|
showRemoveHtml = removeActionsHtml
|
||||||
|
if (userVote === 0) {
|
||||||
|
cardColorCode = "rgba(0, 192, 0, 0.3)"; // or any green you want
|
||||||
|
} else if (userVote === 1) {
|
||||||
|
cardColorCode = "rgba(192, 0, 0, 0.3)"; // or any red you want
|
||||||
|
}
|
||||||
|
|
||||||
if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
||||||
console.warn(`account was already banned, displaying as such...`)
|
console.warn(`account was already banned, displaying as such...`)
|
||||||
@ -1176,9 +1330,9 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
<h2 class="support-header"> Created By: </h2>
|
<h2 class="support-header"> Created By: </h2>
|
||||||
${creatorAvatar}
|
${creatorAvatar}
|
||||||
<h2>${creator}</h2>
|
<h2>${creator}</h2>
|
||||||
${minterOrTopicHtml}
|
${minterOrTopicHtml}${levelText}
|
||||||
<p>${header}</p>
|
<p>${header}</p>
|
||||||
${altText}
|
${penaltyText}${adjustmentText}${altText}
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
${content}
|
${content}
|
||||||
|
@ -30,6 +30,13 @@ const loadMinterBoardPage = async () => {
|
|||||||
<p style="font-size: 1.25em;"> Publish a Minter Card with Information, and obtain and view the support of the community. Welcome to the Minter Board!</p>
|
<p style="font-size: 1.25em;"> Publish a Minter Card with Information, and obtain and view the support of the community. Welcome to the Minter Board!</p>
|
||||||
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px; background-color: ${publishButtonColor}">Publish Minter Card</button>
|
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px; background-color: ${publishButtonColor}">Publish Minter Card</button>
|
||||||
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
|
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
|
||||||
|
<select id="sort-select" style="margin-left: 10px; padding: 5px;">
|
||||||
|
<option value="newest" selected>Sort by Date</option>
|
||||||
|
<option value="name">Sort by Name</option>
|
||||||
|
<option value="recent-comments">Newest Comments</option>
|
||||||
|
<option value="least-votes">Least Votes</option>
|
||||||
|
<option value="most-votes">Most Votes</option>
|
||||||
|
</select>
|
||||||
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
|
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
|
||||||
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 20px;">
|
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 20px;">
|
||||||
<form id="publish-card-form">
|
<form id="publish-card-form">
|
||||||
@ -119,6 +126,12 @@ const loadMinterBoardPage = async () => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
await publishCard(minterCardIdentifierPrefix)
|
await publishCard(minterCardIdentifierPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.getElementById("sort-select").addEventListener("change", async () => {
|
||||||
|
// Re-load the cards whenever user chooses a new sort option.
|
||||||
|
await loadCards(minterCardIdentifierPrefix)
|
||||||
|
})
|
||||||
|
|
||||||
await featureTriggerCheck()
|
await featureTriggerCheck()
|
||||||
await loadCards(minterCardIdentifierPrefix)
|
await loadCards(minterCardIdentifierPrefix)
|
||||||
}
|
}
|
||||||
@ -280,6 +293,159 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
}
|
}
|
||||||
const finalCards = await processMinterCards(validCards)
|
const finalCards = await processMinterCards(validCards)
|
||||||
|
|
||||||
|
// finalCards is already sorted by newest (timestamp) by processMinterCards.
|
||||||
|
// We'll re-sort if "Name" or "Recent Comments" is selected.
|
||||||
|
|
||||||
|
// Grab the selected sort from the dropdown (if it exists).
|
||||||
|
let selectedSort = 'newest'
|
||||||
|
const sortSelect = document.getElementById('sort-select')
|
||||||
|
if (sortSelect) {
|
||||||
|
selectedSort = sortSelect.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSort === 'name') {
|
||||||
|
// Sort alphabetically by the creator's name
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
const nameA = a.name?.toLowerCase() || ''
|
||||||
|
const nameB = b.name?.toLowerCase() || ''
|
||||||
|
return nameA.localeCompare(nameB)
|
||||||
|
})
|
||||||
|
} else if (selectedSort === 'recent-comments') {
|
||||||
|
// We need each card's newest comment timestamp for sorting
|
||||||
|
for (let card of finalCards) {
|
||||||
|
card.newestCommentTimestamp = await getNewestCommentTimestamp(card.identifier)
|
||||||
|
}
|
||||||
|
// Then sort descending by newest comment
|
||||||
|
finalCards.sort((a, b) =>
|
||||||
|
(b.newestCommentTimestamp || 0) - (a.newestCommentTimestamp || 0)
|
||||||
|
)
|
||||||
|
} else if (selectedSort === 'least-votes') {
|
||||||
|
// For each card, fetch its poll data so we know how many admin + minter votes it has.
|
||||||
|
// Store those values on the card object so we can sort on them.
|
||||||
|
// Sort ascending by admin votes; if there's a tie, sort ascending by minter votes.
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
// For each card, fetch the poll data & store counts on the card object.
|
||||||
|
for (const card of finalCards) {
|
||||||
|
try {
|
||||||
|
// We must fetch the card data from QDN to get the `poll` name
|
||||||
|
const cardDataResponse = await qortalRequest({
|
||||||
|
action: "FETCH_QDN_RESOURCE",
|
||||||
|
name: card.name,
|
||||||
|
service: "BLOG_POST",
|
||||||
|
identifier: card.identifier,
|
||||||
|
})
|
||||||
|
// If the card or poll is missing, skip
|
||||||
|
if (!cardDataResponse || !cardDataResponse.poll) {
|
||||||
|
card._adminVotes = 0
|
||||||
|
card._adminYes = 0
|
||||||
|
card._minterVotes = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
||||||
|
const {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
minterYes,
|
||||||
|
minterNo
|
||||||
|
} = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
cardDataResponse.creator,
|
||||||
|
card.identifier
|
||||||
|
)
|
||||||
|
// Store the totals so we can sort on them
|
||||||
|
card._adminVotes = adminYes + adminNo
|
||||||
|
card._adminYes = adminYes
|
||||||
|
card._minterVotes = minterYes + minterNo
|
||||||
|
card._minterYes = minterYes
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching or processing poll for card ${card.identifier}:`, error)
|
||||||
|
// If something fails, default to zero so it sorts "lowest"
|
||||||
|
card._adminVotes = 0
|
||||||
|
card._adminYes = 0
|
||||||
|
card._minterVotes = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now that each card has _adminVotes + _minterVotes, do an ascending sort:
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
// admin votes ascending
|
||||||
|
const diffAdminTotal = a._adminVotes - b._adminVotes
|
||||||
|
if (diffAdminTotal !== 0) return diffAdminTotal
|
||||||
|
// admin YES ascending
|
||||||
|
const diffAdminYes = a._adminYes - b._adminYes
|
||||||
|
if (diffAdminYes !== 0) return diffAdminYes
|
||||||
|
// minter votes ascending
|
||||||
|
const diffMinterTotal = a._minterVotes - b._minterVotes
|
||||||
|
if (diffMinterTotal !== 0) return diffMinterTotal
|
||||||
|
// minter YES ascending
|
||||||
|
return a._minterYes - b._minterYes
|
||||||
|
})
|
||||||
|
} else if (selectedSort === 'most-votes') {
|
||||||
|
// Fetch poll data & store admin + minter votes as before.
|
||||||
|
// Sort descending by admin votes; if there's a tie, sort descending by minter votes.
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
for (const card of finalCards) {
|
||||||
|
try {
|
||||||
|
const cardDataResponse = await qortalRequest({
|
||||||
|
action: "FETCH_QDN_RESOURCE",
|
||||||
|
name: card.name,
|
||||||
|
service: "BLOG_POST",
|
||||||
|
identifier: card.identifier,
|
||||||
|
})
|
||||||
|
if (!cardDataResponse || !cardDataResponse.poll) {
|
||||||
|
card._adminVotes = 0
|
||||||
|
card._adminYes = 0
|
||||||
|
card._minterVotes = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
||||||
|
const {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
minterYes,
|
||||||
|
minterNo
|
||||||
|
} = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
cardDataResponse.creator,
|
||||||
|
card.identifier
|
||||||
|
)
|
||||||
|
card._adminVotes = adminYes + adminNo
|
||||||
|
card._adminYes = adminYes
|
||||||
|
card._minterVotes = minterYes + minterNo
|
||||||
|
card._minterYes = minterYes
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching or processing poll for card ${card.identifier}:`, error)
|
||||||
|
card._adminVotes = 0
|
||||||
|
card._adminYes = 0
|
||||||
|
card._minterVotes = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort descending by admin votes, then minter votes
|
||||||
|
finalCards.sort((a, b) => {
|
||||||
|
// admin votes descending
|
||||||
|
const diffAdminTotal = b._adminVotes - a._adminVotes
|
||||||
|
if (diffAdminTotal !== 0) return diffAdminTotal
|
||||||
|
// admin YES descending
|
||||||
|
const diffAdminYes = b._adminYes - a._adminYes
|
||||||
|
if (diffAdminYes !== 0) return diffAdminYes
|
||||||
|
// minter votes descending
|
||||||
|
const diffMinterTotal = b._minterVotes - a._minterVotes
|
||||||
|
if (diffMinterTotal !== 0) return diffMinterTotal
|
||||||
|
// minter YES descending
|
||||||
|
return b._minterYes - a._minterYes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// else 'newest' => do nothing, finalCards stays in newest-first order
|
||||||
|
|
||||||
// Display skeleton cards immediately
|
// Display skeleton cards immediately
|
||||||
cardsContainer.innerHTML = ""
|
cardsContainer.innerHTML = ""
|
||||||
finalCards.forEach(card => {
|
finalCards.forEach(card => {
|
||||||
@ -308,10 +474,13 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
removeSkeleton(card.identifier)
|
removeSkeleton(card.identifier)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pollPublisherPublicKey = await getPollPublisherPublicKey(cardDataResponse.poll)
|
|
||||||
const cardPublisherPublicKey = await getPublicKeyByName(card.name)
|
|
||||||
|
|
||||||
if (pollPublisherPublicKey != cardPublisherPublicKey) {
|
// Getting the poll owner address uses the same API call as public key, so takes the same time, but address will be needed later.
|
||||||
|
const pollPublisherAddress = await getPollOwnerAddress(cardDataResponse.poll)
|
||||||
|
// Getting the card publisher address to compare instead should cause faster loading, since getting the public key by name first gets the address then converts it
|
||||||
|
const cardPublisherAddress = await fetchOwnerAddressFromName(card.name)
|
||||||
|
|
||||||
|
if (pollPublisherAddress != cardPublisherAddress) {
|
||||||
console.warn(`not displaying card, QuickMythril pollHijack attack found! Discarding card with identifier: ${card.identifier}`)
|
console.warn(`not displaying card, QuickMythril pollHijack attack found! Discarding card with identifier: ${card.identifier}`)
|
||||||
removeSkeleton(card.identifier)
|
removeSkeleton(card.identifier)
|
||||||
return
|
return
|
||||||
@ -346,7 +515,7 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
const finalCardHTML = isARBoard ? // If we're calling from the ARBoard, we will create HTML with a different function.
|
const finalCardHTML = isARBoard ? // If we're calling from the ARBoard, we will create HTML with a different function.
|
||||||
await createARCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
await createARCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
||||||
:
|
:
|
||||||
await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor, cardPublisherAddress)
|
||||||
replaceSkeleton(card.identifier, finalCardHTML)
|
replaceSkeleton(card.identifier, finalCardHTML)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -576,7 +745,8 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
|
|||||||
totalNo: 0,
|
totalNo: 0,
|
||||||
totalYesWeight: 0,
|
totalYesWeight: 0,
|
||||||
totalNoWeight: 0,
|
totalNoWeight: 0,
|
||||||
detailsHtml: `<p>Poll data is invalid or missing.</p>`
|
detailsHtml: `<p>Poll data is invalid or missing.</p>`,
|
||||||
|
userVote: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,6 +765,7 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
|
|||||||
let adminYes = 0, adminNo = 0
|
let adminYes = 0, adminNo = 0
|
||||||
let minterYes = 0, minterNo = 0
|
let minterYes = 0, minterNo = 0
|
||||||
let yesWeight = 0, noWeight = 0
|
let yesWeight = 0, noWeight = 0
|
||||||
|
let userVote = null
|
||||||
|
|
||||||
for (const w of pollData.voteWeights) {
|
for (const w of pollData.voteWeights) {
|
||||||
if (w.optionName.toLowerCase() === 'yes') {
|
if (w.optionName.toLowerCase() === 'yes') {
|
||||||
@ -609,6 +780,10 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
|
|||||||
const voterPublicKey = vote.voterPublicKey
|
const voterPublicKey = vote.voterPublicKey
|
||||||
const voterAddress = await getAddressFromPublicKey(voterPublicKey)
|
const voterAddress = await getAddressFromPublicKey(voterPublicKey)
|
||||||
|
|
||||||
|
if (voterAddress === userState.accountAddress) {
|
||||||
|
userVote = optionIndex
|
||||||
|
}
|
||||||
|
|
||||||
if (optionIndex === 0) {
|
if (optionIndex === 0) {
|
||||||
if (adminAddresses.includes(voterAddress)) {
|
if (adminAddresses.includes(voterAddress)) {
|
||||||
adminYes++
|
adminYes++
|
||||||
@ -703,7 +878,8 @@ const processPollData= async (pollData, minterGroupMembers, minterAdmins, creato
|
|||||||
totalNo,
|
totalNo,
|
||||||
totalYesWeight: totalMinterAndAdminYesWeight,
|
totalYesWeight: totalMinterAndAdminYesWeight,
|
||||||
totalNoWeight: totalMinterAndAdminNoWeight,
|
totalNoWeight: totalMinterAndAdminNoWeight,
|
||||||
detailsHtml
|
detailsHtml,
|
||||||
|
userVote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1593,9 +1769,28 @@ const getMinterAvatar = async (minterName) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNewestCommentTimestamp = async (cardIdentifier) => {
|
||||||
|
try {
|
||||||
|
// fetchCommentsForCard returns resources each with at least 'created' or 'updated'
|
||||||
|
const comments = await fetchCommentsForCard(cardIdentifier)
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
// No comments => fallback to 0 (or card's own date, if you like)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// The newest can be determined by comparing 'updated' or 'created'
|
||||||
|
const newestTimestamp = comments.reduce((acc, c) => {
|
||||||
|
const cTime = c.updated || c.created || 0
|
||||||
|
return (cTime > acc) ? cTime : acc
|
||||||
|
}, 0)
|
||||||
|
return newestTimestamp
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get newest comment timestamp:', err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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, address) => {
|
||||||
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 avatarHtml = await getMinterAvatar(creator)
|
const avatarHtml = await getMinterAvatar(creator)
|
||||||
@ -1607,7 +1802,7 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
|
|
||||||
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, detailsHtml } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator, cardIdentifier)
|
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0, detailsHtml, userVote } = await processPollData(pollResults, minterGroupMembers, minterAdmins, creator, cardIdentifier)
|
||||||
createModal('links')
|
createModal('links')
|
||||||
createModal('poll-details')
|
createModal('poll-details')
|
||||||
|
|
||||||
@ -1616,12 +1811,18 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
|
|
||||||
let finalBgColor = bgColor
|
let finalBgColor = bgColor
|
||||||
let invitedText = "" // for "INVITED" label if found
|
let invitedText = "" // for "INVITED" label if found
|
||||||
|
const addressInfo = await getAddressInfo(address)
|
||||||
|
const penaltyText = addressInfo.blocksMintedPenalty == 0 ? '' : '<p>(has Blocks Penalty)<p>'
|
||||||
|
const adjustmentText = addressInfo.blocksMintedAdjustment == 0 ? '' : '<p>(has Blocks Adjustment)<p>'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const minterAddress = await fetchOwnerAddressFromName(creator)
|
const invites = await fetchGroupInvitesByAddress(address)
|
||||||
const invites = await fetchGroupInvitesByAddress(minterAddress)
|
|
||||||
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
||||||
if (hasMinterInvite) {
|
if (userVote === 0) {
|
||||||
|
finalBgColor = "rgba(0, 192, 0, 0.3)"; // or any green you want
|
||||||
|
} else if (userVote === 1) {
|
||||||
|
finalBgColor = "rgba(192, 0, 0, 0.3)"; // or any red you want
|
||||||
|
} else if (hasMinterInvite) {
|
||||||
// If so, override background color & add an "INVITED" label
|
// If so, override background color & add an "INVITED" label
|
||||||
finalBgColor = "black";
|
finalBgColor = "black";
|
||||||
invitedText = `<h4 style="color: gold; margin-bottom: 0.5em;">INVITED</h4>`
|
invitedText = `<h4 style="color: gold; margin-bottom: 0.5em;">INVITED</h4>`
|
||||||
@ -1651,9 +1852,9 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
<div class="minter-card" style="background-color: ${finalBgColor}">
|
<div class="minter-card" style="background-color: ${finalBgColor}">
|
||||||
<div class="minter-card-header">
|
<div class="minter-card-header">
|
||||||
${avatarHtml}
|
${avatarHtml}
|
||||||
<h3>${creator}</h3>
|
<h3>${creator} - Level ${addressInfo.level}</h3>
|
||||||
<p>${header}</p>
|
<p>${header}</p>
|
||||||
${invitedText}
|
${penaltyText}${adjustmentText}${invitedText}
|
||||||
</div>
|
</div>
|
||||||
<div class="support-header"><h5>USER'S POST</h5></div>
|
<div class="support-header"><h5>USER'S POST</h5></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
|
@ -48,12 +48,12 @@ const timestampToHumanReadableDate = async(timestamp) => {
|
|||||||
const date = new Date(timestamp)
|
const date = new Date(timestamp)
|
||||||
const day = date.getDate()
|
const day = date.getDate()
|
||||||
const month = date.getMonth() + 1
|
const month = date.getMonth() + 1
|
||||||
const year = date.getFullYear() - 2000
|
const year = date.getFullYear()
|
||||||
const hours = date.getHours()
|
const hours = date.getHours()
|
||||||
const minutes = date.getMinutes()
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
const seconds = date.getSeconds()
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
const formattedDate = `${month}.${day}.${year}@${hours}:${minutes}:${seconds}`
|
const formattedDate = `${year}.${month}.${day}@${hours}:${minutes}:${seconds}`
|
||||||
console.log('Formatted date:', formattedDate)
|
console.log('Formatted date:', formattedDate)
|
||||||
return formattedDate
|
return formattedDate
|
||||||
}
|
}
|
||||||
@ -1226,7 +1226,7 @@ const renderData = async (service, name, identifier) => {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error rendering data:', error)
|
console.error('Error rendering data:', error)
|
||||||
// Return the custom message when there’s an error or invalid data
|
// Return the custom message when there's an error or invalid data
|
||||||
return 'Requested data is either missing or still being obtained from QDN... please try again in a short time.'
|
return 'Requested data is either missing or still being obtained from QDN... please try again in a short time.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1347,7 +1347,7 @@ const processTransaction = async (signedTransaction) => {
|
|||||||
const rawText = await response.text();
|
const rawText = await response.text();
|
||||||
console.log("Raw text from server (version 2 means JSON string in text):", rawText)
|
console.log("Raw text from server (version 2 means JSON string in text):", rawText)
|
||||||
|
|
||||||
// Attempt to parse if it’s indeed JSON
|
// Attempt to parse if it's indeed JSON
|
||||||
let parsed;
|
let parsed;
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(rawText);
|
parsed = JSON.parse(rawText);
|
||||||
|
Loading…
Reference in New Issue
Block a user