Compare commits
10 Commits
057b41af1d
...
1aa4985375
Author | SHA1 | Date | |
---|---|---|---|
|
1aa4985375 | ||
|
7cfd0357b5 | ||
|
41e1369d86 | ||
|
9f645f5582 | ||
|
3a083f99f6 | ||
|
1b5e8c38e1 | ||
|
e79e0bf4b1 | ||
|
02868171e3 | ||
|
9971c6d595 | ||
|
0df227d63d |
@ -178,6 +178,21 @@
|
|||||||
background-color: #3d1919;
|
background-color: #3d1919;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-button {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 1vh;
|
||||||
|
background-color: #897016;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 1vh;
|
||||||
|
padding: 0.3vh 0.6vh;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-button:hover {
|
||||||
|
background-color: #402d09;
|
||||||
|
}
|
||||||
|
|
||||||
/* forum-styles.css additions */
|
/* forum-styles.css additions */
|
||||||
|
|
||||||
.message-input-section {
|
.message-input-section {
|
||||||
|
@ -124,26 +124,18 @@ const loadAddRemoveAdminPage = async () => {
|
|||||||
linksContainer.appendChild(newLinkInput)
|
linksContainer.appendChild(newLinkInput)
|
||||||
})
|
})
|
||||||
|
|
||||||
const timeRangeSelectCheckbox = document.getElementById('time-range-select')
|
|
||||||
if (timeRangeSelectCheckbox) {
|
|
||||||
timeRangeSelectCheckbox.addEventListener('change', async (event) => {
|
|
||||||
await loadCards(addRemoveIdentifierPrefix)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
await publishARCard(addRemoveIdentifierPrefix)
|
await publishARCard(addRemoveIdentifierPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById("sort-select").addEventListener("change", async () => {
|
document.getElementById("sort-select").addEventListener("change", async () => {
|
||||||
// Re-load the cards whenever user chooses a new sort option.
|
// Only re-load the cards whenever user presses the refresh button.
|
||||||
await loadCards(addRemoveIdentifierPrefix)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await featureTriggerCheck()
|
await featureTriggerCheck()
|
||||||
await loadCards(addRemoveIdentifierPrefix)
|
|
||||||
await displayExistingMinterAdmins()
|
await displayExistingMinterAdmins()
|
||||||
|
// Only load the cards whenever user presses the refresh button.
|
||||||
await fetchAllARTxData()
|
await fetchAllARTxData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +240,25 @@ const displayExistingMinterAdmins = async () => {
|
|||||||
try {
|
try {
|
||||||
// 1) Fetch addresses
|
// 1) Fetch addresses
|
||||||
const admins = await fetchMinterGroupAdmins()
|
const admins = await fetchMinterGroupAdmins()
|
||||||
|
// Get names for each admin first
|
||||||
|
for (const admin of admins) {
|
||||||
|
try {
|
||||||
|
admin.name = await getNameFromAddress(admin.member)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Error fetching name for ${admin.member}:`, err)
|
||||||
|
admin.name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort admin list by name with NULL ACCOUNT at the top
|
||||||
|
admins.sort((a, b) => {
|
||||||
|
if (a.member === nullAddress && b.member !== nullAddress) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.member === nullAddress && a.member !== nullAddress) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return (a.name || '').localeCompare(b.name || '');
|
||||||
|
})
|
||||||
minterAdminAddresses = admins.map(m => m.member)
|
minterAdminAddresses = admins.map(m => m.member)
|
||||||
// Compute total admin count and signatures needed (40%, rounded up)
|
// Compute total admin count and signatures needed (40%, rounded up)
|
||||||
const totalAdmins = admins.length;
|
const totalAdmins = admins.length;
|
||||||
@ -271,16 +282,8 @@ const displayExistingMinterAdmins = async () => {
|
|||||||
</tr>
|
</tr>
|
||||||
`
|
`
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
// Attempt to get name
|
|
||||||
let adminName
|
|
||||||
try {
|
|
||||||
adminName = await getNameFromAddress(adminAddr.member)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(`Error fetching name for ${adminAddr.member}:`, err)
|
|
||||||
adminName = null
|
|
||||||
}
|
}
|
||||||
const displayName = adminName && adminName !== adminAddr.member ? adminName : "(No Name)"
|
const displayName = adminAddr.name && adminAddr.name !== adminAddr.member ? adminAddr.name : "(No Name)"
|
||||||
rowsHtml += `
|
rowsHtml += `
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border: 1px solid rgb(150, 199, 224); font-size: 1.5rem; padding: 4px; color:rgb(70, 156, 196)">${displayName}</td>
|
<td style="border: 1px solid rgb(150, 199, 224); font-size: 1.5rem; padding: 4px; color:rgb(70, 156, 196)">${displayName}</td>
|
||||||
@ -288,7 +291,7 @@ const displayExistingMinterAdmins = async () => {
|
|||||||
<td style="border: 1px solid rgb(231, 112, 112); padding: 4px;">
|
<td style="border: 1px solid rgb(231, 112, 112); padding: 4px;">
|
||||||
<button
|
<button
|
||||||
style="padding: 5px; background: red; color: white; border-radius: 3px; cursor: pointer;"
|
style="padding: 5px; background: red; color: white; border-radius: 3px; cursor: pointer;"
|
||||||
onclick="handleProposeDemotionWrapper('${adminName}', '${adminAddr.member}')"
|
onclick="handleProposeDemotionWrapper('${adminAddr.name}', '${adminAddr.member}')"
|
||||||
>
|
>
|
||||||
Propose Demotion
|
Propose Demotion
|
||||||
</button>
|
</button>
|
||||||
|
@ -380,123 +380,11 @@ const fetchAllEncryptedCards = async (isRefresh = false) => {
|
|||||||
selectedSort = sortSelect.value
|
selectedSort = sortSelect.value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSort === 'name') {
|
const sortedFinalCards = await sortCards(finalCards, selectedSort, "admin")
|
||||||
// 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 = ""
|
||||||
|
|
||||||
const finalVisualFilterCards = finalCards.filter(({card}) => {
|
const finalVisualFilterCards = sortedFinalCards.filter(({card}) => {
|
||||||
const showKickedBanned = document.getElementById('admin-show-kicked-banned-checkbox')?.checked ?? false
|
const showKickedBanned = document.getElementById('admin-show-kicked-banned-checkbox')?.checked ?? false
|
||||||
const showHiddenAdminCards = document.getElementById('admin-show-hidden-checkbox')?.checked ?? false
|
const showHiddenAdminCards = document.getElementById('admin-show-hidden-checkbox')?.checked ?? false
|
||||||
|
|
||||||
@ -1196,23 +1084,6 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteAdminCard = async (cardIdentifier) => {
|
const deleteAdminCard = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.")
|
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.")
|
||||||
|
@ -32,7 +32,7 @@ const loadMinterAdminToolsPage = async () => {
|
|||||||
|
|
||||||
<div id="tools-submenu" class="tools-submenu">
|
<div id="tools-submenu" class="tools-submenu">
|
||||||
<div class="tools-buttons" style="display: flex; gap: 1em; justify-content: center;">
|
<div class="tools-buttons" style="display: flex; gap: 1em; justify-content: center;">
|
||||||
<button id="toggle-blocklist-button" class="publish-card-button">Add/Remove blockedUsers</button>
|
<button id="toggle-blocklist-button" class="publish-card-button">Show/Hide blockedUsers</button>
|
||||||
<button id="create-group-invite" class="publish-card-button">Create Pending Group Invite</button>
|
<button id="create-group-invite" class="publish-card-button">Create Pending Group Invite</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -140,27 +140,17 @@ const loadMinterBoardPage = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById("time-range-select").addEventListener("change", async () => {
|
document.getElementById("time-range-select").addEventListener("change", async () => {
|
||||||
// Re-load the cards whenever user chooses a new sort option.
|
// Only re-load the cards whenever user presses the refresh button.
|
||||||
await loadCards(minterCardIdentifierPrefix)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById("sort-select").addEventListener("change", async () => {
|
document.getElementById("sort-select").addEventListener("change", async () => {
|
||||||
// Re-load the cards whenever user chooses a new sort option.
|
// Only re-load the cards whenever user presses the refresh button.
|
||||||
await loadCards(minterCardIdentifierPrefix)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const showExistingCardsCheckbox = document.getElementById('show-existing-checkbox')
|
|
||||||
if (showExistingCardsCheckbox) {
|
|
||||||
showExistingCardsCheckbox.addEventListener('change', async (event) => {
|
|
||||||
await loadCards(minterCardIdentifierPrefix)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await featureTriggerCheck()
|
await featureTriggerCheck()
|
||||||
await loadCards(minterCardIdentifierPrefix)
|
// Only load the cards whenever user presses the refresh button.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
||||||
// Ensure the identifier starts with the prefix
|
// Ensure the identifier starts with the prefix
|
||||||
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
||||||
@ -439,31 +429,15 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
selectedSort = sortSelect.value
|
selectedSort = sortSelect.value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSort === 'name') {
|
const sortedFinalCards = isARBoard
|
||||||
finalCards.sort((a, b) => {
|
? await sortCards(finalCards, selectedSort, "ar")
|
||||||
const nameA = a.name?.toLowerCase() || ''
|
: await sortCards(finalCards, selectedSort, "minter")
|
||||||
const nameB = b.name?.toLowerCase() || ''
|
|
||||||
return nameA.localeCompare(nameB)
|
|
||||||
})
|
|
||||||
} else if (selectedSort === 'recent-comments') {
|
|
||||||
// If you need the newest comment timestamp
|
|
||||||
for (let card of finalCards) {
|
|
||||||
card.newestCommentTimestamp = await getNewestCommentTimestamp(card.identifier)
|
|
||||||
}
|
|
||||||
finalCards.sort((a, b) =>
|
|
||||||
(b.newestCommentTimestamp || 0) - (a.newestCommentTimestamp || 0)
|
|
||||||
)
|
|
||||||
} else if (selectedSort === 'least-votes') {
|
|
||||||
await applyVoteSortingData(finalCards, /* ascending= */ true)
|
|
||||||
} else if (selectedSort === 'most-votes') {
|
|
||||||
await applyVoteSortingData(finalCards, /* ascending= */ false)
|
|
||||||
}
|
|
||||||
// else 'newest' => do nothing (already sorted newest-first by your process functions).
|
|
||||||
// Create the 'finalCardsArray' that includes the data, etc.
|
// Create the 'finalCardsArray' that includes the data, etc.
|
||||||
let finalCardsArray = []
|
let finalCardsArray = []
|
||||||
let alreadyMinterCards = []
|
let alreadyMinterCards = []
|
||||||
cardsContainer.innerHTML = ''
|
cardsContainer.innerHTML = ''
|
||||||
for (const card of finalCards) {
|
for (const card of sortedFinalCards) {
|
||||||
try {
|
try {
|
||||||
const skeletonHTML = createSkeletonCardHTML(card.identifier)
|
const skeletonHTML = createSkeletonCardHTML(card.identifier)
|
||||||
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||||
@ -624,71 +598,6 @@ const verifyMinter = async (minterName) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyVoteSortingData = async (cards, ascending = true) => {
|
|
||||||
const minterGroupMembers = await fetchMinterGroupMembers()
|
|
||||||
const minterAdmins = await fetchMinterGroupAdmins()
|
|
||||||
|
|
||||||
for (const card of cards) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ascending) {
|
|
||||||
// least votes first
|
|
||||||
cards.sort((a, b) => {
|
|
||||||
const diffAdminTotal = a._adminVotes - b._adminVotes
|
|
||||||
if (diffAdminTotal !== 0) return diffAdminTotal
|
|
||||||
const diffAdminYes = a._adminYes - b._adminYes
|
|
||||||
if (diffAdminYes !== 0) return diffAdminYes
|
|
||||||
const diffMinterTotal = a._minterVotes - b._minterVotes
|
|
||||||
if (diffMinterTotal !== 0) return diffMinterTotal
|
|
||||||
return a._minterYes - b._minterYes
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// most votes first
|
|
||||||
cards.sort((a, b) => {
|
|
||||||
const diffAdminTotal = b._adminVotes - a._adminVotes
|
|
||||||
if (diffAdminTotal !== 0) return diffAdminTotal
|
|
||||||
const diffAdminYes = b._adminYes - a._adminYes
|
|
||||||
if (diffAdminYes !== 0) return diffAdminYes
|
|
||||||
const diffMinterTotal = b._minterVotes - a._minterVotes
|
|
||||||
if (diffMinterTotal !== 0) return diffMinterTotal
|
|
||||||
return b._minterYes - a._minterYes
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSkeleton = (cardIdentifier) => {
|
const removeSkeleton = (cardIdentifier) => {
|
||||||
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
|
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
|
||||||
if (skeletonCard) {
|
if (skeletonCard) {
|
||||||
@ -891,236 +800,6 @@ const publishCard = async (cardIdentifierPrefix) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalVoterMap = new Map()
|
|
||||||
|
|
||||||
const processPollData= async (pollData, minterGroupMembers, minterAdmins, creator, cardIdentifier) => {
|
|
||||||
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,
|
|
||||||
detailsHtml: `<p>Poll data is invalid or missing.</p>`,
|
|
||||||
userVote: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberAddresses = minterGroupMembers.map(m => m.member)
|
|
||||||
const minterAdminAddresses = minterAdmins.map(m => m.member)
|
|
||||||
const adminGroupsMembers = await fetchAllAdminGroupsMembers()
|
|
||||||
const featureTriggerPassed = await featureTriggerCheck()
|
|
||||||
const groupAdminAddresses = adminGroupsMembers.map(m => m.member)
|
|
||||||
let adminAddresses = [...minterAdminAddresses]
|
|
||||||
|
|
||||||
if (!featureTriggerPassed) {
|
|
||||||
console.log(`featureTrigger is NOT passed, only showing admin results from Minter Admins and Group Admins`)
|
|
||||||
adminAddresses = [...minterAdminAddresses, ...groupAdminAddresses]
|
|
||||||
}
|
|
||||||
|
|
||||||
let adminYes = 0, adminNo = 0
|
|
||||||
let minterYes = 0, minterNo = 0
|
|
||||||
let yesWeight = 0, noWeight = 0
|
|
||||||
let userVote = null
|
|
||||||
|
|
||||||
for (const w of pollData.voteWeights) {
|
|
||||||
if (w.optionName.toLowerCase() === 'yes') {
|
|
||||||
yesWeight = w.voteWeight
|
|
||||||
} else if (w.optionName.toLowerCase() === 'no') {
|
|
||||||
noWeight = w.voteWeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const voterPromises = pollData.votes.map(async (vote) => {
|
|
||||||
const optionIndex = vote.optionIndex; // 0 => yes, 1 => no
|
|
||||||
const voterPublicKey = vote.voterPublicKey
|
|
||||||
const voterAddress = await getAddressFromPublicKey(voterPublicKey)
|
|
||||||
|
|
||||||
if (voterAddress === userState.accountAddress) {
|
|
||||||
userVote = optionIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const allVoters = await Promise.all(voterPromises)
|
|
||||||
const yesVoters = []
|
|
||||||
const noVoters = []
|
|
||||||
let totalMinterAndAdminYesWeight = 0
|
|
||||||
let totalMinterAndAdminNoWeight = 0
|
|
||||||
|
|
||||||
for (const v of allVoters) {
|
|
||||||
if (v.optionIndex === 0) {
|
|
||||||
yesVoters.push(v)
|
|
||||||
totalMinterAndAdminYesWeight+=v.blocksMinted
|
|
||||||
} else if (v.optionIndex === 1) {
|
|
||||||
noVoters.push(v)
|
|
||||||
totalMinterAndAdminNoWeight+=v.blocksMinted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yesVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
|
||||||
noVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
|
||||||
const sortedAllVoters = allVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
|
||||||
await createVoterMap(sortedAllVoters, cardIdentifier)
|
|
||||||
|
|
||||||
const yesTableHtml = buildVotersTableHtml(yesVoters, /* tableColor= */ "green")
|
|
||||||
const noTableHtml = buildVotersTableHtml(noVoters, /* tableColor= */ "red")
|
|
||||||
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 totalNo = adminNo + minterNo
|
|
||||||
|
|
||||||
return {
|
|
||||||
adminYes,
|
|
||||||
adminNo,
|
|
||||||
minterYes,
|
|
||||||
minterNo,
|
|
||||||
totalYes,
|
|
||||||
totalNo,
|
|
||||||
totalYesWeight: totalMinterAndAdminYesWeight,
|
|
||||||
totalNoWeight: totalMinterAndAdminNoWeight,
|
|
||||||
detailsHtml,
|
|
||||||
userVote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createVoterMap = async (voters, cardIdentifier) => {
|
|
||||||
const voterMap = new Map()
|
|
||||||
voters.forEach((voter) => {
|
|
||||||
const voterNameOrAddress = voter.voterName || voter.voterAddress
|
|
||||||
voterMap.set(voterNameOrAddress, {
|
|
||||||
vote: voter.optionIndex === 0 ? "yes" : "no", // Use optionIndex directly
|
|
||||||
voterType: voter.isAdmin ? "Admin" : voter.isMinter ? "Minter" : "User",
|
|
||||||
blocksMinted: voter.blocksMinted,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
globalVoterMap.set(cardIdentifier, voterMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
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}`)
|
||||||
@ -1492,20 +1171,6 @@ const createInviteButtonHtml = (creator, cardIdentifier) => {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureTriggerCheck = async () => {
|
|
||||||
const latestBlockInfo = await getLatestBlockInfo()
|
|
||||||
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
|
||||||
if (isBlockPassed) {
|
|
||||||
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has PASSED:`, isBlockPassed)
|
|
||||||
featureTriggerPassed = true
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has NOT PASSED:`, isBlockPassed)
|
|
||||||
featureTriggerPassed = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||||
const isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
const isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
||||||
const isBlockPassed = await featureTriggerCheck()
|
const isBlockPassed = await featureTriggerCheck()
|
||||||
@ -1963,26 +1628,6 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteCard = async (cardIdentifier) => {
|
const deleteCard = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.")
|
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.")
|
||||||
|
@ -78,28 +78,28 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
mintershipForumLinks.forEach(link => {
|
mintershipForumLinks.forEach(link => {
|
||||||
link.addEventListener('click', async (event) => {
|
link.addEventListener('click', async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!userState.isLoggedIn) {
|
await loadForumPage()
|
||||||
await login()
|
|
||||||
}
|
|
||||||
await loadForumPage();
|
|
||||||
loadRoomContent("general")
|
loadRoomContent("general")
|
||||||
startPollingForNewMessages()
|
startPollingForNewMessages()
|
||||||
createScrollToTopButton()
|
createScrollToTopButton()
|
||||||
|
if (!userState.isLoggedIn) {
|
||||||
|
await login()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]')
|
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]')
|
||||||
minterBoardLinks.forEach(link => {
|
minterBoardLinks.forEach(link => {
|
||||||
link.addEventListener("click", async (event) => {
|
link.addEventListener("click", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
if (!userState.isLoggedIn) {
|
|
||||||
await login()
|
|
||||||
}
|
|
||||||
if (typeof loadMinterBoardPage === "undefined") {
|
if (typeof loadMinterBoardPage === "undefined") {
|
||||||
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
||||||
await loadScript("./assets/js/MinterBoard.js")
|
await loadScript("./assets/js/MinterBoard.js")
|
||||||
}
|
}
|
||||||
await loadMinterBoardPage()
|
await loadMinterBoardPage()
|
||||||
|
if (!userState.isLoggedIn) {
|
||||||
|
await login()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -107,15 +107,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
addRemoveAdminLinks.forEach(link => {
|
addRemoveAdminLinks.forEach(link => {
|
||||||
link.addEventListener('click', async (event) => {
|
link.addEventListener('click', async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
// Possibly require user to login if not logged
|
|
||||||
if (!userState.isLoggedIn) {
|
|
||||||
await login()
|
|
||||||
}
|
|
||||||
if (typeof loadMinterBoardPage === "undefined") {
|
if (typeof loadMinterBoardPage === "undefined") {
|
||||||
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
||||||
await loadScript("./assets/js/MinterBoard.js")
|
await loadScript("./assets/js/MinterBoard.js")
|
||||||
}
|
}
|
||||||
await loadAddRemoveAdminPage()
|
await loadAddRemoveAdminPage()
|
||||||
|
if (!userState.isLoggedIn) {
|
||||||
|
await login()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -240,7 +239,10 @@ const loadForumPage = async () => {
|
|||||||
<div class="forum-main mbr-parallax-background cid-ttRnlSkg2R">
|
<div class="forum-main mbr-parallax-background cid-ttRnlSkg2R">
|
||||||
<div class="forum-header" style="color: lightblue; display: flex; justify-content: center; align-items: center; padding: 10px;">
|
<div class="forum-header" style="color: lightblue; display: flex; justify-content: center; align-items: center; padding: 10px;">
|
||||||
<div class="user-info" style="border: 1px solid lightblue; padding: 5px; color: white; display: flex; align-items: center; justify-content: center;">
|
<div class="user-info" style="border: 1px solid lightblue; padding: 5px; color: white; display: flex; align-items: center; justify-content: center;">
|
||||||
<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;">
|
${userState.isLoggedIn ? `
|
||||||
|
<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;">
|
||||||
|
` : ''
|
||||||
|
}
|
||||||
<span>${userState.accountName || 'Guest'}</span>
|
<span>${userState.accountName || 'Guest'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -468,6 +470,7 @@ let selectedImages = []
|
|||||||
let selectedFiles = []
|
let selectedFiles = []
|
||||||
let multiResource = []
|
let multiResource = []
|
||||||
let attachmentIdentifiers = []
|
let attachmentIdentifiers = []
|
||||||
|
let editMessageIdentifier = null
|
||||||
|
|
||||||
// Set up file input handling
|
// Set up file input handling
|
||||||
const setupFileInputs = (room) => {
|
const setupFileInputs = (room) => {
|
||||||
@ -558,9 +561,14 @@ const processSelectedImages = async (selectedImages, multiResource, room) => {
|
|||||||
|
|
||||||
// Handle send message
|
// Handle send message
|
||||||
const handleSendMessage = async (room, messageHtml, selectedFiles, selectedImages, multiResource) => {
|
const handleSendMessage = async (room, messageHtml, selectedFiles, selectedImages, multiResource) => {
|
||||||
const messageIdentifier = room === "admins"
|
let messageIdentifier
|
||||||
? `${messageIdentifierPrefix}-${room}-e-${randomID()}`
|
if (editMessageIdentifier) {
|
||||||
: `${messageIdentifierPrefix}-${room}-${randomID()}`
|
messageIdentifier = editMessageIdentifier
|
||||||
|
} else {
|
||||||
|
messageIdentifier = room === "admins"
|
||||||
|
? `${messageIdentifierPrefix}-${room}-e-${randomID()}`
|
||||||
|
: `${messageIdentifierPrefix}-${room}-${randomID()}`
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Process selected images
|
// Process selected images
|
||||||
@ -672,6 +680,34 @@ const handleDeleteMessage = async (room, existingMessageIdentifier) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleEditMessage = async (room, existingMessageIdentifier) => {
|
||||||
|
try {
|
||||||
|
const editedMessageObject = {
|
||||||
|
messageHtml: "", // TODO: Add Quill editor content here
|
||||||
|
hasAttachment: false,
|
||||||
|
attachments: [],
|
||||||
|
replyTo: null
|
||||||
|
}
|
||||||
|
const base64Message = btoa(JSON.stringify(editedMessageObject))
|
||||||
|
const service = (room === "admins") ? "MAIL_PRIVATE" : "BLOG_POST"
|
||||||
|
const request = {
|
||||||
|
action: 'PUBLISH_QDN_RESOURCE',
|
||||||
|
name: userState.accountName,
|
||||||
|
service: service,
|
||||||
|
identifier: existingMessageIdentifier,
|
||||||
|
data64: base64Message
|
||||||
|
}
|
||||||
|
if (room === "admins") {
|
||||||
|
request.encrypt = true
|
||||||
|
request.publicKeys = adminPublicKeys
|
||||||
|
}
|
||||||
|
console.log("Editing forum message...")
|
||||||
|
await qortalRequest(request)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error editing message:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clearInputs() {
|
function clearInputs() {
|
||||||
// Clear the file input elements and preview container
|
// Clear the file input elements and preview container
|
||||||
document.getElementById('file-input').value = ''
|
document.getElementById('file-input').value = ''
|
||||||
@ -689,6 +725,7 @@ function clearInputs() {
|
|||||||
attachmentIdentifiers = []
|
attachmentIdentifiers = []
|
||||||
selectedImages = []
|
selectedImages = []
|
||||||
selectedFiles = []
|
selectedFiles = []
|
||||||
|
editMessageIdentifier = null
|
||||||
|
|
||||||
// Remove the reply container
|
// Remove the reply container
|
||||||
const replyContainer = document.querySelector('.reply-container')
|
const replyContainer = document.querySelector('.reply-container')
|
||||||
@ -788,6 +825,7 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
|
|||||||
|
|
||||||
handleReplyLogic(fetchMessages)
|
handleReplyLogic(fetchMessages)
|
||||||
handleDeleteLogic(fetchMessages, room)
|
handleDeleteLogic(fetchMessages, room)
|
||||||
|
handleEditLogic(fetchMessages, room)
|
||||||
|
|
||||||
await updatePaginationControls(room, limit)
|
await updatePaginationControls(room, limit)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1008,6 +1046,7 @@ const buildMessageHTML = async (message, fetchMessages, room, isNewMessage) => {
|
|||||||
const attachmentHtml = await buildAttachmentHtml(message, room)
|
const attachmentHtml = await buildAttachmentHtml(message, room)
|
||||||
const avatarUrl = `/arbitrary/THUMBNAIL/${message.name}/qortal_avatar`
|
const avatarUrl = `/arbitrary/THUMBNAIL/${message.name}/qortal_avatar`
|
||||||
let deleteButtonHtml = ''
|
let deleteButtonHtml = ''
|
||||||
|
let editButtonHtml = ''
|
||||||
if (message.name === userState.accountName) {
|
if (message.name === userState.accountName) {
|
||||||
deleteButtonHtml = `
|
deleteButtonHtml = `
|
||||||
<button class="delete-button"
|
<button class="delete-button"
|
||||||
@ -1016,6 +1055,13 @@ const buildMessageHTML = async (message, fetchMessages, room, isNewMessage) => {
|
|||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
|
editButtonHtml = `
|
||||||
|
<button class="edit-button"
|
||||||
|
data-message-identifier="${message.identifier}"
|
||||||
|
data-room="${room}">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@ -1034,7 +1080,7 @@ const buildMessageHTML = async (message, fetchMessages, room, isNewMessage) => {
|
|||||||
${attachmentHtml}
|
${attachmentHtml}
|
||||||
</div>
|
</div>
|
||||||
<div class="message-actions">
|
<div class="message-actions">
|
||||||
${deleteButtonHtml}
|
${deleteButtonHtml}${editButtonHtml}
|
||||||
<button class="reply-button" data-message-identifier="${message.identifier}">Reply</button>
|
<button class="reply-button" data-message-identifier="${message.identifier}">Reply</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1206,6 +1252,23 @@ const handleDeleteLogic = (fetchMessages, room) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleEditLogic = (fetchMessages, room) => {
|
||||||
|
// Only select buttons that do NOT already have a listener
|
||||||
|
const editButtons = document.querySelectorAll('.edit-button:not(.bound-edit)')
|
||||||
|
editButtons.forEach(button => {
|
||||||
|
button.classList.add('bound-edit')
|
||||||
|
button.addEventListener('click', async () => {
|
||||||
|
const existingMessageIdentifier = button.dataset.messageIdentifier
|
||||||
|
const originalMessage = messagesById[existingMessageIdentifier]
|
||||||
|
if (!originalMessage) return
|
||||||
|
editMessageIdentifier = existingMessageIdentifier
|
||||||
|
const quill = new Quill('#editor')
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(originalMessage.content)
|
||||||
|
// Optionally show a small notice: "Editing message..."
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const showReplyPreview = (repliedMessage) => {
|
const showReplyPreview = (repliedMessage) => {
|
||||||
replyToMessageIdentifier = repliedMessage.identifier
|
replyToMessageIdentifier = repliedMessage.identifier
|
||||||
|
|
||||||
|
@ -205,4 +205,417 @@ const fetchAllInviteTransactions = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sortCards = async (cardsArray, selectedSort, board) => {
|
||||||
|
// Default sort is by newest if none provided
|
||||||
|
if (!selectedSort) selectedSort = 'newest'
|
||||||
|
switch (selectedSort) {
|
||||||
|
case 'name':
|
||||||
|
// Sort by name
|
||||||
|
cardsArray.sort((a, b) => {
|
||||||
|
const nameA = (board === "admin")
|
||||||
|
? (a.decryptedCardData?.minterName || '').toLowerCase()
|
||||||
|
: ((board === "ar")
|
||||||
|
? (a.minterName?.toLowerCase() || '')
|
||||||
|
: (a.name?.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
const nameB = (board === "admin")
|
||||||
|
? (b.decryptedCardData?.minterName || '').toLowerCase()
|
||||||
|
: ((board === "ar")
|
||||||
|
? (b.minterName?.toLowerCase() || '')
|
||||||
|
: (b.name?.toLowerCase() || '')
|
||||||
|
)
|
||||||
|
return nameA.localeCompare(nameB)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'recent-comments':
|
||||||
|
// Sort by newest comment timestamp
|
||||||
|
for (let card of cardsArray) {
|
||||||
|
const cardIdentifier = (board === "admin")
|
||||||
|
? card.card.identifier
|
||||||
|
: card.identifier
|
||||||
|
card.newestCommentTimestamp = await getNewestCommentTimestamp(cardIdentifier, board)
|
||||||
|
}
|
||||||
|
cardsArray.sort((a, b) => {
|
||||||
|
return (b.newestCommentTimestamp || 0) - (a.newestCommentTimestamp || 0)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'least-votes':
|
||||||
|
await applyVoteSortingData(cardsArray, /* ascending= */ true, board)
|
||||||
|
break
|
||||||
|
case 'most-votes':
|
||||||
|
await applyVoteSortingData(cardsArray, /* ascending= */ false, board)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// Sort by date
|
||||||
|
cardsArray.sort((a, b) => {
|
||||||
|
const timestampA = (board === "admin")
|
||||||
|
? a.card.updated || a.card.created || 0
|
||||||
|
: a.updated || a.created || 0
|
||||||
|
const timestampB = (board === "admin")
|
||||||
|
? b.card.updated || b.card.created || 0
|
||||||
|
: b.updated || b.created || 0
|
||||||
|
return timestampB - timestampA;
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return cardsArray
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNewestCommentTimestamp = async (cardIdentifier, board) => {
|
||||||
|
try {
|
||||||
|
const comments = (board === "admin") ? await fetchEncryptedComments(cardIdentifier) : await fetchCommentsForCard(cardIdentifier)
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyVoteSortingData = async (cards, ascending = true, boardType = 'minter') => {
|
||||||
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
for (const card of cards) {
|
||||||
|
try {
|
||||||
|
if (boardType === 'admin') {
|
||||||
|
// For the Admin board, we already have the poll name in `card.decryptedCardData.poll`
|
||||||
|
// No need to fetch the resource from BLOG_POST
|
||||||
|
const pollName = card.decryptedCardData?.poll
|
||||||
|
if (!pollName) {
|
||||||
|
// No poll => no votes
|
||||||
|
card._adminYes = 0
|
||||||
|
card._adminNo = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
card._minterNo = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Fetch poll results
|
||||||
|
const pollResults = await fetchPollResults(pollName)
|
||||||
|
if (!pollResults) {
|
||||||
|
card._adminYes = 0
|
||||||
|
card._adminNo = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
card._minterNo = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Process them
|
||||||
|
const { adminYes, adminNo, minterYes, minterNo } = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
card.decryptedCardData.creator,
|
||||||
|
card.card.identifier
|
||||||
|
)
|
||||||
|
card._adminYes = adminYes
|
||||||
|
card._adminNo = adminNo
|
||||||
|
card._minterYes = minterYes
|
||||||
|
card._minterNo = minterNo
|
||||||
|
} else {
|
||||||
|
const cardDataResponse = await qortalRequest({
|
||||||
|
action: "FETCH_QDN_RESOURCE",
|
||||||
|
name: card.name,
|
||||||
|
service: "BLOG_POST",
|
||||||
|
identifier: card.identifier,
|
||||||
|
})
|
||||||
|
if (!cardDataResponse || !cardDataResponse.poll) {
|
||||||
|
card._adminYes = 0
|
||||||
|
card._adminNo = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
card._minterNo = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const pollResults = await fetchPollResults(cardDataResponse.poll);
|
||||||
|
const { adminYes, adminNo, minterYes, minterNo } = await processPollData(
|
||||||
|
pollResults,
|
||||||
|
minterGroupMembers,
|
||||||
|
minterAdmins,
|
||||||
|
cardDataResponse.creator,
|
||||||
|
card.identifier
|
||||||
|
)
|
||||||
|
card._adminYes = adminYes
|
||||||
|
card._adminNo = adminNo
|
||||||
|
card._minterYes = minterYes
|
||||||
|
card._minterNo = minterNo
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error fetching or processing poll for card ${card.identifier}:`, error)
|
||||||
|
card._adminYes = 0
|
||||||
|
card._adminNo = 0
|
||||||
|
card._minterYes = 0
|
||||||
|
card._minterNo = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ascending) {
|
||||||
|
// least votes first
|
||||||
|
cards.sort((a, b) => {
|
||||||
|
const diffAdminYes = a._adminYes - b._adminYes
|
||||||
|
if (diffAdminYes !== 0) return diffAdminYes
|
||||||
|
const diffAdminNo = b._adminNo - a._adminNo
|
||||||
|
if (diffAdminNo !== 0) return diffAdminNo
|
||||||
|
const diffMinterYes = a._minterYes - b._minterYes
|
||||||
|
if (diffMinterYes !== 0) return diffMinterYes
|
||||||
|
return b._minterNo - a._minterNo
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// most votes first
|
||||||
|
cards.sort((a, b) => {
|
||||||
|
const diffAdminYes = b._adminYes - a._adminYes
|
||||||
|
if (diffAdminYes !== 0) return diffAdminYes
|
||||||
|
const diffAdminNo = a._adminNo - b._adminNo
|
||||||
|
if (diffAdminNo !== 0) return diffAdminNo
|
||||||
|
const diffMinterYes = b._minterYes - a._minterYes
|
||||||
|
if (diffMinterYes !== 0) return diffMinterYes
|
||||||
|
return a._minterNo - b._minterNo
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let globalVoterMap = new Map()
|
||||||
|
|
||||||
|
const processPollData= async (pollData, minterGroupMembers, minterAdmins, creator, cardIdentifier) => {
|
||||||
|
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,
|
||||||
|
detailsHtml: `<p>Poll data is invalid or missing.</p>`,
|
||||||
|
userVote: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const memberAddresses = minterGroupMembers.map(m => m.member)
|
||||||
|
const minterAdminAddresses = minterAdmins.map(m => m.member)
|
||||||
|
const adminGroupsMembers = await fetchAllAdminGroupsMembers()
|
||||||
|
const featureTriggerPassed = await featureTriggerCheck()
|
||||||
|
const groupAdminAddresses = adminGroupsMembers.map(m => m.member)
|
||||||
|
let adminAddresses = [...minterAdminAddresses]
|
||||||
|
|
||||||
|
if (!featureTriggerPassed) {
|
||||||
|
console.log(`featureTrigger is NOT passed, only showing admin results from Minter Admins and Group Admins`)
|
||||||
|
adminAddresses = [...minterAdminAddresses, ...groupAdminAddresses]
|
||||||
|
}
|
||||||
|
|
||||||
|
let adminYes = 0, adminNo = 0
|
||||||
|
let minterYes = 0, minterNo = 0
|
||||||
|
let yesWeight = 0, noWeight = 0
|
||||||
|
let userVote = null
|
||||||
|
|
||||||
|
for (const w of pollData.voteWeights) {
|
||||||
|
if (w.optionName.toLowerCase() === 'yes') {
|
||||||
|
yesWeight = w.voteWeight
|
||||||
|
} else if (w.optionName.toLowerCase() === 'no') {
|
||||||
|
noWeight = w.voteWeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const voterPromises = pollData.votes.map(async (vote) => {
|
||||||
|
const optionIndex = vote.optionIndex; // 0 => yes, 1 => no
|
||||||
|
const voterPublicKey = vote.voterPublicKey
|
||||||
|
const voterAddress = await getAddressFromPublicKey(voterPublicKey)
|
||||||
|
|
||||||
|
if (voterAddress === userState.accountAddress) {
|
||||||
|
userVote = optionIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const allVoters = await Promise.all(voterPromises)
|
||||||
|
const yesVoters = []
|
||||||
|
const noVoters = []
|
||||||
|
let totalMinterAndAdminYesWeight = 0
|
||||||
|
let totalMinterAndAdminNoWeight = 0
|
||||||
|
|
||||||
|
for (const v of allVoters) {
|
||||||
|
if (v.optionIndex === 0) {
|
||||||
|
yesVoters.push(v)
|
||||||
|
totalMinterAndAdminYesWeight+=v.blocksMinted
|
||||||
|
} else if (v.optionIndex === 1) {
|
||||||
|
noVoters.push(v)
|
||||||
|
totalMinterAndAdminNoWeight+=v.blocksMinted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yesVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
||||||
|
noVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
||||||
|
const sortedAllVoters = allVoters.sort((a,b) => b.blocksMinted - a.blocksMinted)
|
||||||
|
await createVoterMap(sortedAllVoters, cardIdentifier)
|
||||||
|
|
||||||
|
const yesTableHtml = buildVotersTableHtml(yesVoters, /* tableColor= */ "green")
|
||||||
|
const noTableHtml = buildVotersTableHtml(noVoters, /* tableColor= */ "red")
|
||||||
|
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 totalNo = adminNo + minterNo
|
||||||
|
|
||||||
|
return {
|
||||||
|
adminYes,
|
||||||
|
adminNo,
|
||||||
|
minterYes,
|
||||||
|
minterNo,
|
||||||
|
totalYes,
|
||||||
|
totalNo,
|
||||||
|
totalYesWeight: totalMinterAndAdminYesWeight,
|
||||||
|
totalNoWeight: totalMinterAndAdminNoWeight,
|
||||||
|
detailsHtml,
|
||||||
|
userVote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createVoterMap = async (voters, cardIdentifier) => {
|
||||||
|
const voterMap = new Map()
|
||||||
|
voters.forEach((voter) => {
|
||||||
|
const voterNameOrAddress = voter.voterName || voter.voterAddress
|
||||||
|
voterMap.set(voterNameOrAddress, {
|
||||||
|
vote: voter.optionIndex === 0 ? "yes" : "no", // Use optionIndex directly
|
||||||
|
voterType: voter.isAdmin ? "Admin" : voter.isMinter ? "Minter" : "User",
|
||||||
|
blocksMinted: voter.blocksMinted,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
globalVoterMap.set(cardIdentifier, voterMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const featureTriggerCheck = async () => {
|
||||||
|
const latestBlockInfo = await getLatestBlockInfo()
|
||||||
|
const isBlockPassed = latestBlockInfo.height >= GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT
|
||||||
|
if (isBlockPassed) {
|
||||||
|
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has PASSED:`, isBlockPassed)
|
||||||
|
featureTriggerPassed = true
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
console.warn(`featureTrigger check (verifyFeatureTrigger) determined block has NOT PASSED:`, isBlockPassed)
|
||||||
|
featureTriggerPassed = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user