BIG update including Admin Board and many additional changes. Version 0.5beta. Too many changes to list.

This commit is contained in:
crowetic 2024-12-17 22:24:40 -08:00
parent 575ff48bb7
commit 9837ea70c0
10 changed files with 167 additions and 697 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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;

View File

@ -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;

View File

@ -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>
`;
}

View File

@ -15,23 +15,112 @@ 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 () => {
// remove everything that isn't the menu from the body to use js to generate page content.
@ -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({

View File

@ -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');

View File

@ -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%);
;

View File

@ -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">
@ -244,18 +278,18 @@
<script src="assets/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/parallax/jarallax.js"></script>
<script src="assets/smoothscroll/smooth-scroll.js"></script>
<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/parallax/jarallax.js"></script>
<script src="assets/smoothscroll/smooth-scroll.js"></script>
<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>