// NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards. const isEncryptedTestMode = false const encryptedCardIdentifierPrefix = "card-MAC" let isUpdateCard = false let existingDecryptedCardData = {} let existingEncryptedCardIdentifier = {} let cardMinterName = {} let existingCardMinterNames = [] let isTopic = false let attemptLoadAdminDataCount = 0 let adminMemberCount = 0 let adminPublicKeys = [] console.log("Attempting to load AdminBoard.js") const loadAdminBoardPage = async () => { // 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 = `
The Admin Board is an encrypted card publishing board to keep track of minter data for the Minter Admins. Any Admin may publish a card, and related data, make comments on existing cards, and vote on existing card data in support or not of the name on the card. It is essentially a 'project management' tool to assist the Minter Admins in keeping track of the data related to minters they are adding/removing from the minter group.
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'.
Refreshing cards...
" await fetchAllEncryptedCards(true) }) } const cancelPublishButton = document.getElementById("cancel-publish-button") if (cancelPublishButton) { cancelPublishButton.addEventListener("click", async () => { 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 }) } 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) }) } 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) }) // await fetchAndValidateAllAdminCards() await fetchAllEncryptedCards() await updateOrSaveAdminGroupsDataLocally() } // 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 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 processCards = async (validEncryptedCards) => { const latestCardsMap = new Map() await Promise.all(validEncryptedCards.map(async card => { const timestamp = card.updated || card.created || 0 const existingCard = latestCardsMap.get(card.identifier) if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) { latestCardsMap.set(card.identifier, card) } })) console.log(`latestCardsMap, by timestamp`, latestCardsMap) const uniqueValidCards = Array.from(latestCardsMap.values()) return uniqueValidCards } //Main function to load the Minter Cards ---------------------------------------- const fetchAllEncryptedCards = async (isRefresh=false) => { const encryptedCardsContainer = document.getElementById("encrypted-cards-container") encryptedCardsContainer.innerHTML = "Loading cards...
" try { const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0) if (!response || !Array.isArray(response) || response.length === 0) { encryptedCardsContainer.innerHTML = "No cards found.
" return } // Validate cards and filter const validatedEncryptedCards = await Promise.all( response.map(async card => { const isValid = await validateEncryptedCardIdentifier(card) return isValid ? card : null }) ) console.log(`validatedEncryptedCards:`, validatedEncryptedCards, `... running next filter...`) const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null) console.log(`validEncryptedcards:`, validEncryptedCards) if (validEncryptedCards.length === 0) { encryptedCardsContainer.innerHTML = "No valid cards found.
"; return; } const finalCards = await processCards(validEncryptedCards) console.log(`finalCards:`,finalCards) // Display skeleton cards immediately encryptedCardsContainer.innerHTML = "" finalCards.forEach(card => { const skeletonHTML = createSkeletonCardHTML(card.identifier) encryptedCardsContainer.insertAdjacentHTML("beforeend", skeletonHTML) }) // Fetch and update each card finalCards.forEach(async card => { try { // const hasMinterName = await extractEncryptedCardsMinterName(card.identifier) // if (hasMinterName) existingCardMinterNames.push(hasMinterName) const cardDataResponse = await qortalRequest({ action: "FETCH_QDN_RESOURCE", name: card.name, service: "MAIL_PRIVATE", identifier: card.identifier, encoding: "base64" }) if (!cardDataResponse) { console.warn(`Skipping invalid card: ${JSON.stringify(card)}`) removeSkeleton(card.identifier) return } const decryptedCardData = await decryptAndParseObject(cardDataResponse) // Skip cards without polls if (!decryptedCardData.poll) { console.warn(`Skipping card with no poll: ${card.identifier}`) removeSkeleton(card.identifier) return } const encryptedCardPollPublisherPublicKey = await getPollPublisherPublicKey(decryptedCardData.poll) const encryptedCardPublisherPublicKey = await getPublicKeyByName(card.name) if (encryptedCardPollPublisherPublicKey != encryptedCardPublisherPublicKey) { console.warn(`QuickMythril cardPollHijack attack found! Not including card with identifier: ${card.identifier}`) removeSkeleton(card.identifier) return } // Fetch poll results and discard cards with no results const pollResults = await fetchPollResults(decryptedCardData.poll) if (pollResults?.error) { console.warn(`Skipping card with failed poll results?: ${card.identifier}, poll=${decryptedCardData.poll}`) removeSkeleton(card.identifier) return } if (!isRefresh) { console.log(`This is a REFRESH, NOT adding names to duplicates list...`) const obtainedMinterName = decryptedCardData.minterName // if ((obtainedMinterName) && existingCardMinterNames.includes(obtainedMinterName)) { // console.warn(`name found in existing names array...${obtainedMinterName} skipping duplicate card...${card.identifier}`) // removeSkeleton(card.identifier) // return // } else if ((obtainedMinterName) && (!existingCardMinterNames.includes(obtainedMinterName))) { // existingCardMinterNames.push(obtainedMinterName) // console.log(`minterName: ${obtainedMinterName} found, doesn't exist in existing array, added to existingCardMinterNames array`) // } if (obtainedMinterName && existingCardMinterNames.some(item => item.minterName === obtainedMinterName)) { console.warn(`name found in existing names array...${obtainedMinterName} skipping duplicate card...${card.identifier}`) removeSkeleton(card.identifier) return } else if (obtainedMinterName) { existingCardMinterNames.push({ minterName: obtainedMinterName, identifier: card.identifier }) console.log(`Added minterName and identifier to existingCardMinterNames array:`, { minterName: obtainedMinterName, identifier: card.identifier }) } } // const minterNameFromIdentifier = await extractCardsMinterName(card.identifier); const encryptedCommentCount = await getEncryptedCommentCount(card.identifier) // Generate final card HTML const finalCardHTML = await createEncryptedCardHTML(decryptedCardData, pollResults, card.identifier, encryptedCommentCount) replaceSkeleton(card.identifier, finalCardHTML) } catch (error) { console.error(`Error processing card ${card.identifier}:`, error) removeSkeleton(card.identifier) } }) } catch (error) { console.error("Error loading cards:", error) encryptedCardsContainer.innerHTML = "Failed to load cards.
" } } //TODO verify that this actually isn't necessary. if not, remove it. // const removeEncryptedSkeleton = (cardIdentifier) => { // const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`) // if (encryptedSkeletonCard) { // encryptedSkeletonCard.remove(); // Remove the skeleton silently // } // } // const replaceEncryptedSkeleton = (cardIdentifier, htmlContent) => { // const encryptedSkeletonCard = document.getElementById(`skeleton-${cardIdentifier}`) // if (encryptedSkeletonCard) { // encryptedSkeletonCard.outerHTML = htmlContent; // } // } // Function to create a skeleton card const createEncryptedSkeletonCardHTML = (cardIdentifier) => { return `${decryptedCommentData.creator} ${adminBadge}
${decryptedCommentData.content}
${timestamp}
${header}
(click COMMENTS button to open/close card comments)
By: ${creator} - ${formattedDate}
${decryptedCommentData.creator}:
//${decryptedCommentData.content}
//${timestamp}
//