2024-12-17 22:24:40 -08:00
// // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards.
2025-01-01 13:47:45 -08:00
const testMode = false
const cardIdentifierPrefix = "Minter-board-card"
let isExistingCard = false
let existingCardData = { }
let existingCardIdentifier = { }
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
const loadMinterBoardPage = async ( ) => {
2024-12-11 14:40:32 -08:00
// Clear existing content on the page
2025-01-01 13:47:45 -08:00
const bodyChildren = document . body . children
2024-12-11 14:40:32 -08:00
for ( let i = bodyChildren . length - 1 ; i >= 0 ; i -- ) {
const child = bodyChildren [ i ] ;
if ( ! child . classList . contains ( "menu" ) ) {
2025-01-01 13:47:45 -08:00
child . remove ( )
2024-12-11 14:40:32 -08:00
}
}
// Add the "Minter Board" content
2025-01-01 13:47:45 -08:00
const mainContent = document . createElement ( "div" )
2024-12-24 00:27:17 -08:00
const publishButtonColor = '#527c9d'
const minterBoardNameColor = '#527c9d'
2024-12-11 14:40:32 -08:00
mainContent . innerHTML = `
< div class = "minter-board-main" style = "padding: 20px; text-align: center;" >
2024-12-20 22:07:18 -08:00
< h1 style = "color: ${minterBoardNameColor};" > Minter Board < / h 1 >
< p style = "font-size: 1.25em;" > Publish a Minter Card with Information , and obtain and view the support of the community . Welcome to the Minter Board ! < / p >
< button id = "publish-card-button" class = "publish-card-button" style = "margin: 20px; padding: 10px; background-color: ${publishButtonColor}" > Publish Minter Card < / b u t t o n >
2024-12-16 19:53:37 -08:00
< button id = "refresh-cards-button" class = "refresh-cards-button" style = "padding: 10px;" > Refresh Cards < / b u t t o n >
2024-12-11 14:40:32 -08:00
< div id = "cards-container" class = "cards-container" style = "margin-top: 20px;" > < / d i v >
< div id = "publish-card-view" class = "publish-card-view" style = "display: none; text-align: left; padding: 20px;" >
< form id = "publish-card-form" >
< h3 > Create or Update Your Minter Card < / h 3 >
< label for = "card-header" > Header : < / l a b e l >
< input type = "text" id = "card-header" maxlength = "100" placeholder = "Enter card header" required >
< label for = "card-content" > Content : < / l a b e l >
2024-12-28 22:49:18 -08:00
< textarea id = "card-content" placeholder = "Enter detailed information about why you would like to be a minter... the more the better, and links to things you have published on QDN will help a lot! Give the Minter Admins things to make decisions by!" required > < / t e x t a r e a >
2024-12-11 14:40:32 -08:00
< label for = "card-links" > Links ( qortal : //...):</label>
< div id = "links-container" >
< input type = "text" class = "card-link" placeholder = "Enter QDN link" >
< / d i v >
< button type = "button" id = "add-link-button" > Add Another Link < / b u t t o n >
< button type = "submit" id = "submit-publish-button" > Publish Card < / b u t t o n >
< button type = "button" id = "cancel-publish-button" > Cancel < / b u t t o n >
< / f o r m >
< / d i v >
< / d i v >
2025-01-01 13:47:45 -08:00
`
document . body . appendChild ( mainContent )
2024-12-11 14:40:32 -08:00
document . getElementById ( "publish-card-button" ) . addEventListener ( "click" , async ( ) => {
try {
2025-01-01 13:47:45 -08:00
const fetchedCard = await fetchExistingCard ( )
2024-12-16 19:53:37 -08:00
if ( fetchedCard ) {
// An existing card is found
if ( testMode ) {
// In test mode, ask user what to do
2025-01-01 13:47:45 -08:00
const updateCard = confirm ( "A card already exists. Do you want to update it?" )
2024-12-16 19:53:37 -08:00
if ( updateCard ) {
2025-01-01 13:47:45 -08:00
isExistingCard = true
await loadCardIntoForm ( existingCardData )
alert ( "Edit your existing card and publish." )
2024-12-16 19:53:37 -08:00
} else {
2025-01-01 13:47:45 -08:00
alert ( "Test mode: You can now create a new card." )
isExistingCard = false
existingCardData = { }
document . getElementById ( "publish-card-form" ) . reset ( )
2024-12-16 19:53:37 -08:00
}
2024-12-11 14:40:32 -08:00
} else {
2024-12-16 19:53:37 -08:00
// Not in test mode, force editing
alert ( "A card already exists. Publishing of multiple cards is not allowed. Please update your card." ) ;
isExistingCard = true ;
2025-01-01 13:47:45 -08:00
await loadCardIntoForm ( existingCardData )
2024-12-11 14:40:32 -08:00
}
} else {
2024-12-16 19:53:37 -08:00
// No existing card found
2025-01-01 13:47:45 -08:00
console . log ( "No existing card found. Creating a new card." )
isExistingCard = false
2024-12-11 14:40:32 -08:00
}
2024-12-16 19:53:37 -08:00
// Show the form
2025-01-01 13:47:45 -08:00
const publishCardView = document . getElementById ( "publish-card-view" )
2024-12-11 14:40:32 -08:00
publishCardView . style . display = "flex" ;
2025-01-01 13:47:45 -08:00
document . getElementById ( "cards-container" ) . style . display = "none"
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2025-01-01 13:47:45 -08:00
console . error ( "Error checking for existing card:" , error )
alert ( "Failed to check for existing card. Please try again." )
2024-12-11 14:40:32 -08:00
}
2025-01-01 13:47:45 -08:00
} )
2024-12-11 14:40:32 -08:00
2024-12-16 19:53:37 -08:00
document . getElementById ( "refresh-cards-button" ) . addEventListener ( "click" , async ( ) => {
2025-01-01 13:47:45 -08:00
const cardsContainer = document . getElementById ( "cards-container" )
cardsContainer . innerHTML = "<p>Refreshing cards...</p>"
2024-12-16 19:53:37 -08:00
await loadCards ( ) ;
2025-01-01 13:47:45 -08:00
} )
2024-12-16 19:53:37 -08:00
2024-12-12 17:23:34 -08:00
document . getElementById ( "cancel-publish-button" ) . addEventListener ( "click" , async ( ) => {
2025-01-01 13:47:45 -08:00
const cardsContainer = document . getElementById ( "cards-container" )
2024-12-11 14:40:32 -08:00
cardsContainer . style . display = "flex" ; // Restore visibility
2025-01-01 13:47:45 -08:00
const publishCardView = document . getElementById ( "publish-card-view" )
2024-12-11 14:40:32 -08:00
publishCardView . style . display = "none" ; // Hide the publish form
2025-01-01 13:47:45 -08:00
} )
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
document . getElementById ( "add-link-button" ) . addEventListener ( "click" , async ( ) => {
2025-01-01 13:47:45 -08:00
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 )
} )
2024-12-11 14:40:32 -08:00
document . getElementById ( "publish-card-form" ) . addEventListener ( "submit" , async ( event ) => {
2025-01-01 13:47:45 -08:00
event . preventDefault ( )
await publishCard ( )
} )
2024-12-11 14:40:32 -08:00
2025-01-01 13:47:45 -08:00
await loadCards ( )
2024-12-11 14:40:32 -08:00
}
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
const extractMinterCardsMinterName = async ( cardIdentifier ) => {
// Ensure the identifier starts with the prefix
if ( ! cardIdentifier . startsWith ( ` ${ cardIdentifierPrefix } - ` ) ) {
2024-12-27 23:04:16 -08:00
throw new Error ( 'Invalid identifier format or prefix mismatch' )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
}
// Split the identifier into parts
2024-12-27 23:04:16 -08:00
const parts = cardIdentifier . split ( '-' )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
// Ensure the format has at least 3 parts
if ( parts . length < 3 ) {
2024-12-27 23:04:16 -08:00
throw new Error ( 'Invalid identifier format' )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
}
try {
2024-12-27 23:04:16 -08:00
const searchSimpleResults = await searchSimple ( 'BLOG_POST' , ` ${ cardIdentifier } ` , '' , 1 )
const minterName = await searchSimpleResults . name
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
return minterName
} catch ( error ) {
throw error
}
}
const processMinterCards = async ( validMinterCards ) => {
const latestCardsMap = new Map ( )
validMinterCards . forEach ( 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 )
}
} )
2024-12-30 21:39:18 -08:00
const minterGroupMembers = await fetchMinterGroupMembers ( )
const minterGroupAddresses = minterGroupMembers . map ( m => m . member )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
const minterNameMap = new Map ( )
for ( const card of validMinterCards ) {
const minterName = await extractMinterCardsMinterName ( card . identifier )
2024-12-30 21:39:18 -08:00
console . log ( ` minterName ` , minterName )
const minterNameInfo = await getNameInfo ( minterName )
if ( ! minterNameInfo ) {
console . warn ( ` minterNameInfo is null for minter: ${ minterName } ` )
continue
}
const minterAddress = await minterNameInfo . owner
if ( ! minterAddress ) {
console . warn ( ` minterAddress is FAKE or INVALID in some way! minter: ${ minterName } ` )
continue
} else if ( minterGroupAddresses . includes ( minterAddress ) ) {
console . log ( ` existing minter FOUND and/or FAKE NAME FOUND (if following is null then fake name: ${ minterAddress } ), not including minter card: ${ card . identifier } ` )
continue
}
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
const existingCard = minterNameMap . get ( minterName )
const cardTimestamp = card . updated || card . created || 0
const existingTimestamp = existingCard ? . updated || existingCard ? . created || 0
if ( ! existingCard || cardTimestamp > existingTimestamp ) {
minterNameMap . set ( minterName , card )
}
}
const finalCards = [ ]
const seenMinterNames = new Set ( )
for ( const [ minterName , card ] of minterNameMap . entries ( ) ) {
if ( ! seenMinterNames . has ( minterName ) ) {
finalCards . push ( card )
2024-12-30 21:39:18 -08:00
seenMinterNames . add ( minterName )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
}
}
finalCards . sort ( ( a , b ) => {
const timestampA = a . updated || a . created || 0
const timestampB = b . updated || b . created || 0
return timestampB - timestampA
} )
return finalCards
}
2024-12-12 17:23:34 -08:00
//Main function to load the Minter Cards ----------------------------------------
const loadCards = async ( ) => {
2024-12-30 21:39:18 -08:00
const cardsContainer = document . getElementById ( "cards-container" )
cardsContainer . innerHTML = "<p>Loading cards...</p>"
2024-12-12 17:23:34 -08:00
try {
2024-12-27 23:04:16 -08:00
const response = await searchSimple ( 'BLOG_POST' , ` ${ cardIdentifierPrefix } ` , '' , 0 )
2024-12-12 17:23:34 -08:00
if ( ! response || ! Array . isArray ( response ) || response . length === 0 ) {
2024-12-30 21:39:18 -08:00
cardsContainer . innerHTML = "<p>No cards found.</p>"
2024-12-12 17:23:34 -08:00
return ;
}
2024-12-16 19:53:37 -08:00
// Validate cards and filter
2024-12-12 17:23:34 -08:00
const validatedCards = await Promise . all (
response . map ( async card => {
2024-12-30 21:39:18 -08:00
const isValid = await validateCardStructure ( card )
return isValid ? card : null
2024-12-12 17:23:34 -08:00
} )
) ;
2024-12-30 21:39:18 -08:00
const validCards = validatedCards . filter ( card => card !== null )
2024-12-12 17:23:34 -08:00
if ( validCards . length === 0 ) {
2024-12-30 21:39:18 -08:00
cardsContainer . innerHTML = "<p>No valid cards found.</p>"
return
2024-12-12 17:23:34 -08:00
}
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
const finalCards = await processMinterCards ( validCards )
2024-12-16 19:53:37 -08:00
// Display skeleton cards immediately
2024-12-30 21:39:18 -08:00
cardsContainer . innerHTML = ""
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
finalCards . forEach ( card => {
2024-12-30 21:39:18 -08:00
const skeletonHTML = createSkeletonCardHTML ( card . identifier )
cardsContainer . insertAdjacentHTML ( "beforeend" , skeletonHTML )
} )
2024-12-14 19:40:31 -08:00
2024-12-16 19:53:37 -08:00
// Fetch and update each card
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
finalCards . forEach ( async card => {
2024-12-16 19:53:37 -08:00
try {
const cardDataResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : card . name ,
service : "BLOG_POST" ,
identifier : card . identifier ,
2024-12-30 21:39:18 -08:00
} )
2024-12-16 19:53:37 -08:00
if ( ! cardDataResponse ) {
2024-12-30 21:39:18 -08:00
console . warn ( ` Skipping invalid card: ${ JSON . stringify ( card ) } ` )
removeSkeleton ( card . identifier )
return
2024-12-12 17:23:34 -08:00
}
2024-12-16 19:53:37 -08:00
if ( ! cardDataResponse . poll ) {
2024-12-30 21:39:18 -08:00
console . warn ( ` Skipping card with no poll: ${ card . identifier } ` )
removeSkeleton ( card . identifier )
return
2024-12-16 19:53:37 -08:00
}
2024-12-30 21:39:18 -08:00
const pollPublisherPublicKey = await getPollPublisherPublicKey ( cardDataResponse . poll )
const cardPublisherPublicKey = await getPublicKeyByName ( card . name )
if ( pollPublisherPublicKey != cardPublisherPublicKey ) {
console . warn ( ` not displaying card, QuickMythril pollHijack attack found! Discarding card with identifier: ${ card . identifier } ` )
removeSkeleton ( card . identifier )
return
}
const pollResults = await fetchPollResults ( cardDataResponse . poll )
2024-12-20 22:07:18 -08:00
const BgColor = generateDarkPastelBackgroundBy ( card . name )
const commentCount = await countComments ( card . identifier )
2024-12-24 00:27:17 -08:00
const cardUpdatedTime = card . updated || null
2024-12-30 21:39:18 -08:00
const finalCardHTML = await createCardHTML ( cardDataResponse , pollResults , card . identifier , commentCount , cardUpdatedTime , BgColor )
2024-12-20 22:07:18 -08:00
2024-12-30 21:39:18 -08:00
replaceSkeleton ( card . identifier , finalCardHTML )
2024-12-16 19:53:37 -08:00
} catch ( error ) {
2024-12-30 21:39:18 -08:00
console . error ( ` Error processing card ${ card . identifier } : ` , error )
removeSkeleton ( card . identifier )
2024-12-16 19:53:37 -08:00
}
2024-12-30 21:39:18 -08:00
} )
2024-12-16 19:53:37 -08:00
2024-12-12 17:23:34 -08:00
} catch ( error ) {
2024-12-30 21:39:18 -08:00
console . error ( "Error loading cards:" , error )
cardsContainer . innerHTML = "<p>Failed to load cards.</p>"
2024-12-12 17:23:34 -08:00
}
2024-12-30 21:39:18 -08:00
}
2024-12-12 17:23:34 -08:00
2024-12-16 19:53:37 -08:00
const removeSkeleton = ( cardIdentifier ) => {
2024-12-30 21:39:18 -08:00
const skeletonCard = document . getElementById ( ` skeleton- ${ cardIdentifier } ` )
2024-12-16 19:53:37 -08:00
if ( skeletonCard ) {
2024-12-30 21:39:18 -08:00
skeletonCard . remove ( )
2024-12-16 19:53:37 -08:00
}
2024-12-30 21:39:18 -08:00
}
2024-12-12 17:23:34 -08:00
2024-12-16 19:53:37 -08:00
const replaceSkeleton = ( cardIdentifier , htmlContent ) => {
2024-12-30 21:39:18 -08:00
const skeletonCard = document . getElementById ( ` skeleton- ${ cardIdentifier } ` )
2024-12-16 19:53:37 -08:00
if ( skeletonCard ) {
2024-12-30 21:39:18 -08:00
skeletonCard . outerHTML = htmlContent
2024-12-16 19:53:37 -08:00
}
2024-12-30 21:39:18 -08:00
}
2024-12-16 19:53:37 -08:00
const createSkeletonCardHTML = ( cardIdentifier ) => {
return `
< div id = "skeleton-${cardIdentifier}" class = "skeleton-card" style = "padding: 10px; border: 1px solid gray; margin: 10px 0;" >
< div style = "display: flex; align-items: center;" >
2024-12-20 22:07:18 -08:00
< div > < p style = "color:rgb(174, 174, 174)" > LOADING CARD ... < / p > < / d i v >
2024-12-16 19:53:37 -08:00
< div style = "width: 50px; height: 50px; background-color: #ccc; border-radius: 50%;" > < / d i v >
< div style = "margin-left: 10px;" >
< div style = "width: 120px; height: 20px; background-color: #ccc; margin-bottom: 5px;" > < / d i v >
< div style = "width: 80px; height: 15px; background-color: #ddd;" > < / d i v >
< / d i v >
< / d i v >
< div style = "margin-top: 10px;" >
2024-12-20 22:07:18 -08:00
< div style = "width: 100%; height: 80px; background-color: #eee; color:rgb(17, 24, 28); padding: 0.22vh" > < p > PLEASE BE PATIENT < / p > < p s t y l e = " c o l o r : # 1 1 1 2 1 c " > W h i l e d a t a l o a d s f r o m Q D N . . . < / d i v >
2024-12-16 19:53:37 -08:00
< / d i v >
< / d i v >
2024-12-30 21:39:18 -08:00
`
}
2024-12-14 19:40:31 -08:00
2024-12-12 17:23:34 -08:00
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
const fetchExistingCard = async ( ) => {
2024-12-11 14:40:32 -08:00
try {
2025-01-01 10:47:37 -08:00
const response = await searchSimple ( 'BLOG_POST' , ` ${ cardIdentifierPrefix } ` , ` ${ userState . accountName } ` , 0 , 0 , '' , true )
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
console . log ( ` SEARCH_QDN_RESOURCES response: ${ JSON . stringify ( response , null , 2 ) } ` )
2024-12-11 14:40:32 -08:00
if ( ! response || ! Array . isArray ( response ) || response . length === 0 ) {
2024-12-28 22:49:18 -08:00
console . log ( "No cards found for the current user." )
return null
2024-12-27 23:04:16 -08:00
} else if ( response . length === 1 ) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it.
2025-01-01 10:47:37 -08:00
const mostRecentCard = response [ 0 ]
const cardDataResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : userState . accountName , // User's account name
service : "BLOG_POST" ,
identifier : mostRecentCard . identifier
} )
existingCardIdentifier = mostRecentCard . identifier
existingCardData = cardDataResponse
return cardDataResponse
2024-12-11 14:40:32 -08:00
}
2024-12-12 17:23:34 -08:00
const validatedCards = await Promise . all (
response . map ( async card => {
2024-12-28 22:49:18 -08:00
const isValid = await validateCardStructure ( card )
return isValid ? card : null
2024-12-12 17:23:34 -08:00
} )
2024-12-28 22:49:18 -08:00
)
2024-12-12 17:23:34 -08:00
2024-12-28 22:49:18 -08:00
const validCards = validatedCards . filter ( card => card !== null )
2024-12-11 14:40:32 -08:00
if ( validCards . length > 0 ) {
2024-12-30 21:39:18 -08:00
2024-12-28 22:49:18 -08:00
const mostRecentCard = validCards . sort ( ( a , b ) => b . created - a . created ) [ 0 ]
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
const cardDataResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : userState . accountName , // User's account name
service : "BLOG_POST" ,
identifier : mostRecentCard . identifier
2024-12-28 22:49:18 -08:00
} )
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
existingCardIdentifier = mostRecentCard . identifier
existingCardData = cardDataResponse
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
console . log ( "Full card data fetched successfully:" , cardDataResponse )
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
return cardDataResponse
2024-12-12 17:23:34 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
console . log ( "No valid cards found." )
return null
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2024-12-28 22:49:18 -08:00
console . error ( "Error fetching existing card:" , error )
return null
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
}
2024-12-12 17:23:34 -08:00
// Validate that a card is indeed a card and not a comment. -------------------------------------
const validateCardStructure = async ( card ) => {
2024-12-11 14:40:32 -08:00
return (
typeof card === "object" &&
card . name &&
card . service === "BLOG_POST" &&
card . identifier && ! card . identifier . includes ( "comment" ) &&
card . created
2024-12-28 22:49:18 -08:00
)
2024-12-11 14:40:32 -08:00
}
2024-12-12 17:23:34 -08:00
// Load existing card data passed, into the form for editing -------------------------------------
const loadCardIntoForm = async ( cardData ) => {
2024-12-28 22:49:18 -08:00
console . log ( "Loading existing card data:" , cardData )
document . getElementById ( "card-header" ) . value = cardData . header
document . getElementById ( "card-content" ) . value = cardData . content
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
const linksContainer = document . getElementById ( "links-container" )
linksContainer . innerHTML = ""
2024-12-11 14:40:32 -08:00
cardData . links . forEach ( link => {
2024-12-28 22:49:18 -08:00
const linkInput = document . createElement ( "input" )
linkInput . type = "text"
linkInput . className = "card-link"
2024-12-11 14:40:32 -08:00
linkInput . value = link ;
2025-01-01 13:47:45 -08:00
linksContainer . appendChild ( linkInput )
2024-12-28 22:49:18 -08:00
} )
2024-12-11 14:40:32 -08:00
}
2024-12-12 17:23:34 -08:00
// Main function to publish a new Minter Card -----------------------------------------------
const publishCard = async ( ) => {
2024-12-28 22:49:18 -08:00
2024-12-30 21:39:18 -08:00
const minterGroupData = await fetchMinterGroupMembers ( )
const minterGroupAddresses = minterGroupData . map ( m => m . member )
2024-12-28 22:49:18 -08:00
const userAddress = userState . accountAddress ;
if ( minterGroupAddresses . includes ( userAddress ) ) {
2024-12-30 21:39:18 -08:00
alert ( "You are already a Minter and cannot publish a new card!" )
2025-01-01 13:47:45 -08:00
return
2024-12-28 22:49:18 -08:00
}
2024-12-30 21:39:18 -08:00
const header = document . getElementById ( "card-header" ) . value . trim ( )
const content = document . getElementById ( "card-content" ) . value . trim ( )
2024-12-11 14:40:32 -08:00
const links = Array . from ( document . querySelectorAll ( ".card-link" ) )
. map ( input => input . value . trim ( ) )
2024-12-28 22:49:18 -08:00
. filter ( link => link . startsWith ( "qortal://" ) )
2024-12-11 14:40:32 -08:00
if ( ! header || ! content ) {
2024-12-28 22:49:18 -08:00
alert ( "Header and content are required!" )
return
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
const cardIdentifier = isExistingCard ? existingCardIdentifier : ` ${ cardIdentifierPrefix } - ${ await uid ( ) } `
const pollName = ` ${ cardIdentifier } -poll `
const pollDescription = ` Mintership Board Poll for ${ userState . accountName } `
2024-12-11 14:40:32 -08:00
const cardData = {
header ,
content ,
links ,
creator : userState . accountName ,
timestamp : Date . now ( ) ,
poll : pollName ,
2024-12-28 22:49:18 -08:00
}
2024-12-12 17:23:34 -08:00
2024-12-11 14:40:32 -08:00
try {
2024-12-28 22:49:18 -08:00
let base64CardData = await objectToBase64 ( cardData )
2024-12-11 14:40:32 -08:00
if ( ! base64CardData ) {
2024-12-28 22:49:18 -08:00
console . log ( ` initial base64 object creation with objectToBase64 failed, using btoa... ` )
base64CardData = btoa ( JSON . stringify ( cardData ) )
2024-12-11 14:40:32 -08:00
}
await qortalRequest ( {
action : "PUBLISH_QDN_RESOURCE" ,
name : userState . accountName ,
service : "BLOG_POST" ,
identifier : cardIdentifier ,
data64 : base64CardData ,
2024-12-28 22:49:18 -08:00
} )
2024-12-11 16:41:20 -08:00
if ( ! isExistingCard ) {
await qortalRequest ( {
action : "CREATE_POLL" ,
pollName ,
pollDescription ,
pollOptions : [ 'Yes, No' ] ,
pollOwnerAddress : userState . accountAddress ,
2024-12-28 22:49:18 -08:00
} )
alert ( "Card and poll published successfully!" )
2024-12-11 16:41:20 -08:00
}
2024-12-28 22:49:18 -08:00
2024-12-11 16:41:20 -08:00
if ( isExistingCard ) {
alert ( "Card Updated Successfully! (No poll updates are possible at this time...)" )
}
2024-12-28 22:49:18 -08:00
document . getElementById ( "publish-card-form" ) . reset ( )
document . getElementById ( "publish-card-view" ) . style . display = "none"
document . getElementById ( "cards-container" ) . style . display = "flex"
await loadCards ( )
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2024-12-28 22:49:18 -08:00
console . error ( "Error publishing card or poll:" , error )
alert ( "Failed to publish card and poll." )
2024-12-11 14:40:32 -08:00
}
}
2025-01-01 13:47:45 -08:00
let globalVoterMap = new Map ( )
const processPollData = async ( pollData , minterGroupMembers , minterAdmins , creator , cardIdentifier ) => {
2024-12-28 22:49:18 -08:00
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> `
}
}
const memberAddresses = minterGroupMembers . map ( m => m . member )
const minterAdminAddresses = minterAdmins . map ( m => m . member )
2024-12-14 19:40:31 -08:00
const adminGroupsMembers = await fetchAllAdminGroupsMembers ( )
2024-12-28 22:49:18 -08:00
const groupAdminAddresses = adminGroupsMembers . map ( m => m . member )
const adminAddresses = [ ... minterAdminAddresses , ... groupAdminAddresses ]
let adminYes = 0 , adminNo = 0
let minterYes = 0 , minterNo = 0
let yesWeight = 0 , noWeight = 0
for ( const w of pollData . voteWeights ) {
if ( w . optionName . toLowerCase ( ) === 'yes' ) {
yesWeight = w . voteWeight
} else if ( w . optionName . toLowerCase ( ) === 'no' ) {
noWeight = w . voteWeight
}
}
2024-12-11 17:44:04 -08:00
2024-12-28 22:49:18 -08:00
const voterPromises = pollData . votes . map ( async ( vote ) => {
const optionIndex = vote . optionIndex ; // 0 => yes, 1 => no
const voterPublicKey = vote . voterPublicKey
const voterAddress = await getAddressFromPublicKey ( voterPublicKey )
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
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. ` )
}
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
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 )
}
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
let blocksMinted = 0
try {
const addressInfo = await getAddressInfo ( voterAddress )
blocksMinted = addressInfo ? . blocksMinted || 0
} catch ( e ) {
console . warn ( ` Failed to get addressInfo for ${ voterAddress } ` , e )
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
const isAdmin = adminAddresses . includes ( voterAddress )
const isMinter = memberAddresses . includes ( voterAddress )
return {
optionIndex ,
voterPublicKey ,
voterAddress ,
voterName ,
isAdmin ,
isMinter ,
blocksMinted
}
} )
2025-01-01 13:47:45 -08:00
//TODO verify this new voterPromises async function works better.
// const voterPromises = pollData.votes.map(async (vote) => {
// const voterPublicKey = vote.voterPublicKey;
// const voterAddress = await getAddressFromPublicKey(voterPublicKey);
// const [nameInfo, addressInfo] = await Promise.all([
// getNameFromAddress(voterAddress).catch(() => ""),
// getAddressInfo(voterAddress).catch(() => ({})),
// ]);
// const voterName = nameInfo || (nameInfo === voterAddress ? "" : voterAddress);
// const blocksMinted = addressInfo?.blocksMinted || 0;
// return {
// optionIndex: vote.optionIndex,
// voterPublicKey,
// voterAddress,
// voterName,
// isAdmin: adminAddresses.includes(voterAddress),
// isMinter: memberAddresses.includes(voterAddress),
// blocksMinted,
// };
// });
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
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
}
}
2024-12-11 14:40:32 -08:00
2025-01-01 13:47:45 -08:00
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 )
2024-12-28 22:49:18 -08:00
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 < / h 1 > < h 3 s t y l e = " c o l o r : w h i t e ; t e x t - a l i g n : c e n t e r ; f o n t - s i z e : 1 . 8 r e m " > S u p p o r t P o l l R e s u l t D e t a i l s < / h 3 >
< h4 style = "color: green; text-align: center;" > Yes Vote Details < / h 4 >
$ { yesTableHtml }
< h4 style = "color: red; text-align: center; margin-top: 2em;" > No Vote Details < / h 4 >
$ { noTableHtml }
< / d i v >
`
2024-12-12 17:23:34 -08:00
const totalYes = adminYes + minterYes
const totalNo = adminNo + minterNo
2024-12-28 22:49:18 -08:00
return {
adminYes ,
adminNo ,
minterYes ,
minterNo ,
totalYes ,
totalNo ,
totalYesWeight : totalMinterAndAdminYesWeight ,
totalNoWeight : totalMinterAndAdminNoWeight ,
detailsHtml
}
}
2025-01-01 13:47:45 -08:00
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 )
}
2024-12-28 22:49:18 -08:00
const buildVotersTableHtml = ( voters , tableColor ) => {
if ( ! voters . length ) {
2025-01-01 13:47:45 -08:00
return ` <p>No voters here.</p> `
2024-12-28 22:49:18 -08:00
}
// Decide extremely dark background for the <tbody>
2025-01-01 13:47:45 -08:00
let bodyBackground
2024-12-28 22:49:18 -08:00
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.15 rem ;
border - color : # 576 b6f ;
margin - bottom : 1 em ;
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 < / t h >
< th style = "padding: 0.1rem; text-align: center;" > Voter Type < / t h >
< th style = "padding: 0.1rem; text-align: center;" > Voter Weight ( = BlocksMinted ) < / t h >
< / t r >
< / t h e a d >
<!-- Tbody with extremely dark green or red -- >
< tbody style = "background-color: ${bodyBackground}; color: #c6c6c6;" >
$ { voters
. map ( v => {
2025-01-01 13:47:45 -08:00
const userType = v . isAdmin ? "Admin" : v . isMinter ? "Minter" : "User"
2024-12-28 22:49:18 -08:00
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.2 rem ; border - width : 0.1 rem ; border - style : dotted ; border - color : lightgrey ; text - align : center ;
color : $ { userType === 'Admin' ? adminColor : v . isMinter ? minterColor : userColor } ; " > $ { displayName } < / t d >
< td style = " padding : 1.2 rem ; border - width : 0.1 rem ; border - style : dotted ; border - color : lightgrey ; text - align : center ;
color : $ { userType === 'Admin' ? adminColor : v . isMinter ? minterColor : userColor } ; " > $ { userType } < / t d >
< td style = " padding : 1.2 rem ; border - width : 0.1 rem ; border - style : dotted ; border - color : lightgrey ; text - align : center ;
color : $ { userType === 'Admin' ? adminColor : v . isMinter ? minterColor : userColor } ; " > $ { v . blocksMinted } < / t d >
< / t r >
`
} )
. join ( "" ) }
< / t b o d y >
< / t a b l e >
`
2024-12-12 17:23:34 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-28 22:49:18 -08:00
2024-12-12 17:23:34 -08:00
// Post a comment on a card. ---------------------------------
2024-12-11 14:40:32 -08:00
const postComment = async ( cardIdentifier ) => {
2024-12-28 22:49:18 -08:00
const commentInput = document . getElementById ( ` new-comment- ${ cardIdentifier } ` )
const commentText = commentInput . value . trim ( )
2024-12-11 14:40:32 -08:00
if ( ! commentText ) {
2024-12-28 22:49:18 -08:00
alert ( 'Comment cannot be empty!' )
return
2024-12-11 14:40:32 -08:00
}
const commentData = {
content : commentText ,
creator : userState . accountName ,
timestamp : Date . now ( ) ,
2024-12-28 22:49:18 -08:00
}
const commentIdentifier = ` comment- ${ cardIdentifier } - ${ await uid ( ) } `
2024-12-11 14:40:32 -08:00
try {
2024-12-28 22:49:18 -08:00
const base64CommentData = await objectToBase64 ( commentData )
if ( ! base64CommentData ) {
console . log ( ` initial base64 object creation with objectToBase64 failed, using btoa... ` )
base64CommentData = btoa ( JSON . stringify ( commentData ) )
}
2024-12-11 14:40:32 -08:00
await qortalRequest ( {
action : 'PUBLISH_QDN_RESOURCE' ,
name : userState . accountName ,
service : 'BLOG_POST' ,
identifier : commentIdentifier ,
data64 : base64CommentData ,
2024-12-28 22:49:18 -08:00
} )
2024-12-11 16:41:20 -08:00
2024-12-28 22:49:18 -08:00
alert ( 'Comment posted successfully!' )
commentInput . value = ''
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2024-12-28 22:49:18 -08:00
console . error ( 'Error posting comment:' , error )
alert ( 'Failed to post comment.' )
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
//Fetch the comments for a card with passed card identifier ----------------------------
2024-12-11 14:40:32 -08:00
const fetchCommentsForCard = async ( cardIdentifier ) => {
try {
2024-12-28 22:49:18 -08:00
const response = await searchSimple ( 'BLOG_POST' , ` comment- ${ cardIdentifier } ` , '' , 0 , 0 , '' , 'false' )
2024-12-27 23:04:16 -08:00
return response
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2024-12-27 23:04:16 -08:00
console . error ( ` Error fetching comments for ${ cardIdentifier } : ` , error )
return [ ]
2024-12-11 14:40:32 -08:00
}
2024-12-27 23:04:16 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
// display the comments on the card, with passed cardIdentifier to identify the card --------------
2025-01-01 13:47:45 -08:00
// const displayComments = async (cardIdentifier) => {
// try {
// const comments = await fetchCommentsForCard(cardIdentifier);
// const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`)
// for (const comment of comments) {
// const commentDataResponse = await qortalRequest({
// action: "FETCH_QDN_RESOURCE",
// name: comment.name,
// service: "BLOG_POST",
// identifier: comment.identifier,
// })
// const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp)
// const commentHTML = `
// <div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
// <p><strong><u>${commentDataResponse.creator}</strong>:</p></u>
// <p>${commentDataResponse.content}</p>
// <p><i>${timestamp}</p></i>
// </div>
// `
// commentsContainer.insertAdjacentHTML('beforeend', commentHTML)
// }
// } catch (error) {
// console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error)
// }
// }
2024-12-11 14:40:32 -08:00
const displayComments = async ( cardIdentifier ) => {
try {
2025-01-01 13:47:45 -08:00
const comments = await fetchCommentsForCard ( cardIdentifier )
2024-12-28 22:49:18 -08:00
const commentsContainer = document . getElementById ( ` comments-container- ${ cardIdentifier } ` )
2025-01-01 13:47:45 -08:00
commentsContainer . innerHTML = ''
const voterMap = globalVoterMap . get ( cardIdentifier ) || new Map ( )
const commentHTMLArray = await Promise . all (
comments . map ( async ( comment ) => {
try {
const commentDataResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : comment . name ,
service : "BLOG_POST" ,
identifier : comment . identifier ,
} )
const timestamp = await timestampToHumanReadableDate ( commentDataResponse . timestamp ) ;
const commenter = commentDataResponse . creator
const voterInfo = voterMap . get ( commenter )
let commentColor = "transparent"
let adminBadge = ""
if ( voterInfo ) {
if ( voterInfo . voterType === "Admin" ) {
commentColor = voterInfo . vote === "yes" ? "rgba(21, 150, 21, 0.6)" : "rgba(212, 37, 64, 0.6)" // Light green for yes, light red for no
const badgeColor = voterInfo . vote === "yes" ? "green" : "red"
adminBadge = ` <span style="color: ${ badgeColor } ; font-weight: bold; margin-left: 0.5em;">(Admin)</span> `
} else {
commentColor = voterInfo . vote === "yes" ? "rgba(0, 100, 0, 0.3)" : "rgba(100, 0, 0, 0.3)" // Darker green for yes, darker red for no
}
}
return `
< div class = "comment" style = "border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: ${commentColor};" >
< p >
< strong > < u > $ { commentDataResponse . creator } < / u > < / s t r o n g >
$ { adminBadge }
< / p >
< p > $ { commentDataResponse . content } < / p >
< p > < i > $ { timestamp } < / i > < / p >
< / d i v >
`
} catch ( err ) {
console . error ( ` Error processing comment ${ comment . identifier } : ` , err )
return null
}
2024-12-28 22:49:18 -08:00
} )
2025-01-01 13:47:45 -08:00
)
2024-12-28 22:49:18 -08:00
2025-01-01 13:47:45 -08:00
commentHTMLArray
. filter ( ( html ) => html !== null ) // Filter out failed comments
. forEach ( ( commentHTML ) => {
commentsContainer . insertAdjacentHTML ( 'beforeend' , commentHTML )
} )
2024-12-11 14:40:32 -08:00
} catch ( error ) {
2024-12-28 22:49:18 -08:00
console . error ( ` Error displaying comments (or no comments) for ${ cardIdentifier } : ` , error )
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-12 17:23:34 -08:00
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
2024-12-11 14:40:32 -08:00
const toggleComments = async ( cardIdentifier ) => {
2024-12-28 22:49:18 -08:00
const commentsSection = document . getElementById ( ` comments-section- ${ cardIdentifier } ` )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
const commentButton = document . getElementById ( ` comment-button- ${ cardIdentifier } ` )
2024-12-28 22:49:18 -08:00
if ( ! commentsSection || ! commentButton ) return
const count = commentButton . dataset . commentCount
const isHidden = ( commentsSection . style . display === 'none' || ! commentsSection . style . display )
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
if ( isHidden ) {
// Show comments
2024-12-28 22:49:18 -08:00
commentButton . textContent = "LOADING..."
2024-12-30 21:39:18 -08:00
await displayComments ( cardIdentifier )
2024-12-28 22:49:18 -08:00
commentsSection . style . display = 'block'
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
// Change the button text to 'HIDE COMMENTS'
2024-12-28 22:49:18 -08:00
commentButton . textContent = 'HIDE COMMENTS'
2024-12-11 14:40:32 -08:00
} else {
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
// Hide comments
2024-12-28 22:49:18 -08:00
commentsSection . style . display = 'none'
commentButton . textContent = ` COMMENTS ( ${ count } ) `
2024-12-11 14:40:32 -08:00
}
2024-12-28 22:49:18 -08:00
}
2024-12-11 14:40:32 -08:00
2024-12-20 22:07:18 -08:00
const countComments = async ( cardIdentifier ) => {
try {
2024-12-28 22:49:18 -08:00
const response = await searchSimple ( 'BLOG_POST' , ` comment- ${ cardIdentifier } ` , '' , 0 , 0 , '' , 'false' )
return Array . isArray ( response ) ? response . length : 0
2024-12-20 22:07:18 -08:00
} catch ( error ) {
2024-12-28 22:49:18 -08:00
console . error ( ` Error fetching comment count for ${ cardIdentifier } : ` , error )
return 0
2024-12-20 22:07:18 -08:00
}
2024-12-28 22:49:18 -08:00
}
const createModal = ( modalType = '' ) => {
if ( document . getElementById ( ` ${ modalType } -modal ` ) ) {
return
}
const isIframe = ( modalType === 'links' )
2024-12-20 22:07:18 -08:00
2024-12-12 17:23:34 -08:00
const modalHTML = `
2024-12-28 22:49:18 -08:00
< div id = "${modalType}-modal"
style = " display : none ;
position : fixed ;
top : 0 ; left : 0 ;
width : 100 % ; height : 100 % ;
background : rgba ( 0 , 0 , 0 , 0.50 ) ;
z - index : 10000 ; " >
< div id = "${modalType}-modalContainer"
style = " position : relative ;
margin : 10 % auto ;
width : 80 % ;
height : 70 % ;
background : rgba ( 0 , 0 , 0 , 0.80 ) ;
border - radius : 10 px ;
overflow : hidden ; " >
$ {
isIframe
? ` <iframe id=" ${ modalType } -modalContent"
src = ""
style = "width: 100%; height: 100%; border: none;" >
< / i f r a m e > `
: ` <div id=" ${ modalType } -modalContent"
style = "width: 100%; height: 100%; overflow: auto;" >
< / d i v > `
}
< button onclick = "closeModal('${modalType}')"
style = " position : absolute ; top : 0.2 rem ; right : 0.2 rem ;
background : rgba ( 0 , 0 , 0 , 0.66 ) ; color : white ; border : none ;
font - size : 2.2 rem ;
padding : 0.4 rem 1 rem ;
border - radius : 0.33 rem ;
border - style : dashed ;
border - color : rgb ( 213 , 224 , 225 ) ;
"
onmouseover = "this.style.backgroundColor='rgb(73, 7, 7) '"
onmouseout = "this.style.backgroundColor='rgba(5, 14, 11, 0.63) '" >
X
< / b u t t o n >
2024-12-12 17:23:34 -08:00
< / d i v >
< / d i v >
2024-12-28 22:49:18 -08:00
`
document . body . insertAdjacentHTML ( 'beforeend' , modalHTML )
const modal = document . getElementById ( ` ${ modalType } -modal ` )
window . addEventListener ( 'click' , ( event ) => {
if ( event . target === modal ) {
closeModal ( modalType )
}
} )
2024-12-12 17:23:34 -08:00
}
2024-12-28 22:49:18 -08:00
const openLinksModal = async ( link ) => {
const processedLink = await processLink ( link )
const modal = document . getElementById ( 'links-modal' )
const modalContent = document . getElementById ( 'links-modalContent' )
modalContent . src = processedLink
modal . style . display = 'block'
2024-12-12 17:23:34 -08:00
}
2024-12-28 22:49:18 -08:00
const closeModal = async ( modalType = 'links' ) => {
const modal = document . getElementById ( ` ${ modalType } -modal ` )
const modalContent = document . getElementById ( ` ${ modalType } -modalContent ` )
if ( modal ) {
modal . style . display = 'none'
}
if ( modalContent ) {
modalContent . src = ''
}
2024-12-12 17:23:34 -08:00
}
const processLink = async ( link ) => {
if ( link . startsWith ( 'qortal://' ) ) {
2024-12-28 22:49:18 -08:00
const match = link . match ( /^qortal:\/\/([^/]+)(\/.*)?$/ )
2024-12-12 21:49:14 -08:00
if ( match ) {
2024-12-28 22:49:18 -08:00
const firstParam = match [ 1 ] . toUpperCase ( )
const remainingPath = match [ 2 ] || ""
const themeColor = window . _qdnTheme || 'default'
await new Promise ( resolve => setTimeout ( resolve , 10 ) )
return ` /render/ ${ firstParam } ${ remainingPath } ?theme= ${ themeColor } `
2024-12-12 21:49:14 -08:00
}
2024-12-12 17:23:34 -08:00
}
2024-12-28 22:49:18 -08:00
return link
}
const togglePollDetails = ( cardIdentifier ) => {
const detailsDiv = document . getElementById ( ` poll-details- ${ cardIdentifier } ` )
const modal = document . getElementById ( ` poll-details-modal ` )
const modalContent = document . getElementById ( ` poll-details-modalContent ` )
if ( ! detailsDiv || ! modal || ! modalContent ) return
// modalContent.appendChild(detailsDiv)
modalContent . innerHTML = detailsDiv . innerHTML
modal . style . display = 'block'
window . onclick = ( event ) => {
if ( event . target === modal ) {
modal . style . display = 'none'
}
}
}
2024-12-20 22:07:18 -08:00
const generateDarkPastelBackgroundBy = ( name ) => {
2024-12-28 22:49:18 -08:00
let hash = 0
2024-12-20 22:07:18 -08:00
for ( let i = 0 ; i < name . length ; i ++ ) {
2024-12-28 22:49:18 -08:00
hash = ( hash << 5 ) - hash + name . charCodeAt ( i )
hash |= 0
2024-12-20 22:07:18 -08:00
}
2024-12-28 22:49:18 -08:00
const safeHash = Math . abs ( hash )
const hueSteps = 69.69
const hueIndex = safeHash % hueSteps
const hueRange = 288
const hue = 140 + ( hueIndex * ( hueRange / hueSteps ) )
2024-12-20 22:07:18 -08:00
2024-12-28 22:49:18 -08:00
const satSteps = 13.69
const satIndex = safeHash % satSteps
const saturation = 18 + ( satIndex * 1.333 )
2024-12-20 22:07:18 -08:00
2024-12-28 22:49:18 -08:00
const lightSteps = 3.69
const lightIndex = safeHash % lightSteps
const lightness = 7 + lightIndex
2024-12-12 17:23:34 -08:00
2024-12-28 22:49:18 -08:00
return ` hsl( ${ hue } , ${ saturation } %, ${ lightness } %) `
}
2024-12-12 17:23:34 -08:00
// Create the overall Minter Card HTML -----------------------------------------------
2024-12-24 00:27:17 -08:00
const createCardHTML = async ( cardData , pollResults , cardIdentifier , commentCount , cardUpdatedTime , BgColor ) => {
2025-01-01 13:47:45 -08:00
const { header , content , links , creator , timestamp , poll } = cardData
2024-12-24 00:27:17 -08:00
const formattedDate = cardUpdatedTime ? new Date ( cardUpdatedTime ) . toLocaleString ( ) : new Date ( timestamp ) . toLocaleString ( )
2024-12-20 22:07:18 -08:00
const avatarHtml = await getMinterAvatar ( creator )
2024-12-11 14:40:32 -08:00
const linksHTML = links . map ( ( link , index ) => `
2024-12-28 22:49:18 -08:00
< button onclick = "openLinksModal('${link}')" >
2024-12-11 14:40:32 -08:00
$ { ` Link ${ index + 1 } - ${ link } ` }
< / b u t t o n >
2024-12-28 22:49:18 -08:00
` ).join("")
const minterGroupMembers = await fetchMinterGroupMembers ( )
const minterAdmins = await fetchMinterGroupAdmins ( )
2025-01-01 13:47:45 -08:00
const { adminYes = 0 , adminNo = 0 , minterYes = 0 , minterNo = 0 , totalYes = 0 , totalNo = 0 , totalYesWeight = 0 , totalNoWeight = 0 , detailsHtml } = await processPollData ( pollResults , minterGroupMembers , minterAdmins , creator , cardIdentifier )
2024-12-28 22:49:18 -08:00
createModal ( 'links' )
createModal ( 'poll-details' )
2024-12-11 14:40:32 -08:00
return `
2024-12-20 22:07:18 -08:00
< div class = "minter-card" style = "background-color: ${BgColor}" >
2024-12-11 14:40:32 -08:00
< div class = "minter-card-header" >
2024-12-20 22:07:18 -08:00
$ { avatarHtml }
2024-12-11 14:40:32 -08:00
< h3 > $ { creator } < / h 3 >
< p > $ { header } < / p >
< / d i v >
2024-12-20 22:07:18 -08:00
< div class = "support-header" > < h5 > MINTER ' S POST < / h 5 > < / d i v >
2024-12-11 14:40:32 -08:00
< div class = "info" >
$ { content }
< / d i v >
2024-12-20 22:07:18 -08:00
< div class = "support-header" > < h5 > MINTER ' S LINKS < / h 5 > < / d i v >
2024-12-11 14:40:32 -08:00
< div class = "info-links" >
$ { linksHTML }
< / d i v >
2024-12-20 22:07:18 -08:00
< div class = "results-header support-header" > < h5 > CURRENT RESULTS < / h 5 > < / d i v >
2024-12-11 14:40:32 -08:00
< div class = "minter-card-results" >
2024-12-28 22:49:18 -08:00
< button onclick = "togglePollDetails('${cardIdentifier}')" > Display Poll Details < / b u t t o n >
< div id = "poll-details-${cardIdentifier}" style = "display: none;" >
$ { detailsHtml }
< / d i v >
2024-12-11 14:40:32 -08:00
< div class = "admin-results" >
< span class = "admin-yes" > Admin Yes : $ { adminYes } < / s p a n >
< span class = "admin-no" > Admin No : $ { adminNo } < / s p a n >
< / d i v >
< div class = "minter-results" >
< span class = "minter-yes" > Minter Yes : $ { minterYes } < / s p a n >
< span class = "minter-no" > Minter No : $ { minterNo } < / s p a n >
< / d i v >
< div class = "total-results" >
< span class = "total-yes" > Total Yes : $ { totalYes } < / s p a n >
2024-12-20 22:07:18 -08:00
< span class = "total-yes" > Weight : $ { totalYesWeight } < / s p a n >
2024-12-11 14:40:32 -08:00
< span class = "total-no" > Total No : $ { totalNo } < / s p a n >
2024-12-20 22:07:18 -08:00
< span class = "total-no" > Weight : $ { totalNoWeight } < / s p a n >
2024-12-11 14:40:32 -08:00
< / d i v >
< / d i v >
2024-12-20 22:07:18 -08:00
< div class = "support-header" > < h5 > SUPPORT < / h 5 > < h 5 s t y l e = " c o l o r : # f f a e 4 2 ; " > $ { c r e a t o r } < / h 5 >
< p style = "color: #c7c7c7; font-size: .65rem; margin-top: 1vh" > ( click COMMENTS button to open / close card comments ) < / p >
< / d i v >
2024-12-11 14:40:32 -08:00
< div class = "actions" >
< div class = "actions-buttons" >
2024-12-11 18:32:48 -08:00
< button class = "yes" onclick = "voteYesOnPoll('${poll}')" > YES < / b u t t o n >
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-26 20:06:51 -08:00
< button class = "comment" id = "comment-button-${cardIdentifier}" data - comment - count = "${commentCount}" onclick = "toggleComments('${cardIdentifier}')" > COMMENTS ( $ { commentCount } ) < / b u t t o n >
2024-12-11 18:32:48 -08:00
< button class = "no" onclick = "voteNoOnPoll('${poll}')" > NO < / b u t t o n >
2024-12-11 14:40:32 -08:00
< / d i v >
< / d i v >
< div id = "comments-section-${cardIdentifier}" class = "comments-section" style = "display: none; margin-top: 20px;" >
< div id = "comments-container-${cardIdentifier}" class = "comments-container" > < / d i v >
< textarea id = "new-comment-${cardIdentifier}" placeholder = "Write a comment..." style = "width: 100%; margin-top: 10px;" > < / t e x t a r e a >
< button onclick = "postComment('${cardIdentifier}')" > Post Comment < / b u t t o n >
< / d i v >
2024-12-20 22:07:18 -08:00
< p style = "font-size: 0.75rem; margin-top: 3vh; color: #4496a1" > By : $ { creator } - $ { formattedDate } < / p >
2024-12-11 14:40:32 -08:00
< / d i v >
2025-01-01 13:47:45 -08:00
`
2024-12-11 14:40:32 -08:00
}