const isEncryptedTestMode = false
const encryptedCardIdentifierPrefix = "card-MAC"
const adminBoardPublishEditorKey = "admin-card-content"
let isUpdateCard = false
let existingDecryptedCardData = {}
let existingEncryptedCardIdentifier = {}
let cardMinterName = {}
let existingCardMinterNames = []
let isTopic = false
let attemptLoadAdminDataCount = 0
let adminMemberCount = 0
let adminPublicKeys = []
// Kakashi Note: Batch size keeps encrypted-board rendering progressive without flooding decrypt and poll calls.
const ADMIN_SCROLL_BATCH_SIZE = 10
const adminBoardInfiniteState = {
loadToken: 0,
cards: [],
cursor: 0,
inFlight: false,
complete: false,
displayedCount: 0,
totalCount: 0,
isBackgroundLoading: false,
container: null,
progressEl: null,
sharedBoardData: null,
scrollHandler: null,
backgroundRunnerToken: 0
}
const adminBoardSearchCache = {
resourcesByKey: new Map(),
maxDaysCovered: 0,
hasAllRange: false
}
const adminBoardDecryptedCardCache = new Map()
const adminBoardDecryptedCardByIdentifier = new Map()
const optimisticEncryptedCommentCache = new Map()
// let kickTransactions = []
// let banTransactions = []
let adminBoardState = {
kickedCards: new Set(), // store identifiers
bannedCards: new Set(), // likewise
hiddenList: new Set(), // user-hidden
// ... we can add other things to state if needed...
}
const loadAdminBoardState = () => {
// Load from localStorage if available
const rawState = localStorage.getItem('adminBoardState')
if (rawState) {
try {
const parsed = JSON.parse(rawState);
// Make sure bannedCards and kickedCards are sets
return {
bannedCards: new Set(parsed.bannedCards ?? []),
kickedCards: new Set(parsed.kickedCards ?? []),
hiddenList: new Set(parsed.hiddenList ?? []),
// ... any other fields
};
} catch (e) {
console.warn("Failed to parse adminBoardState from storage:", e)
}
}
// If nothing found or parse error, return a default
return {
bannedCards: new Set(),
kickedCards: new Set(),
hiddenList: new Set(),
}
}
// Saving the state back into localStorage as needed:
const saveAdminBoardState = () => {
const stateToSave = {
bannedCards: Array.from(adminBoardState.bannedCards),
kickedCards: Array.from(adminBoardState.kickedCards),
hiddenList: Array.from(adminBoardState.hiddenList),
}
localStorage.setItem('adminBoardState', JSON.stringify(stateToSave))
}
console.log("Attempting to load AdminBoard.js")
const loadAdminBoardPage = async () => {
// Kakashi Note: Remove other board scroll listeners before loading this board to avoid stale lazy-load callbacks.
if (typeof detachMinterBoardInfiniteScroll === "function") {
detachMinterBoardInfiniteScroll()
}
if (typeof detachAdminBoardInfiniteScroll === "function") {
detachAdminBoardInfiniteScroll()
}
// Clear existing content on the page
const bodyChildren = document.body.children
for (let i = bodyChildren.length - 1; i >= 0; i--) {
const child = bodyChildren[i];
if (!child.classList.contains("menu")) {
child.remove()
}
}
// Add the "Minter Board" content
const mainContent = document.createElement("div")
mainContent.innerHTML = `
AdminBoard
The Admin Board was meant to be utilized for DECISIONS regarding Minters or would-be Minters, and is encrypted to the Admins so that the data for the DECISIONS remains private. However, it later became the location to REMOVE minters as well. This, not being the original intended purpose has become problematic, as the removal data SHOULD be public. In the future, this data WILL be made public. The Admin Board will continue to be utilized for decision-making, but will NOT be a place for hidden removal data only.
`
document.body.appendChild(mainContent)
if (typeof clearBoardCommentEditState === "function") {
clearBoardCommentEditState()
}
if (typeof boardCommentContentCache !== "undefined") {
boardCommentContentCache.clear()
}
const publishCardButton = document.getElementById("publish-card-button")
if (publishCardButton) {
publishCardButton.addEventListener("click", async () => {
isUpdateCard = false
existingDecryptedCardData = {}
existingEncryptedCardIdentifier = {}
const publishForm = document.getElementById("publish-card-form")
if (publishForm) {
publishForm.reset()
}
const linksContainer = document.getElementById("links-container")
if (linksContainer) {
linksContainer.innerHTML = ``
}
const publishCardView = document.getElementById("publish-card-view")
publishCardView.style.display = "flex"
document.getElementById("encrypted-cards-container").style.display = "none"
if (typeof ensureBoardRichTextEditor === "function") {
ensureBoardRichTextEditor(
adminBoardPublishEditorKey,
"Enter any information you like."
)
clearBoardRichTextEditor(adminBoardPublishEditorKey)
}
const submitButton = document.getElementById("submit-publish-button")
if (submitButton) {
submitButton.textContent = "Publish Card"
}
})
}
const refreshCardsButton = document.getElementById("refresh-cards-button")
if (refreshCardsButton) {
refreshCardsButton.addEventListener("click", async () => {
const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
encryptedCardsContainer.innerHTML = getBoardLoadingHTML("Refreshing cards...")
await fetchAllEncryptedCards(true)
})
}
const cancelPublishButton = document.getElementById("cancel-publish-button")
if (cancelPublishButton) {
cancelPublishButton.addEventListener("click", async () => {
const publishForm = document.getElementById("publish-card-form")
if (publishForm) {
publishForm.reset()
}
if (typeof clearBoardRichTextEditor === "function") {
clearBoardRichTextEditor(adminBoardPublishEditorKey)
}
const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
encryptedCardsContainer.style.display = "flex"; // Restore visibility
const publishCardView = document.getElementById("publish-card-view")
publishCardView.style.display = "none"; // Hide the publish form
isUpdateCard = false
existingDecryptedCardData = {}
existingEncryptedCardIdentifier = {}
const submitButton = document.getElementById("submit-publish-button")
if (submitButton) {
submitButton.textContent = "Publish Card"
}
})
}
const addLinkButton = document.getElementById("add-link-button")
if (addLinkButton) {
addLinkButton.addEventListener("click", async () => {
const linksContainer = document.getElementById("links-container")
const newLinkInput = document.createElement("input")
newLinkInput.type = "text"
newLinkInput.className = "card-link"
newLinkInput.placeholder = "Enter QDN link"
linksContainer.appendChild(newLinkInput)
})
}
const showKickedBannedCheckbox = document.getElementById('admin-show-kicked-banned-checkbox')
if (showKickedBannedCheckbox) {
showKickedBannedCheckbox.addEventListener('change', async (event) => {
await fetchAllEncryptedCards()
})
}
const showHiddenCardsCheckbox = document.getElementById('admin-show-hidden-checkbox')
if (showHiddenCardsCheckbox) {
showHiddenCardsCheckbox.addEventListener('change', async (event) => {
await fetchAllEncryptedCards()
})
}
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
event.preventDefault()
const isTopicChecked = document.getElementById("topic-checkbox").checked
// Pass that boolean to publishEncryptedCard
await publishEncryptedCard(isTopicChecked)
})
document.getElementById("sort-select").addEventListener("change", async () => {
// Re-load the cards whenever user chooses a new sort option.
await fetchAllEncryptedCards()
})
document.getElementById("time-range-select").addEventListener("change", async () => {
await fetchAllEncryptedCards()
})
createScrollToTopButton()
// await fetchAndValidateAllAdminCards()
await updateOrSaveAdminGroupsDataLocally()
await fetchAllEncryptedCards()
}
// Example: fetch and save admin public keys and count
const updateOrSaveAdminGroupsDataLocally = async () => {
try {
// Fetch the array of admin public keys
const verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
// Build an object containing the count and the array
const adminData = {
keysCount: verifiedAdminPublicKeys.length,
publicKeys: verifiedAdminPublicKeys
}
adminPublicKeys = verifiedAdminPublicKeys
// Stringify and save to localStorage
localStorage.setItem('savedAdminData', JSON.stringify(adminData))
console.log('Admin public keys saved locally:', adminData)
} catch (error) {
console.error('Error fetching/storing admin public keys:', error)
attemptLoadAdminDataCount++
}
}
const loadOrFetchAdminGroupsData = async () => {
try {
// Pull the JSON from localStorage
const storedData = localStorage.getItem('savedAdminData')
if (!storedData && attemptLoadAdminDataCount <= 3) {
console.log('No saved admin public keys found in local storage. Fetching...')
await updateOrSaveAdminGroupsDataLocally()
attemptLoadAdminDataCount++
return null
}
// Parse the JSON, then store the global variables.
const parsedData = JSON.parse(storedData)
adminMemberCount = parsedData.keysCount
adminPublicKeys = parsedData.publicKeys
console.log(typeof adminPublicKeys) // Should be "object"
console.log(Array.isArray(adminPublicKeys))
console.log(`Loaded admins 'keysCount'=${adminMemberCount}, publicKeys=`, adminPublicKeys)
attemptLoadAdminDataCount = 0
return parsedData // and return { adminMemberCount, adminKeys } to the caller
} catch (error) {
console.error('Error loading/parsing saved admin public keys:', error)
return null
}
}
const adminRunWithConcurrency = async (tasks, concurrency = 8) => {
const results = new Array(tasks.length)
let index = 0
const workers = new Array(concurrency).fill(null).map(async () => {
while (index < tasks.length) {
const currentIndex = index++
const task = tasks[currentIndex]
results[currentIndex] = await task()
}
})
await Promise.all(workers)
return results
}
const getAdminBoardResourceTimestamp = (resource) => resource?.updated || resource?.created || 0
const getAdminBoardResourceCacheKey = (resource) => `${resource?.name || ""}::${resource?.identifier || ""}::${getAdminBoardResourceTimestamp(resource)}`
const getAdminBoardResourceIdentityKey = (resource) => `${resource?.name || ""}::${resource?.identifier || ""}`
const getOptimisticEncryptedCommentCacheKey = (publisherName, commentIdentifier) => `${publisherName || ""}::${commentIdentifier || ""}`
const fetchCachedAdminSearchResources = async (dayRange, afterTime, forceSearch = false) => {
if (forceSearch) {
adminBoardSearchCache.resourcesByKey.clear()
adminBoardSearchCache.maxDaysCovered = 0
adminBoardSearchCache.hasAllRange = false
}
const cacheCoversRange = dayRange === 0
? adminBoardSearchCache.hasAllRange
: (adminBoardSearchCache.hasAllRange || adminBoardSearchCache.maxDaysCovered >= dayRange)
if (!cacheCoversRange) {
const fetched = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0, 0, '', false, true, afterTime)
const fetchedArray = Array.isArray(fetched) ? fetched : []
for (const resource of fetchedArray) {
adminBoardSearchCache.resourcesByKey.set(getAdminBoardResourceCacheKey(resource), resource)
}
if (dayRange === 0) {
adminBoardSearchCache.hasAllRange = true
} else {
adminBoardSearchCache.maxDaysCovered = Math.max(adminBoardSearchCache.maxDaysCovered, dayRange)
}
}
const allCached = Array.from(adminBoardSearchCache.resourcesByKey.values())
if (afterTime > 0) {
return allCached.filter((resource) => getAdminBoardResourceTimestamp(resource) >= afterTime)
}
return allCached
}
const getDecryptedAdminCardCached = async (cardResource) => {
const cacheKey = getAdminBoardResourceCacheKey(cardResource)
if (adminBoardDecryptedCardCache.has(cacheKey)) {
return adminBoardDecryptedCardCache.get(cacheKey)
}
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: cardResource.name,
service: "MAIL_PRIVATE",
identifier: cardResource.identifier,
encoding: "base64",
})
if (!cardDataResponse) {
return null
}
const decryptedCardData = await decryptAndParseObject(cardDataResponse)
adminBoardDecryptedCardCache.set(cacheKey, decryptedCardData)
return decryptedCardData
}
const rememberOptimisticEncryptedComment = (cardIdentifier, publisherName, commentIdentifier, commentData, timestamp = Date.now()) => {
if (!cardIdentifier || !publisherName || !commentIdentifier || !commentData) return
const resource = {
name: publisherName,
service: "MAIL_PRIVATE",
identifier: commentIdentifier,
created: timestamp,
updated: timestamp,
_optimisticComment: true,
_cardIdentifier: cardIdentifier
}
optimisticEncryptedCommentCache.set(getOptimisticEncryptedCommentCacheKey(publisherName, commentIdentifier), {
cardIdentifier,
resource,
commentData: {
...commentData,
_optimisticPending: true
}
})
if (typeof rememberBoardCommentContent === "function") {
rememberBoardCommentContent(commentIdentifier, commentData?.content || "")
}
}
const getOptimisticEncryptedComments = (cardIdentifier, existingResourcesByIdentity = new Map()) => {
const comments = []
for (const [cacheKey, entry] of optimisticEncryptedCommentCache.entries()) {
if (!entry || entry.cardIdentifier !== cardIdentifier || !entry.resource) continue
const identityKey = getAdminBoardResourceIdentityKey(entry.resource)
const existingResource = existingResourcesByIdentity.get(identityKey)
const existingTimestamp = getAdminBoardResourceTimestamp(existingResource)
const optimisticTimestamp = getAdminBoardResourceTimestamp(entry.resource)
if (existingResource && existingTimestamp >= optimisticTimestamp) {
optimisticEncryptedCommentCache.delete(cacheKey)
continue
}
comments.push(entry.resource)
}
return comments
}
const fetchEncryptedCommentData = async (commentResource) => {
const optimisticEntry = optimisticEncryptedCommentCache.get(getOptimisticEncryptedCommentCacheKey(commentResource?.name, commentResource?.identifier))
if (optimisticEntry?.commentData) {
return optimisticEntry.commentData
}
const commentDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: commentResource.name,
service: "MAIL_PRIVATE",
identifier: commentResource.identifier,
encoding: "base64",
})
return await decryptAndParseObject(commentDataResponse)
}
const detachAdminBoardInfiniteScroll = () => {
if (adminBoardInfiniteState.scrollHandler) {
window.removeEventListener("scroll", adminBoardInfiniteState.scrollHandler)
adminBoardInfiniteState.scrollHandler = null
}
}
const removeAdminBoardSkeleton = (cardIdentifier) => {
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
if (skeletonCard) {
skeletonCard.remove()
}
}
const replaceAdminBoardSkeleton = (cardIdentifier, htmlContent) => {
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`)
if (skeletonCard) {
skeletonCard.outerHTML = htmlContent
}
}
const maybeRenderMoreAdminBoardCards = async (loadToken) => {
if (loadToken !== adminBoardInfiniteState.loadToken) return
if (adminBoardInfiniteState.inFlight || adminBoardInfiniteState.complete) return
await renderAdminBoardCardBatch(loadToken)
}
const startAdminBoardBackgroundRender = (loadToken) => {
if (adminBoardInfiniteState.backgroundRunnerToken === loadToken) return
adminBoardInfiniteState.backgroundRunnerToken = loadToken
const run = async () => {
try {
while (loadToken === adminBoardInfiniteState.loadToken && !adminBoardInfiniteState.complete) {
await maybeRenderMoreAdminBoardCards(loadToken)
await new Promise((resolve) => setTimeout(resolve, 0))
}
} catch (error) {
console.warn("Error during admin board background render:", error)
} finally {
if (adminBoardInfiniteState.backgroundRunnerToken === loadToken) {
adminBoardInfiniteState.backgroundRunnerToken = 0
}
}
}
run()
}
const updateAdminBoardProgressText = () => {
const progressEl = adminBoardInfiniteState.progressEl
if (!progressEl) return
const displayed = adminBoardInfiniteState.displayedCount
const total = adminBoardInfiniteState.totalCount || adminBoardInfiniteState.cards.length || 0
if (adminBoardInfiniteState.isBackgroundLoading && total > 0) {
const loadingHtml = (typeof getBoardInlineLoadingHTML === "function")
? getBoardInlineLoadingHTML(`Loading cards ${Math.min(displayed, total)}/${total}`)
: "Loading cards..."
progressEl.innerHTML = `${loadingHtml}`
return
}
if (total > 0) {
progressEl.textContent = `(${displayed} of ${total} cards displayed)`
return
}
progressEl.textContent = ""
}
const renderAdminBoardCardBatch = async (loadToken) => {
// Kakashi Note: Load token gates prevent old async work from mutating the board after a refresh or sort change.
if (loadToken !== adminBoardInfiniteState.loadToken) return
if (adminBoardInfiniteState.inFlight || adminBoardInfiniteState.complete) return
const cardsContainer = adminBoardInfiniteState.container
if (!cardsContainer || !document.body.contains(cardsContainer)) {
adminBoardInfiniteState.complete = true
adminBoardInfiniteState.inFlight = false
adminBoardInfiniteState.isBackgroundLoading = false
detachAdminBoardInfiniteScroll()
updateAdminBoardProgressText()
return
}
const start = adminBoardInfiniteState.cursor
const end = Math.min(start + ADMIN_SCROLL_BATCH_SIZE, adminBoardInfiniteState.cards.length)
if (start >= end) {
adminBoardInfiniteState.complete = true
adminBoardInfiniteState.isBackgroundLoading = false
updateAdminBoardProgressText()
return
}
const batch = adminBoardInfiniteState.cards.slice(start, end)
adminBoardInfiniteState.cursor = end
adminBoardInfiniteState.inFlight = true
// Kakashi Note: Insert skeletons first so users see immediate progress while encrypted payloads finalize.
for (const { card } of batch) {
if (loadToken !== adminBoardInfiniteState.loadToken) {
adminBoardInfiniteState.inFlight = false
return
}
const skeletonHTML = createEncryptedSkeletonCardHTML(card.identifier)
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
}
const finalizeTasks = batch.map(({ card, decryptedCardData }) => {
return async () => {
if (loadToken !== adminBoardInfiniteState.loadToken) return
try {
const encryptedCardPollPublisherPublicKey = await getPollPublisherPublicKey(decryptedCardData.poll)
const encryptedCardPublisherPublicKey = await getPublicKeyByName(card.name)
if (encryptedCardPollPublisherPublicKey !== encryptedCardPublisherPublicKey) {
console.warn(`QuickMythril cardPollHijack attack detected! Skipping card: ${card.identifier}`)
if (loadToken === adminBoardInfiniteState.loadToken) {
removeAdminBoardSkeleton(card.identifier)
}
return
}
const pollResults = await fetchPollResultsCached(decryptedCardData.poll)
if (pollResults?.error) {
if (loadToken === adminBoardInfiniteState.loadToken) {
removeAdminBoardSkeleton(card.identifier)
}
return
}
const encryptedCommentCount = await getEncryptedCommentCount(card.identifier)
const finalCardHTML = await createEncryptedCardHTML(
decryptedCardData,
pollResults,
card.identifier,
encryptedCommentCount,
adminBoardInfiniteState.sharedBoardData
)
if (loadToken !== adminBoardInfiniteState.loadToken) return
if (!finalCardHTML || finalCardHTML === "") {
removeAdminBoardSkeleton(card.identifier)
return
}
adminBoardInfiniteState.displayedCount += 1
updateAdminBoardProgressText()
replaceAdminBoardSkeleton(card.identifier, finalCardHTML)
} catch (error) {
console.error(`Error finalizing card ${card.identifier}:`, error)
if (loadToken === adminBoardInfiniteState.loadToken) {
removeAdminBoardSkeleton(card.identifier)
}
}
}
})
try {
await adminRunWithConcurrency(finalizeTasks, 6)
} finally {
adminBoardInfiniteState.inFlight = false
}
if (loadToken !== adminBoardInfiniteState.loadToken) return
if (adminBoardInfiniteState.cursor >= adminBoardInfiniteState.cards.length) {
adminBoardInfiniteState.complete = true
adminBoardInfiniteState.isBackgroundLoading = false
}
updateAdminBoardProgressText()
}
const extractEncryptedCardsMinterName = (cardIdentifier) => {
const parts = cardIdentifier.split('-')
// Ensure the format has at least 3 parts
if (parts.length < 3) {
throw new Error('Invalid identifier format')
}
if (parts.slice(2, -1).join('-') === 'TOPIC') {
console.log(`TOPIC found in identifier: ${cardIdentifier} - not including in duplicatesList`)
return
}
// Extract minterName (everything from the second part to the second-to-last part)
const minterName = parts.slice(2, -1).join('-')
// Return the extracted minterName
return minterName
}
const fetchAllEncryptedCards = async (forceSearch = false) => {
const loadToken = adminBoardInfiniteState.loadToken + 1
adminBoardInfiniteState.loadToken = loadToken
detachAdminBoardInfiniteScroll()
adminBoardInfiniteState.cards = []
adminBoardInfiniteState.cursor = 0
adminBoardInfiniteState.inFlight = false
adminBoardInfiniteState.complete = false
adminBoardInfiniteState.displayedCount = 0
adminBoardInfiniteState.totalCount = 0
adminBoardInfiniteState.isBackgroundLoading = false
adminBoardInfiniteState.progressEl = null
adminBoardInfiniteState.sharedBoardData = null
adminBoardInfiniteState.backgroundRunnerToken = 0
if (forceSearch) {
adminBoardDecryptedCardCache.clear()
if (typeof clearPollResultsCache === "function") {
clearPollResultsCache()
}
}
const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
encryptedCardsContainer.innerHTML = getBoardLoadingHTML("Loading cards...")
adminBoardInfiniteState.container = encryptedCardsContainer
adminBoardInfiniteState.progressEl = document.getElementById("admin-board-progress")
updateAdminBoardProgressText()
let afterTime = 0
let dayRange = 0
const timeRangeSelect = document.getElementById("time-range-select")
if (timeRangeSelect) {
const days = parseInt(timeRangeSelect.value, 10)
dayRange = Number.isNaN(days) ? 0 : days
if (dayRange > 0) {
const now = Date.now()
const dayMs = 24 * 60 * 60 * 1000
afterTime = now - dayRange * dayMs // e.g. last X days
console.log(`afterTime for last ${dayRange} days = ${new Date(afterTime).toLocaleString()}`)
}
}
try {
const response = await fetchCachedAdminSearchResources(dayRange, afterTime, forceSearch)
if (loadToken !== adminBoardInfiniteState.loadToken) return
if (!response || response.length === 0) {
adminBoardInfiniteState.isBackgroundLoading = false
adminBoardInfiniteState.totalCount = 0
updateAdminBoardProgressText()
encryptedCardsContainer.innerHTML = "
`
showRemoveHtml = ''
if (!adminBoardState.kickedCards.has(cardIdentifier)){
adminBoardState.kickedCards.add(cardIdentifier)
}
if (!showKickedBanned) {
console.warn(`kick/ban checkbox is unchecked, card is kicked, not displaying...`)
return ''
}
}
if (confirmedBan && !pendingBan && !pendingKick && !existingMinter) {
console.warn(`account was already banned, displaying as such...`)
cardColorCode = 'rgb(24, 3, 3)'
altText = `
BANNED From MINTER Group
`
showRemoveHtml = ''
if (!adminBoardState.bannedCards.has(cardIdentifier)){
adminBoardState.bannedCards.add(cardIdentifier)
}
if (!showKickedBanned){
console.warn(`kick/bank checkbox is unchecked, and card is banned, not displaying...`)
return ''
}
}
} else {
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
showRemoveHtml = ''
}
return `
${commenterNameHtml} ${levelBadgeHtml} ${adminBadge}
${safeTimestamp}
${optimisticNotice}