Added Card organization, refresh button, and started on 'Minter Data Board' that will include encryption and ability to publish card data for other minters. More work required to finish it.

This commit is contained in:
crowetic 2024-12-16 19:53:37 -08:00
parent 9406c396bf
commit d7bd413c31
8 changed files with 729 additions and 81 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -599,6 +599,13 @@ body {
padding: 1.2vh;
}
}
.refresh-cards-button {
border-color: white;
border-radius: 1.5vh;
background-color: black;
color: white;
}
/* Two cards per row on medium screens */
/* @media (max-width: 768px) {

View File

@ -13,7 +13,7 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log(`User is an Admin, buttons for MA Tools not removed. userState.isAdmin = ${userState.isMinterAdmin}`);
} else {
// Remove all "TOOLS" links and their related elements
const toolsLinks = document.querySelectorAll('a[href="TOOLS"]');
const toolsLinks = document.querySelectorAll('a[href="TOOLS"], a[href="MINTER-BOARD"], a[href="MINTERS"]');
toolsLinks.forEach(link => {
// If the link is within a button, remove the button
const buttonParent = link.closest('button');

View File

@ -1,4 +1,5 @@
// const cardIdentifierPrefix = "test-board-card"
const testMode = true;
const cardIdentifierPrefix = "testMB-board-card";
let isExistingCard = false;
let existingCardData = {};
@ -35,6 +36,7 @@ const loadMinterBoardPage = async () => {
<h1 style="color: lightblue;">Minter Board</h1>
<p style="font-size: 1.25em;"> The Minter Board is a place to publish information about yourself in order to obtain support from existing Minters and Minter Admins on the Qortal network. You may publish a header, content, and links to other QDN-published content in order to support you in your mission. Minter Admins and Existing Minters will then support you (or not) by way of a vote on your card. Card details you publish, along with existing poll results, and comments from others, will be displayed here. Good Luck on your Qortal journey to becoming a minter!</p>
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px;">Publish Minter Card</button>
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
<div id="publish-card-view" class="publish-card-view" style="display: none; text-align: left; padding: 20px;">
<form id="publish-card-form">
@ -58,30 +60,35 @@ const loadMinterBoardPage = async () => {
document.getElementById("publish-card-button").addEventListener("click", async () => {
try {
// await fetchExistingCard();
const cardIdentifier = await fetchExistingCard().identifier
if (cardIdentifier) {
// Update existing card
const fetchedCard = await fetchExistingCard();
if (fetchedCard) {
// An existing card is found
if (testMode) {
// In test mode, ask user what to do
const updateCard = confirm("A card already exists. Do you want to update it?");
isExistingCard = true;
if (updateCard) {
// Load existing card into the form for editing
isExistingCard = true;
await loadCardIntoForm(existingCardData);
alert("Edit your existing card and publish.");
} else {
// Allow creating a new card for testing purposes
alert("You can now create a new card for testing.");
alert("Test mode: You can now create a new card.");
isExistingCard = false;
existingCardData = {}; // Reset to allow new card creation
existingCardData = {}; // Reset
document.getElementById("publish-card-form").reset();
}
} else {
// Not in test mode, force editing
alert("A card already exists. Publishing of multiple cards is not allowed. Please update your card.");
isExistingCard = true;
await loadCardIntoForm(existingCardData);
}
} else {
// No existing card found
alert("No existing card found. Create a new card.");
isExistingCard = false;
}
// Show the form for publishing a card
// Show the form
const publishCardView = document.getElementById("publish-card-view");
publishCardView.style.display = "flex";
document.getElementById("cards-container").style.display = "none";
@ -91,6 +98,13 @@ const loadMinterBoardPage = async () => {
}
});
document.getElementById("refresh-cards-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container");
cardsContainer.innerHTML = "<p>Refreshing cards...</p>";
await loadCards();
});
document.getElementById("cancel-publish-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container");
cardsContainer.style.display = "flex"; // Restore visibility
@ -133,16 +147,14 @@ const loadCards = async () => {
return;
}
// Validate cards
// Validate cards and filter
const validatedCards = await Promise.all(
response.map(async card => {
console.log("Validating card:", card);
const isValid = await validateCardStructure(card);
return isValid ? card : null;
})
);
// Filter valid cards
const validCards = validatedCards.filter(card => card !== null);
if (validCards.length === 0) {
@ -150,22 +162,22 @@ const loadCards = async () => {
return;
}
// Sort valid cards by timestamp (oldest to newest)
// Sort cards by timestamp descending (newest first)
validCards.sort((a, b) => {
const timestampA = a.updated || a.created || 0; // Use updated or created timestamp
const timestampA = a.updated || a.created || 0;
const timestampB = b.updated || b.created || 0;
return timestampA - timestampB;
return timestampB - timestampA;
});
// Reverse the sorted order (newest first)
validCards.reverse();
// Clear the container before adding cards
// Display skeleton cards immediately
cardsContainer.innerHTML = "";
validCards.forEach(card => {
const skeletonHTML = createSkeletonCardHTML(card.identifier);
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML);
});
// Process and display each card
await Promise.all(
validCards.map(async card => {
// Fetch and update each card
validCards.forEach(async card => {
try {
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
@ -174,36 +186,68 @@ const loadCards = async () => {
identifier: card.identifier,
});
const cardData = cardDataResponse;
if (!cardData || !cardData.poll) {
console.warn(`Skipping card with missing poll data: ${JSON.stringify(cardData)}`);
if (!cardDataResponse) {
console.warn(`Skipping invalid card: ${JSON.stringify(card)}`);
removeSkeleton(card.identifier);
return;
}
// Fetch poll results and check admin votes
const pollResults = await fetchPollResults(cardData.poll);
console.log(`Poll Results Fetched - totalVotes: ${pollResults.totalVotes}`);
// Check if more than 3 admins voted "No"
if (pollResults.adminNo > 3) {
console.log(`Card ${card.identifier} hidden due to more than 3 admin downvotes.`);
return; // Skip this card
// Skip cards without polls
if (!cardDataResponse.poll) {
console.warn(`Skipping card with no poll: ${card.identifier}`);
removeSkeleton(card.identifier);
return;
}
const cardHTML = await createCardHTML(cardData, pollResults, card.identifier);
cardsContainer.insertAdjacentHTML("beforeend", cardHTML);
// Fetch poll results
const pollResults = await fetchPollResults(cardDataResponse.poll);
// Generate final card HTML
const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier);
replaceSkeleton(card.identifier, finalCardHTML);
} catch (error) {
console.error(`Error processing card ${card.identifier}:`, error);
removeSkeleton(card.identifier); // Silently remove skeleton on error
}
})
);
});
} catch (error) {
console.error("Error loading cards:", error);
cardsContainer.innerHTML = "<p>Failed to load cards.</p>";
}
};
const removeSkeleton = (cardIdentifier) => {
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`);
if (skeletonCard) {
skeletonCard.remove(); // Remove the skeleton silently
}
};
const replaceSkeleton = (cardIdentifier, htmlContent) => {
const skeletonCard = document.getElementById(`skeleton-${cardIdentifier}`);
if (skeletonCard) {
skeletonCard.outerHTML = htmlContent;
}
};
// Function to create a skeleton card
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;">
<div style="width: 50px; height: 50px; background-color: #ccc; border-radius: 50%;"></div>
<div style="margin-left: 10px;">
<div style="width: 120px; height: 20px; background-color: #ccc; margin-bottom: 5px;"></div>
<div style="width: 80px; height: 15px; background-color: #ddd;"></div>
</div>
</div>
<div style="margin-top: 10px;">
<div style="width: 100%; height: 40px; background-color: #eee;"></div>
</div>
</div>
`;
};
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
@ -266,7 +310,6 @@ const fetchExistingCard = async () => {
}
};
// Validate that a card is indeed a card and not a comment. -------------------------------------
const validateCardStructure = async (card) => {
return (
@ -490,24 +533,6 @@ const displayComments = async (cardIdentifier) => {
}
};
// Vote YES on a poll ------------------------------
const voteYesOnPoll = async (poll) => {
await qortalRequest({
action: "VOTE_ON_POLL",
pollName: poll,
optionIndex: 0,
});
}
// Vote NO on a poll -----------------------------
const voteNoOnPoll = async (poll) => {
await qortalRequest({
action: "VOTE_ON_POLL",
pollName: poll,
optionIndex: 1,
});
}
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
const toggleComments = async (cardIdentifier) => {
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`);

View File

@ -0,0 +1,598 @@
// const cardIdentifierPrefix = "test-board-card"
const testMode = true;
const cardIdentifierPrefix = "test-mData-card";
let isExistingCard = false;
let existingCardData = {};
let existingCardIdentifier = {};
const isAdmin = await verifyUserIsAdmin()
document.addEventListener("DOMContentLoaded", async () => {
// Verify admin status and hide/show buttons
if (await isAdmin()) {
document.addEventListener("DOMContentLoaded", async () => {
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-DATA-BOARD"], a[href="DATA-BOARD"]');
minterBoardLinks.forEach(link => {
link.addEventListener("click", async (event) => {
event.preventDefault();
if (!userState.isLoggedIn) {
await login();
}
await loadMinterDataBoardPage();
});
});
});
}
})
const loadMinterDataBoardPage = async () => {
// Clear existing content on the page
const bodyChildren = document.body.children;
for (let i = bodyChildren.length - 1; i >= 0; i--) {
const child = bodyChildren[i];
if (!child.classList.contains("menu")) {
child.remove();
}
}
// Add the "Minter Board" content
const mainContent = document.createElement("div");
mainContent.innerHTML = `
<div class="minter-board-main" style="padding: 20px; text-align: center;">
<h1 style="color: lightblue;">Minter Board</h1>
<p style="font-size: 1.25em;"> The Minter Data Board is an encrypted card publishing board to keep track of minter data for the Minter Admins. Any Admin may publish a card, and related data, make comments on existing cards, and vote on existing card data in support or not of the name on the card. </p>
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px;">Publish Encrypted Card</button>
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
<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</h3>
<label for="card-header">Header:</label>
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
<label for="card-content">Content:</label>
<textarea id="card-content" placeholder="Enter detailed information about why you deserve to be a minter..." required></textarea>
<label for="card-links">Links (qortal://...):</label>
<div id="links-container">
<input type="text" class="card-link" placeholder="Enter QDN link">
</div>
<button type="button" id="add-link-button">Add Another Link</button>
<button type="submit" id="submit-publish-button">Publish Card</button>
<button type="button" id="cancel-publish-button">Cancel</button>
</form>
</div>
</div>
`;
document.body.appendChild(mainContent);
document.getElementById("publish-card-button").addEventListener("click", async () => {
try {
const fetchedCard = await checkDuplicateEncryptedCard();
if (fetchedCard) {
// An existing card is found
if (testMode) {
// In test mode, ask user what to do
const updateCard = confirm("A card already exists. Do you want to update it? Note, updating it will overwrite the data of the original publisher, only do this if necessary!");
if (updateCard) {
isExistingCard = true;
await loadEncryptedCardIntoForm(existingCardData);
alert("Edit the existing ");
} else {
alert("Test mode: You can now create a new card.");
isExistingCard = false;
existingCardData = {}; // Reset
document.getElementById("publish-card-form").reset();
}
} else {
// Not in test mode, force editing
alert("A card already exists. Publishing of multiple cards for the same minter is not allowed, either use existing card or update it.");
isExistingCard = true;
await loadEncryptedCardIntoForm(existingCardData);
}
} else {
// No existing card found
alert("No existing card found. Create a new card.");
isExistingCard = false;
}
// Show the form
const publishCardView = document.getElementById("publish-card-view");
publishCardView.style.display = "flex";
document.getElementById("cards-container").style.display = "none";
} catch (error) {
console.error("Error checking for existing card:", error);
alert("Failed to check for existing card. Please try again.");
}
});
const checkDuplicateEncryptedCard = async (minterName) => {
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "MAIL_PRIVATE",
query: `${cardIdentifierPrefix}-${minterName}`,
mode: "ALL",
});
return response && response.length > 0;
};
document.getElementById("refresh-cards-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container");
cardsContainer.innerHTML = "<p>Refreshing cards...</p>";
await fetchEncryptedCards();
});
document.getElementById("cancel-publish-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container");
cardsContainer.style.display = "flex"; // Restore visibility
const publishCardView = document.getElementById("publish-card-view");
publishCardView.style.display = "none"; // Hide the publish form
});
document.getElementById("add-link-button").addEventListener("click", async () => {
const linksContainer = document.getElementById("links-container");
const newLinkInput = document.createElement("input");
newLinkInput.type = "text";
newLinkInput.className = "card-link";
newLinkInput.placeholder = "Enter QDN link";
linksContainer.appendChild(newLinkInput);
});
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
event.preventDefault();
await publishEncryptedCard();
});
await fetchEncryptedCards();
}
//Main function to load the Minter Cards ----------------------------------------
const fetchEncryptedCards = async () => {
const cardsContainer = document.getElementById("cards-container");
cardsContainer.innerHTML = "<p>Loading cards...</p>";
try {
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "MAIL_PRIVATE",
query: cardIdentifierPrefix,
mode: "ALL"
});
if (!response || !Array.isArray(response) || response.length === 0) {
cardsContainer.innerHTML = "<p>No cards found.</p>";
return;
}
// Validate cards and filter
const validatedCards = await Promise.all(
response.map(async card => {
const isValid = await validateEncryptedCardStructure(card);
return isValid ? card : null;
})
);
const validCards = validatedCards.filter(card => card !== null);
if (validCards.length === 0) {
cardsContainer.innerHTML = "<p>No valid cards found.</p>";
return;
}
// Sort cards by timestamp descending (newest first)
validCards.sort((a, b) => {
const timestampA = a.updated || a.created || 0;
const timestampB = b.updated || b.created || 0;
return timestampB - timestampA;
});
// Display skeleton cards immediately
cardsContainer.innerHTML = "";
validCards.forEach(card => {
const skeletonHTML = createSkeletonCardHTML(card.identifier);
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML);
});
// Fetch and update each card
validCards.forEach(async card => {
try {
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: card.name,
service: "MAIL_PRIVATE",
identifier: card.identifier,
});
if (!cardDataResponse) {
console.warn(`Skipping invalid card: ${JSON.stringify(card)}`);
removeSkeleton(card.identifier);
return;
}
// Skip cards without polls
if (!cardDataResponse.poll) {
console.warn(`Skipping card with no poll: ${card.identifier}`);
removeSkeleton(card.identifier);
return;
}
// Fetch poll results
const pollResults = await fetchPollResults(cardDataResponse.poll);
// Generate final card HTML
const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier);
replaceSkeleton(card.identifier, finalCardHTML);
} catch (error) {
console.error(`Error processing card ${card.identifier}:`, error);
removeSkeleton(card.identifier); // Silently remove skeleton on error
}
});
} catch (error) {
console.error("Error loading cards:", error);
cardsContainer.innerHTML = "<p>Failed to load cards.</p>";
}
};
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
const fetchExistingEncryptedCard = async () => {
try {
// Step 1: Perform the search
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "BLOG_POST",
identifier: cardIdentifierPrefix,
name: userState.accountName,
mode: "ALL",
exactMatchNames: true // Search for the exact userName only when finding existing cards
});
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
// Step 2: Check if the response is an array and not empty
if (!response || !Array.isArray(response) || response.length === 0) {
console.log("No cards found for the current user.");
return null;
}
// Step 3: Validate cards asynchronously
const validatedCards = await Promise.all(
response.map(async card => {
const isValid = await validateEncryptedCardStructure(card);
return isValid ? card : null;
})
);
// Step 4: Filter out invalid cards
const validCards = validatedCards.filter(card => card !== null);
if (validCards.length > 0) {
// Step 5: Sort by most recent timestamp
const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0];
// Step 6: Fetch full card data
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;
console.log("Full card data fetched successfully:", cardDataResponse);
return cardDataResponse;
}
console.log("No valid cards found.");
return null;
} catch (error) {
console.error("Error fetching existing card:", error);
return null;
}
};
// Validate that a card is indeed a card and not a comment. -------------------------------------
const validateEncryptedCardStructure = async (card) => {
return (
typeof card === "object" &&
card.name &&
card.service === "BLOG_POST" &&
card.identifier && !card.identifier.includes("comment") &&
card.created
);
}
// Load existing card data passed, into the form for editing -------------------------------------
const loadEncryptedCardIntoForm = async (cardData) => {
console.log("Loading existing card data:", cardData);
document.getElementById("card-header").value = cardData.header;
document.getElementById("card-content").value = cardData.content;
const linksContainer = document.getElementById("links-container");
linksContainer.innerHTML = ""; // Clear previous links
cardData.links.forEach(link => {
const linkInput = document.createElement("input");
linkInput.type = "text";
linkInput.className = "card-link";
linkInput.value = link;
linksContainer.appendChild(linkInput);
});
}
// Main function to publish a new Minter Card -----------------------------------------------
const publishEncryptedCard = async () => {
const header = document.getElementById("card-header").value.trim();
const content = document.getElementById("card-content").value.trim();
const links = Array.from(document.querySelectorAll(".card-link"))
.map(input => input.value.trim())
.filter(link => link.startsWith("qortal://"));
if (!header || !content) {
alert("Header and content are required!");
return;
}
const cardIdentifier = isExistingCard ? existingCardIdentifier : `${cardIdentifierPrefix}-${await uid()}`;
const pollName = `${cardIdentifier}-poll`;
const pollDescription = `Mintership Board Poll for ${userState.accountName}`;
const cardData = {
header,
content,
links,
creator: userState.accountName,
timestamp: Date.now(),
poll: pollName,
};
try {
let base64CardData = await objectToBase64(cardData);
if (!base64CardData) {
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`);
base64CardData = btoa(JSON.stringify(cardData));
}
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName,
service: "BLOG_POST",
identifier: cardIdentifier,
data64: base64CardData,
});
if (!isExistingCard){
await qortalRequest({
action: "CREATE_POLL",
pollName,
pollDescription,
pollOptions: ['Yes, No'],
pollOwnerAddress: userState.accountAddress,
});
alert("Card and poll published successfully!");
}
if (isExistingCard){
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
}
document.getElementById("publish-card-form").reset();
document.getElementById("publish-card-view").style.display = "none";
document.getElementById("cards-container").style.display = "flex";
await loadCards();
} catch (error) {
console.error("Error publishing card or poll:", error);
alert("Failed to publish card and poll.");
}
}
// Post a comment on a card. ---------------------------------
const postEncryptedComment = async (cardIdentifier) => {
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`);
const commentText = commentInput.value.trim();
if (!commentText) {
alert('Comment cannot be empty!');
return;
}
const commentData = {
content: commentText,
creator: userState.accountName,
timestamp: Date.now(),
};
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`;
try {
const base64CommentData = await objectToBase64(commentData);
if (!base64CommentData) {
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`);
base64CommentData = btoa(JSON.stringify(commentData));
}
await qortalRequest({
action: 'PUBLISH_QDN_RESOURCE',
name: userState.accountName,
service: 'BLOG_POST',
identifier: commentIdentifier,
data64: base64CommentData,
});
alert('Comment posted successfully!');
commentInput.value = ''; // Clear input
// await displayComments(cardIdentifier); // Refresh comments - We don't need to do this as comments will be displayed only after confirmation.
} catch (error) {
console.error('Error posting comment:', error);
alert('Failed to post comment.');
}
};
//Fetch the comments for a card with passed card identifier ----------------------------
const fetchCommentsForEncryptedCard = async (cardIdentifier) => {
try {
const response = await qortalRequest({
action: 'SEARCH_QDN_RESOURCES',
service: 'BLOG_POST',
query: `comment-${cardIdentifier}`,
mode: "ALL"
});
return response;
} catch (error) {
console.error(`Error fetching comments for ${cardIdentifier}:`, error);
return [];
}
};
// display the comments on the card, with passed cardIdentifier to identify the card --------------
const displayEncryptedComments = async (cardIdentifier) => {
try {
const comments = await fetchCommentsForCard(cardIdentifier);
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`);
// Fetch and display each comment
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);
//TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section.
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 for ${cardIdentifier}:`, error);
alert("Failed to load comments. Please try again.");
}
};
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
const toggleComments = async (cardIdentifier) => {
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`);
if (commentsSection.style.display === 'none' || !commentsSection.style.display) {
await displayComments(cardIdentifier);
commentsSection.style.display = 'block';
} else {
commentsSection.style.display = 'none';
}
};
const createModal = async () => {
const modalHTML = `
<div id="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 1000;">
<div style="position: relative; margin: 10% auto; width: 95%; height: 80%; background: white; border-radius: 10px; overflow: hidden;">
<iframe id="modalContent" src="" style="width: 100%; height: 100%; border: none;"></iframe>
<button onclick="closeModal()" style="position: absolute; top: 10px; right: 10px; background: red; color: white; border: none; padding: 5px 10px; border-radius: 5px;">Close</button>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
// Function to open the modal
const openModal = async (link) => {
const processedLink = await processLink(link) // Process the link to replace `qortal://` for rendering in modal
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');
modalContent.src = processedLink; // Set the iframe source to the link
modal.style.display = 'block'; // Show the modal
}
// Function to close the modal
const closeModal = async () => {
const modal = document.getElementById('modal');
const modalContent = document.getElementById('modalContent');
modal.style.display = 'none'; // Hide the modal
modalContent.src = ''; // Clear the iframe source
}
const processLink = async (link) => {
if (link.startsWith('qortal://')) {
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/);
if (match) {
const firstParam = match[1].toUpperCase(); // Convert to uppercase
const remainingPath = match[2] || ""; // Rest of the URL
// Perform any asynchronous operation if necessary
await new Promise(resolve => setTimeout(resolve, 10)); // Simulating async operation
return `/render/${firstParam}${remainingPath}`;
}
}
return link; // Return unchanged if not a Qortal link
}
// Create the overall Minter Card HTML -----------------------------------------------
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier) => {
const { header, content, links, creator, timestamp, poll } = cardData;
const formattedDate = new Date(timestamp).toLocaleString();
const avatarUrl = `/arbitrary/THUMBNAIL/${creator}/qortal_avatar`;
const linksHTML = links.map((link, index) => `
<button onclick="openModal('${link}')">
${`Link ${index + 1} - ${link}`}
</button>
`).join("");
const minterGroupMembers = await fetchMinterGroupMembers();
const minterAdmins = await fetchMinterGroupAdmins();
const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0 } = await calculatePollResults(pollResults, minterGroupMembers, minterAdmins)
await createModal()
return `
<div class="minter-card">
<div class="minter-card-header">
<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; align-self: center;">
<h3>${creator}</h3>
<p>${header}</p>
</div>
<div class="support-header"><h5>Minter Post:</h5></div>
<div class="info">
${content}
</div>
<div class="support-header"><h5>Minter Links:</h5></div>
<div class="info-links">
${linksHTML}
</div>
<div class="results-header support-header"><h5>Current Results:</h5></div>
<div class="minter-card-results">
<div class="admin-results">
<span class="admin-yes">Admin Yes: ${adminYes}</span>
<span class="admin-no">Admin No: ${adminNo}</span>
</div>
<div class="minter-results">
<span class="minter-yes">Minter Yes: ${minterYes}</span>
<span class="minter-no">Minter No: ${minterNo}</span>
</div>
<div class="total-results">
<span class="total-yes">Total Yes: ${totalYes}</span>
<span class="total-no">Total No: ${totalNo}</span>
</div>
</div>
<div class="support-header"><h5>Support Minter?</h5></div>
<div class="actions">
<div class="actions-buttons">
<button class="yes" onclick="voteYesOnPoll('${poll}')">YES</button>
<button class="comment" onclick="toggleComments('${cardIdentifier}')">COMMENTS</button>
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div>
</div>
<div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;">
<div id="comments-container-${cardIdentifier}" class="comments-container"></div>
<textarea id="new-comment-${cardIdentifier}" placeholder="Write a comment..." style="width: 100%; margin-top: 10px;"></textarea>
<button onclick="postComment('${cardIdentifier}')">Post Comment</button>
</div>
<p style="font-size: 12px; color: gray;">Published by: ${creator} on ${formattedDate}</p>
</div>
`;
}

View File

@ -793,6 +793,24 @@ const fetchPollResults = async (pollName) => {
}
};
// Vote YES on a poll ------------------------------
const voteYesOnPoll = async (poll) => {
await qortalRequest({
action: "VOTE_ON_POLL",
pollName: poll,
optionIndex: 0,
});
}
// Vote NO on a poll -----------------------------
const voteNoOnPoll = async (poll) => {
await qortalRequest({
action: "VOTE_ON_POLL",
pollName: poll,
optionIndex: 1,
});
}
// export {
// userState,
// adminGroups,