BIG update including Admin Board and many additional changes. Version 0.5beta. Too many changes to list.
This commit is contained in:
parent
575ff48bb7
commit
9837ea70c0
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -6,57 +6,6 @@ async function verifyMinterAdminState() {
|
||||
return minterGroupAdmins.members.some(admin => admin.member === userState.accountAddress && admin.isAdmin);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const isAdmin = await verifyUserIsAdmin();
|
||||
|
||||
if (isAdmin) {
|
||||
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"], a[href="DATA-BOARD"]');
|
||||
toolsLinks.forEach(link => {
|
||||
// If the link is within a button, remove the button
|
||||
const buttonParent = link.closest('button');
|
||||
if (buttonParent) {
|
||||
buttonParent.remove();
|
||||
}
|
||||
|
||||
// If the link is within an image card or any other element, remove that element
|
||||
const cardParent = link.closest('.item.features-image');
|
||||
if (cardParent) {
|
||||
cardParent.remove();
|
||||
}
|
||||
|
||||
// Finally, remove the link itself if it's not covered by the above removals
|
||||
link.remove();
|
||||
});
|
||||
|
||||
console.log(`User is NOT a Minter Admin, buttons for MA Tools removed. userState.isMinterAdmin = ${userState.isMinterAdmin}`);
|
||||
|
||||
// Center the remaining card if it exists
|
||||
const remainingCard = document.querySelector('.features7 .row .item.features-image');
|
||||
if (remainingCard) {
|
||||
remainingCard.classList.remove('col-lg-6', 'col-md-6');
|
||||
remainingCard.classList.add('col-12', 'text-center');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Add event listener for admin tools link if the user is an admin
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"]');
|
||||
toolsLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
await loadMinterAdminToolsPage();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
async function loadMinterAdminToolsPage() {
|
||||
// Remove all body content except for menu elements
|
||||
const bodyChildren = document.body.children;
|
||||
|
@ -1,24 +1,10 @@
|
||||
// const cardIdentifierPrefix = "test-board-card"
|
||||
// // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards.
|
||||
const testMode = true;
|
||||
const cardIdentifierPrefix = "testMB-board-card";
|
||||
let isExistingCard = false;
|
||||
let existingCardData = {};
|
||||
let existingCardIdentifier = {};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]');
|
||||
|
||||
minterBoardLinks.forEach(link => {
|
||||
link.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
await loadMinterBoardPage();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const loadMinterBoardPage = async () => {
|
||||
// Clear existing content on the page
|
||||
const bodyChildren = document.body.children;
|
||||
|
@ -1,598 +0,0 @@
|
||||
// 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 minterDataBoardLinks = document.querySelectorAll('a[href="DATA-BOARD"]');
|
||||
|
||||
minterDataBoardLinks.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>
|
||||
`;
|
||||
}
|
||||
|
@ -15,22 +15,111 @@ if (localStorage.getItem("latestMessageIdentifiers")) {
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
// Identify the links for 'Mintership Forum' and apply functionality
|
||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]');
|
||||
console.log("DOMContentLoaded fired!");
|
||||
|
||||
// --- GENERAL LINKS (MINTERSHIP-FORUM and MINTER-BOARD) ---
|
||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]');
|
||||
mintershipForumLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
//login if not already logged in.
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
await loadForumPage();
|
||||
loadRoomContent("general"); // Automatically load General Room on forum load
|
||||
startPollingForNewMessages(); // Start polling for new messages after loading the forum page
|
||||
loadRoomContent("general");
|
||||
startPollingForNewMessages();
|
||||
});
|
||||
});
|
||||
|
||||
const minterBoardLinks = document.querySelectorAll('a[href="MINTER-BOARD"], a[href="MINTERS"]');
|
||||
minterBoardLinks.forEach(link => {
|
||||
link.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
if (typeof loadMinterBoardPage === "undefined") {
|
||||
console.log("loadMinterBoardPage not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/MinterBoard.js");
|
||||
}
|
||||
await loadMinterBoardPage();
|
||||
});
|
||||
});
|
||||
|
||||
// --- ADMIN CHECK ---
|
||||
await verifyUserIsAdmin();
|
||||
|
||||
if (userState.isAdmin) {
|
||||
console.log(`User is an Admin. Admin-specific buttons will remain visible.`);
|
||||
|
||||
// DATA-BOARD Links for Admins
|
||||
const minterDataBoardLinks = document.querySelectorAll('a[href="ADMINBOARD"]');
|
||||
minterDataBoardLinks.forEach(link => {
|
||||
link.addEventListener("click", async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
if (typeof loadAdminBoardPage === "undefined") {
|
||||
console.log("loadAdminBoardPage function not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/AdminBoard.js");
|
||||
}
|
||||
await loadAdminBoardPage();
|
||||
});
|
||||
});
|
||||
|
||||
// TOOLS Links for Admins
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"]');
|
||||
toolsLinks.forEach(link => {
|
||||
link.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
if (!userState.isLoggedIn) {
|
||||
await login();
|
||||
}
|
||||
if (typeof loadMinterAdminToolsPage === "undefined") {
|
||||
console.log("loadMinterAdminToolsPage function not found, loading script dynamically...");
|
||||
await loadScript("./assets/js/AdminTools.js");
|
||||
}
|
||||
await loadMinterAdminToolsPage();
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("User is NOT an Admin. Removing admin-specific links.");
|
||||
|
||||
// Remove all admin-specific links and their parents
|
||||
const toolsLinks = document.querySelectorAll('a[href="TOOLS"], a[href="DATA-BOARD"]');
|
||||
toolsLinks.forEach(link => {
|
||||
const buttonParent = link.closest('button');
|
||||
if (buttonParent) buttonParent.remove();
|
||||
|
||||
const cardParent = link.closest('.item.features-image');
|
||||
if (cardParent) cardParent.remove();
|
||||
|
||||
link.remove();
|
||||
});
|
||||
|
||||
// Center the remaining card if it exists
|
||||
const remainingCard = document.querySelector('.features7 .row .item.features-image');
|
||||
if (remainingCard) {
|
||||
remainingCard.classList.remove('col-lg-6', 'col-md-6');
|
||||
remainingCard.classList.add('col-12', 'text-center');
|
||||
}
|
||||
}
|
||||
|
||||
console.log("All DOMContentLoaded tasks completed.");
|
||||
});
|
||||
|
||||
async function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Main load function to clear existing HTML and load the forum page -----------------------------------------------------
|
||||
const loadForumPage = async () => {
|
||||
@ -452,6 +541,7 @@ const showSuccessNotification = () => {
|
||||
notification.style.color = "green";
|
||||
notification.style.marginTop = "1em";
|
||||
document.querySelector(".message-input-section").appendChild(notification);
|
||||
alert(`Successfully Published! Please note that messages will not display until after they are CONFIRMED, be patient!`)
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
@ -464,17 +554,6 @@ const generateAttachmentID = (room, fileIndex = null) => {
|
||||
return fileIndex !== null ? `${baseID}-${fileIndex}` : baseID;
|
||||
};
|
||||
|
||||
const decryptObject = async (encryptedData) => {
|
||||
// const publicKey = await getPublicKeyFromAddress(userState.accountAddress)
|
||||
const response = await qortalRequest({
|
||||
action: 'DECRYPT_DATA',
|
||||
encryptedData, // has to be in base64 format
|
||||
// publicKey: publisherPublicKey // requires the public key of the opposite user with whom you've created the encrypted data. For DIRECT messages only.
|
||||
});
|
||||
const decryptedObject = response
|
||||
return decryptedObject
|
||||
}
|
||||
|
||||
const decryptFile = async (encryptedData) => {
|
||||
const publicKey = await getPublicKeyByName(userState.accountName)
|
||||
const response = await qortalRequest({
|
||||
|
@ -453,7 +453,7 @@ const fetchAdminGroupsMembersPublicKeys = async () => {
|
||||
};
|
||||
|
||||
|
||||
// QDN data calls
|
||||
// QDN data calls --------------------------------------------------------------------------------------------------
|
||||
const searchLatestDataByIdentifier = async (identifier) => {
|
||||
console.log('fetchAllDataByIdentifier called');
|
||||
console.log('identifier:', identifier);
|
||||
@ -493,6 +493,22 @@ const publishMultipleResources = async (resources, publicKeys = null, isPrivate
|
||||
};
|
||||
};
|
||||
|
||||
// the object must be in base64 when sent
|
||||
const decryptObject = async (encryptedData) => {
|
||||
// const publicKey = await getPublicKeyFromAddress(userState.accountAddress)
|
||||
const response = await qortalRequest({
|
||||
action: 'DECRYPT_DATA',
|
||||
encryptedData, // has to be in base64 format
|
||||
// publicKey: publisherPublicKey // requires the public key of the opposite user with whom you've created the encrypted data. For DIRECT messages only.
|
||||
});
|
||||
const decryptedObject = response
|
||||
return decryptedObject
|
||||
}
|
||||
|
||||
const decryptAndParseObject = async (base64Data) => {
|
||||
const decrypted = await decryptObject(base64Data);
|
||||
return JSON.parse(atob(decrypted));
|
||||
};
|
||||
|
||||
const searchResourcesWithMetadata = async (query, limit) => {
|
||||
console.log('searchResourcesWithMetadata called');
|
||||
|
@ -138,7 +138,7 @@ body {
|
||||
}
|
||||
.btn-secondary,
|
||||
.btn-secondary:active {
|
||||
background-color: #a4a2a2 !important;
|
||||
background-color: #7cddff !important;
|
||||
border-color: #a4a2a2 !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2);
|
||||
@ -148,7 +148,7 @@ body {
|
||||
.btn-secondary.focus,
|
||||
.btn-secondary.active {
|
||||
color: #ffffff !important;
|
||||
background-color: #797676 !important;
|
||||
background-color: #50abf1b5 !important;
|
||||
border-color: #797676 !important;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
@ -1283,6 +1283,10 @@ a {
|
||||
}
|
||||
}
|
||||
.cid-ttRnktJ11Q .mbr-section-btn .btn,
|
||||
.cid-ttRnktJ11Q .mbr-section-btn-main .btn .admin-btn {
|
||||
background-image: linear-gradient(99deg, rgba(23, 64, 86, 0.468) 30%, #77dff6 100%), radial-gradient(circle at 50% 50%, #64f7cb 0, rgba(255, 255, 255, 0) 70%);
|
||||
;
|
||||
}
|
||||
.cid-ttRnktJ11Q .mbr-section-btn-main .btn {
|
||||
background-image: linear-gradient(99deg, rgba(255, 255, 255, 0) 30%, #ffffff 100%), radial-gradient(circle at 50% 50%, #ffffff 0, rgba(255, 255, 255, 0) 70%);
|
||||
;
|
||||
|
50
index.html
50
index.html
@ -68,12 +68,12 @@
|
||||
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
|
||||
</a>
|
||||
</span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.41b<br></a></span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.5b<br></a></span>
|
||||
</div>
|
||||
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
|
||||
|
||||
|
||||
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn btn-secondary display-4" href="MINTERS">MINTER BOARD</a></div>
|
||||
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn admin-btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn admin-btn btn-secondary display-4" href="ADMINBOARD">ADMIN BOARD</a><a class="btn btn-danger display-4" href="MINTERS">MINTER BOARD</a></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -185,7 +185,7 @@
|
||||
<div class="col-12 card">
|
||||
<div class="title-wrapper">
|
||||
<h2 class="mbr-section-title mbr-fonts-style display-1">
|
||||
More information...</h2>
|
||||
Updates and information</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -196,6 +196,40 @@
|
||||
|
||||
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
<div class="title-wrapper">
|
||||
<h2 class="mbr-section-title mbr-fonts-style display-2">
|
||||
a few things remaining</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 card">
|
||||
<div class="text-wrapper">
|
||||
<p class="mbr-text mbr-fonts-style display-7">
|
||||
There are still a few things remaining, such as downloading of encrypted attachments in the Admin Room on the forum, and final testing of the Minter Data Board, with potentially a rename of that section to 'Minter Management' in the future. Many additional changes will be coming as time goes on as well. Turning Q-Mintership-Alpha into a fully featured Mintership app! Hope you enjoy it and that it is useful for you, and if you have any suggestions in regard to new features or modifications to existing ones, <a href="qortal://APP/Q-Mail/to/crowetic">send a Q-Mail message to crowetic.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
<div class="title-wrapper">
|
||||
<h2 class="mbr-section-title mbr-fonts-style display-2">
|
||||
Updates 12.17.2024</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 card">
|
||||
<div class="text-wrapper">
|
||||
<p class="mbr-text mbr-fonts-style display-7">
|
||||
The Q-Mintership-Alpha application is now published on the Q-Mintership name, and has a plethora of new features. Including the Minter Board, where new minters will publish information about themselves so that the admins can make informed decisions, and the Minter Data Board as a new Minter Admin Tool to manage the data regarding minters they want to add/remove from the MINTER group. The Forum portion of the app now also contains an encrypted Admin room, and has had a ton of improvements made as well. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
@ -229,7 +263,7 @@
|
||||
</div>
|
||||
|
||||
<a class="link-wrap" href="#">
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.40.41beta</p>
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.5beta</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
@ -249,13 +283,13 @@
|
||||
<script src="assets/ytplayer/index.js"></script>
|
||||
<script src="assets/dropdown/js/navbar-dropdown.js"></script>
|
||||
<script src="assets/theme/js/script.js"></script>
|
||||
|
||||
|
||||
<script src="./assets/quill/quill.min.js"></script>
|
||||
|
||||
<script src="./assets/js/QortalApi.js"></script>
|
||||
<script src="./assets/js/Q-Mintership.js"></script>
|
||||
<script src="./assets/js/AdminTools.js"></script>
|
||||
<script src="./assets/js/MinterBoard.js"></script>
|
||||
<script src="./assets/js/AdminTools.js"></script>
|
||||
<script src="./assets/js/AdminBoard.js"></script>
|
||||
<script src="./assets/js/Q-Mintership.js"></script>
|
||||
<input name="animation" type="hidden">
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user