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 = { }
2025-01-04 20:28:26 -08:00
const MIN _ADMIN _YES _VOTES = 9 ;
2025-01-13 15:53:20 -08:00
const GROUP _APPROVAL _FEATURE _TRIGGER _HEIGHT = 9999950 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
let featureTriggerPassed = false
2025-01-04 20:28:26 -08:00
let isApproved = false
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" >
2025-01-04 20:28:26 -08:00
< h3 > Create or Update Your Card < / h 3 >
2024-12-11 14:40:32 -08:00
< 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 )
2025-01-04 20:28:26 -08:00
createScrollToTopButton ( )
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 ( )
} )
2025-01-13 15:53:20 -08:00
await featureTriggerCheck ( )
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 )
2025-01-08 20:26:24 -08:00
const bgColor = generateDarkPastelBackgroundBy ( card . name )
2024-12-20 22:07:18 -08:00
const commentCount = await countComments ( card . identifier )
2024-12-24 00:27:17 -08:00
const cardUpdatedTime = card . updated || null
2025-01-08 20:26:24 -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 ]
2025-01-04 20:28:26 -08:00
isExistingCard = true
2025-01-01 10:47:37 -08:00
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
2025-01-04 20:28:26 -08:00
isExistingCard = true
2025-01-01 10:47:37 -08:00
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
2025-01-04 20:28:26 -08:00
isExistingCard = true
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...)" )
2025-01-04 20:28:26 -08:00
isExistingCard = false
2024-12-11 16:41:20 -08:00
}
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 ( )
2025-01-13 15:53:20 -08:00
const featureTriggerPassed = await featureTriggerCheck ( )
2024-12-28 22:49:18 -08:00
const groupAdminAddresses = adminGroupsMembers . map ( m => m . member )
2025-01-13 15:53:20 -08:00
let adminAddresses = [ ... minterAdminAddresses ]
if ( ! featureTriggerPassed ) {
console . log ( ` featureTrigger is NOT passed, only showing admin results from Minter Admins and Group Admins ` )
adminAddresses = [ ... minterAdminAddresses , ... groupAdminAddresses ]
}
2024-12-28 22:49:18 -08:00
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
}
} )
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
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
}
2025-01-04 20:28:26 -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
2025-01-04 20:28:26 -08:00
const handleInviteMinter = async ( minterName ) => {
try {
const blockInfo = await getLatestBlockInfo ( )
2025-01-06 18:43:54 -08:00
const blockHeight = blockInfo . height
2025-01-04 20:28:26 -08:00
const minterAccountInfo = await getNameInfo ( minterName )
const minterAddress = await minterAccountInfo . owner
2025-01-08 20:26:24 -08:00
let adminPublicKey
let txGroupId
if ( blockHeight >= GROUP _APPROVAL _FEATURE _TRIGGER _HEIGHT ) {
if ( userState . isMinterAdmin ) {
adminPublicKey = await getPublicKeyByName ( userState . accountName )
txGroupId = 694
} else {
console . warn ( ` user is not a minter admin, cannot create invite! ` )
return
}
} else {
adminPublicKey = await getPublicKeyByName ( userState . accountName )
txGroupId = 0
}
const fee = 0.01
const timeToLive = 864000
2025-01-04 20:28:26 -08:00
2025-01-08 20:26:24 -08:00
console . log ( ` about to attempt group invite, minterAddress: ${ minterAddress } , adminPublicKey: ${ adminPublicKey } ` )
const inviteTransaction = await createGroupInviteTransaction ( minterAddress , adminPublicKey , 694 , minterAddress , timeToLive , txGroupId , fee )
2025-01-04 20:28:26 -08:00
const signedTransaction = await qortalRequest ( {
action : "SIGN_TRANSACTION" ,
unsignedBytes : inviteTransaction
} )
console . warn ( ` signed transaction ` , signedTransaction )
const processResponse = await processTransaction ( signedTransaction )
2025-01-08 20:26:24 -08:00
if ( typeof processResponse === 'object' ) {
// The successful object might have a "signature" or "type" or "approvalStatus"
console . log ( "Invite transaction success object:" , processResponse )
alert ( ` ${ minterName } has been successfully invited! Wait for confirmation...Transaction Response: ${ JSON . stringify ( processResponse ) } ` )
2025-01-04 20:28:26 -08:00
} else {
2025-01-08 20:26:24 -08:00
// fallback string or something
console . log ( "Invite transaction raw text response:" , processResponse )
alert ( ` Invite transaction response: ${ JSON . stringify ( processResponse ) } ` )
2025-01-04 20:28:26 -08:00
}
2025-01-08 20:26:24 -08:00
2025-01-04 20:28:26 -08:00
} catch ( error ) {
console . error ( "Error inviting minter:" , error )
alert ( "Error inviting minter. Please try again." )
}
}
const createInviteButtonHtml = ( creator , cardIdentifier ) => {
return `
< div id = "invite-button-container-${cardIdentifier}" style = "margin-top: 1em;" >
< button onclick = "handleInviteMinter('${creator}')"
2025-01-13 15:53:20 -08:00
style = "padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;"
2025-01-04 20:28:26 -08:00
onmouseover = "this.style.backgroundColor='rgb(25, 47, 39) '"
onmouseout = "this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
>
2025-01-13 15:53:20 -08:00
Create Minter Invite
2025-01-04 20:28:26 -08:00
< / b u t t o n >
< / d i v >
`
}
2025-01-13 15:53:20 -08:00
const featureTriggerCheck = async ( ) => {
2025-01-04 20:28:26 -08:00
const latestBlockInfo = await getLatestBlockInfo ( )
2025-01-06 18:43:54 -08:00
const isBlockPassed = latestBlockInfo . height >= GROUP _APPROVAL _FEATURE _TRIGGER _HEIGHT
2025-01-13 15:53:20 -08:00
if ( isBlockPassed ) {
console . warn ( ` featureTrigger check (verifyFeatureTrigger) determined block has PASSED: ` , isBlockPassed )
featureTriggerPassed = true
return true
} else {
console . warn ( ` featureTrigger check (verifyFeatureTrigger) determined block has NOT PASSED: ` , isBlockPassed )
featureTriggerPassed = false
return false
}
}
const checkAndDisplayInviteButton = async ( adminYes , creator , cardIdentifier ) => {
const isBlockPassed = await featureTriggerCheck ( )
2025-01-08 20:26:24 -08:00
let minAdminCount
const minterAdmins = await fetchMinterGroupAdmins ( )
if ( ! isBlockPassed ) {
console . warn ( ` feature trigger not passed, using static number for minAdminCount ` )
minAdminCount = 9
}
if ( ( minterAdmins ) && ( minterAdmins . length === 1 ) ) {
console . warn ( ` simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${ minAdminCount } ` )
} else if ( ( minterAdmins ) && ( minterAdmins . length > 1 ) && isBlockPassed ) {
const totalAdmins = minterAdmins . length
const fortyPercent = totalAdmins * 0.40
minAdminCount = Math . round ( fortyPercent )
console . warn ( ` this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${ minAdminCount } ` )
}
if ( isBlockPassed ) {
2025-01-13 15:53:20 -08:00
const minterNameInfo = await getNameInfo ( creator )
const minterAddress = await minterNameInfo . owner
2025-01-08 20:26:24 -08:00
if ( userState . isMinterAdmin ) {
2025-01-13 15:53:20 -08:00
let groupApprovalHtml = await checkGroupApprovalAndCreateButton ( minterAddress , cardIdentifier , "GROUP_INVITE" )
2025-01-08 20:26:24 -08:00
if ( groupApprovalHtml ) {
return groupApprovalHtml
}
} else {
console . log ( ` USER NOT ADMIN, no need for group approval buttons... ` )
}
}
2025-01-04 20:28:26 -08:00
2025-01-13 15:53:20 -08:00
if ( adminYes >= minAdminCount && ( userState . isMinterAdmin ) ) {
const inviteButtonHtml = createInviteButtonHtml ( creator , cardIdentifier )
console . log ( ` admin votes over 9, creating invite button... ` , adminYes )
2025-01-04 20:28:26 -08:00
return inviteButtonHtml
}
return null
}
2025-01-13 15:53:20 -08:00
const findPendingApprovalTxForAddress = async ( address , txType , limit = 0 , offset = 0 ) => {
// 1) Fetch all pending transactions
const pendingTxs = await searchPendingTransactions ( limit , offset )
// if a txType is passed, return the results related to that type, if not, then return any pending tx of the potential types.
let relevantTypes
if ( txType ) {
relevantTypes = new Set ( [ txType ] )
} else {
relevantTypes = new Set ( [ "GROUP_INVITE" , "GROUP_BAN" , "GROUP_KICK" ] )
}
// Filter pending TX for relevant types
const relevantTxs = pendingTxs . filter ( ( tx ) => relevantTypes . has ( tx . type ) )
// Further filter by whether 'address' matches the correct field
// - GROUP_INVITE => invitee
// - GROUP_BAN => offender
// - GROUP_KICK => member
// If the user passed a specific txType, only one branch might matter.
const matchedTxs = relevantTxs . filter ( ( tx ) => {
switch ( tx . type ) {
case "GROUP_INVITE" :
return tx . invitee === address
case "GROUP_BAN" :
return tx . offender === address
case "GROUP_KICK" :
return tx . member === address
default :
return false
}
} )
return matchedTxs // Array of matching pending transactions
}
2025-01-08 20:26:24 -08:00
const checkGroupApprovalAndCreateButton = async ( address , cardIdentifier , transactionType ) => {
2025-01-13 15:53:20 -08:00
const txTypes = [ transactionType ]
const txSearchResults = await searchTransactions ( {
txTypes ,
address : ` ${ address } ` ,
confirmationStatus : 'CONFIRMED' ,
limit : 0 ,
reverse : true ,
offset : 0 ,
startBlock : 1990000 ,
blockLimit : 0 ,
txGroupId : 694
} )
2025-01-08 20:26:24 -08:00
2025-01-13 15:53:20 -08:00
const approvalTxType = [ 'GROUP_APPROVAL' ]
const approvalSearchResults = await searchTransactions ( {
txTypes : approvalTxType ,
address : ` ${ address } ` ,
confirmationStatus : 'CONFIRMED' ,
limit : 0 ,
reverse : true ,
offset : 0 ,
startBlock : 1990000 ,
blockLimit : 0 ,
txGroupId : 0
} )
2025-01-08 20:26:24 -08:00
2025-01-13 15:53:20 -08:00
console . warn ( ` transaction search results, this is for comparison to pendingApprovals search, these are not used: ` , txSearchResults )
const pendingApprovals = await findPendingApprovalTxForAddress ( address , transactionType )
2025-01-08 20:26:24 -08:00
if ( pendingApprovals ) {
2025-01-13 15:53:20 -08:00
console . warn ( ` this is what is used for pending results... pendingApprovals FOUND: ` , pendingApprovals )
2025-01-08 20:26:24 -08:00
}
2025-01-13 15:53:20 -08:00
if ( ( pendingApprovals . length === 0 ) || ( ! pendingApprovals ) ) {
console . warn ( ` no pending approval transactions found, returning null... ` )
return null
2025-01-08 20:26:24 -08:00
}
2025-01-13 15:53:20 -08:00
const existingApprovalCount = approvalSearchResults . length
2025-01-08 20:26:24 -08:00
const txSig = pendingApprovals [ 0 ] . signature
if ( transactionType === ` GROUP_INVITE ` ) {
const approvalButtonHtml = `
< div id = "approval-button-container-${cardIdentifier}" style = "margin-top: 1em;" >
2025-01-13 15:53:20 -08:00
< h2 style = "color:rgb(181, 214, 100);" > Existing Invite Approvals : $ { existingApprovalCount } < / h 2 >
2025-01-08 20:26:24 -08:00
< button
2025-01-13 15:53:20 -08:00
style = "padding: 8px; background:rgb(37, 97, 99); color:rgb(215, 215, 215) ; border: 1px solid #333; border-color: white; border-radius: 5px; cursor: pointer;"
2025-01-08 20:26:24 -08:00
onmouseover = "this.style.backgroundColor='rgb(25, 47, 39) '"
2025-01-13 15:53:20 -08:00
onmouseout = "this.style.backgroundColor='rgb(37, 96, 99) '"
onclick = "handleGroupApproval('${txSig}')" >
Approve Invite Tx
2025-01-08 20:26:24 -08:00
< / b u t t o n >
< / d i v >
`
return approvalButtonHtml
}
if ( transactionType === ` GROUP_KICK ` ) {
const approvalButtonHtml = `
< div id = "approval-button-container-${cardIdentifier}" style = "margin-top: 1em;" >
2025-01-13 15:53:20 -08:00
< h2 style = "color:rgb(199, 100, 64);" > Existing Kick Approvals : $ { existingApprovalCount } < / h 2 >
2025-01-08 20:26:24 -08:00
< button
2025-01-13 15:53:20 -08:00
style = "padding: 8px; background:rgb(119, 91, 21); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(102, 69, 60); border-radius: 5px; cursor: pointer;"
2025-01-08 20:26:24 -08:00
onmouseover = "this.style.backgroundColor='rgb(50, 52, 51) '"
onmouseout = "this.style.backgroundColor='rgb(119, 91, 21) '"
2025-01-13 15:53:20 -08:00
onclick = "handleGroupApproval('${txSig}')" >
Approve Kick Tx
2025-01-08 20:26:24 -08:00
< / b u t t o n >
< / d i v >
`
return approvalButtonHtml
}
if ( transactionType === ` GROUP_BAN ` ) {
const approvalButtonHtml = `
< div id = "approval-button-container-${cardIdentifier}" style = "margin-top: 1em;" >
2025-01-13 15:53:20 -08:00
< h2 style = "color:rgb(189, 40, 40);" > Existing Ban Approvals : $ { existingApprovalCount } < / h 2 >
2025-01-08 20:26:24 -08:00
< button
2025-01-13 15:53:20 -08:00
style = "padding: 8px; background:rgb(54, 7, 7); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(204, 94, 94); border-radius: 5px; cursor: pointer;"
2025-01-08 20:26:24 -08:00
onmouseover = "this.style.backgroundColor='rgb(50, 52, 51) '"
onmouseout = "this.style.backgroundColor='rgb(54, 7, 7) '"
2025-01-13 15:53:20 -08:00
onclick = "handleGroupApproval('${txSig}')" >
Approve Ban Tx
2025-01-08 20:26:24 -08:00
< / b u t t o n >
< / d i v >
`
return approvalButtonHtml
}
}
2025-01-13 15:53:20 -08:00
const handleGroupApproval = async ( pendingSignature ) => {
2025-01-08 20:26:24 -08:00
try {
if ( ! userState . isMinterAdmin ) {
console . warn ( ` non-admin attempting to sign approval! ` )
return
}
const fee = 0.01
const adminPublicKey = await getPublicKeyByName ( userState . accountName )
2025-01-13 15:53:20 -08:00
const txGroupId = 0
const rawGroupApprovalTransaction = await createGroupApprovalTransaction ( adminPublicKey , pendingSignature , txGroupId , fee )
2025-01-08 20:26:24 -08:00
const signedGroupApprovalTransaction = await qortalRequest ( {
action : "SIGN_TRANSACTION" ,
unsignedBytes : rawGroupApprovalTransaction
} )
2025-01-13 15:53:20 -08:00
let txToProcess = signedGroupApprovalTransaction
2025-01-08 20:26:24 -08:00
const processGroupApprovalTx = await processTransaction ( txToProcess )
if ( processGroupApprovalTx ) {
alert ( ` transaction processed, please wait for CONFIRMATION: ${ JSON . stringify ( processGroupApprovalTx ) } ` )
} else {
alert ( ` creating tx failed for some reason ` )
}
} catch ( error ) {
console . error ( error )
throw error
}
}
const handleJoinGroup = async ( minterAddress ) => {
try {
if ( userState . accountAddress === minterAddress ) {
console . log ( ` minter user found ` )
2025-01-13 15:53:20 -08:00
const qRequestAttempt = await qortalRequest ( {
action : "JOIN_GROUP" ,
groupId : 694
} )
if ( qRequestAttempt ) {
return true
}
2025-01-08 20:26:24 -08:00
const joinerPublicKey = getPublicKeyFromAddress ( minterAddress )
2025-01-13 15:53:20 -08:00
const fee = 0.01
2025-01-08 20:26:24 -08:00
const joinGroupTransactionData = await createGroupJoinTransaction ( minterAddress , joinerPublicKey , 694 , 0 , fee )
const signedJoinGroupTransaction = await qortalRequest ( {
action : "SIGN_TRANSACTION" ,
unsignedBytes : joinGroupTransactionData
} )
let txToProcess = signedJoinGroupTransaction
const processJoinGroupTransaction = await processTransaction ( txToProcess )
if ( processJoinGroupTransaction ) {
console . warn ( ` processed JOIN_GROUP tx ` , processJoinGroupTransaction )
alert ( ` JOIN GROUP Transaction Processed Successfully, please WAIT FOR CONFIRMATION txData: ${ JSON . stringify ( processJoinGroupTransaction ) } ` )
}
} else {
console . warn ( ` user is not the minter ` )
return ''
}
} catch ( error ) {
throw error
}
}
2025-01-06 18:43:54 -08:00
const getMinterAvatar = async ( minterName ) => {
const avatarUrl = ` /arbitrary/THUMBNAIL/ ${ minterName } /qortal_avatar `
try {
const response = await fetch ( avatarUrl , { method : 'HEAD' } )
if ( response . ok ) {
return ` <img src=" ${ avatarUrl } " alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; align-self: center;"> `
} else {
return ''
}
} catch ( error ) {
console . error ( 'Error checking avatar availability:' , error )
return ''
}
}
2025-01-04 20:28:26 -08:00
2024-12-12 17:23:34 -08:00
// Create the overall Minter Card HTML -----------------------------------------------
2025-01-08 20:26:24 -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
2025-01-04 20:28:26 -08:00
const inviteButtonHtml = await checkAndDisplayInviteButton ( adminYes , creator , cardIdentifier )
2025-01-08 20:26:24 -08:00
let inviteHtmlAdd = ( inviteButtonHtml ) ? inviteButtonHtml : ''
let finalBgColor = bgColor
let invitedText = "" // for "INVITED" label if found
try {
const minterAddress = await fetchOwnerAddressFromName ( creator )
const invites = await fetchGroupInvitesByAddress ( minterAddress )
const hasMinterInvite = invites . some ( ( invite ) => invite . groupId === 694 )
if ( hasMinterInvite ) {
// If so, override background color & add an "INVITED" label
finalBgColor = "black" ;
invitedText = ` <h4 style="color: gold; margin-bottom: 0.5em;">INVITED</h4> `
if ( userState . accountName === creator ) { //Check also if the creator is the user, and display the join group button if so.
inviteHtmlAdd = `
< div id = "join-button-container-${cardIdentifier}" style = "margin-top: 1em;" >
< button
2025-01-13 15:53:20 -08:00
style = "padding: 8px; background:rgb(37, 99, 44); color:rgb(240, 240, 240); border: 1px solid rgb(255, 255, 255); border-radius: 5px; cursor: pointer;"
2025-01-08 20:26:24 -08:00
onmouseover = "this.style.backgroundColor='rgb(25, 47, 39) '"
onmouseout = "this.style.backgroundColor='rgb(37, 99, 44) '"
onclick = "handleJoinGroup('${userState.accountAddress}')" >
2025-01-13 15:53:20 -08:00
Join MINTER Group
2025-01-08 20:26:24 -08:00
< / b u t t o n >
< / d i v >
`
} else {
2025-01-13 15:53:20 -08:00
console . log ( ` user is not the minter... NOT displaying any join button ` )
2025-01-08 20:26:24 -08:00
inviteHtmlAdd = ''
}
}
//do not display invite button as they're already invited. Create a join button instead.
} catch ( error ) {
console . error ( "Error checking invites for user:" , error )
}
2025-01-04 20:28:26 -08:00
2024-12-11 14:40:32 -08:00
return `
2025-01-08 20:26:24 -08:00
< div class = "minter-card" style = "background-color: ${finalBgColor}" >
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 >
2025-01-08 20:26:24 -08:00
$ { invitedText }
2024-12-11 14:40:32 -08:00
< / d i v >
2025-01-04 20:28:26 -08:00
< div class = "support-header" > < h5 > USER ' S POST < / h 5 > < / d i v >
2024-12-11 14:40:32 -08:00
< div class = "info" >
$ { content }
< / d i v >
2025-01-04 20:28:26 -08:00
< div class = "support-header" > < h5 > USER ' S LINKS < / h 5 > < / d i v >
2024-12-11 14:40:32 -08:00
< div class = "info-links" >
$ { linksHTML }
< / d i v >
2025-01-04 20:28:26 -08:00
< div class = "results-header support-header" > < h5 > CURRENT SUPPORT 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 >
2025-01-04 20:28:26 -08:00
$ { inviteHtmlAdd }
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 >
2025-01-04 20:28:26 -08:00
< div class = "support-header" > < h5 > SUPPORT ACTION FOR < / 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 >
2024-12-20 22:07:18 -08:00
< 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
}