testing-20250123 #6

Closed
Ghost wants to merge 18 commits from (deleted):testing-20250123 into main
6 changed files with 300 additions and 40 deletions

View File

@ -28,7 +28,6 @@ const loadAddRemoveAdminPage = async () => {
</p> </p>
<div id="admin-table-section" class="admin-table-section" style="margin-top: 2em;"> <div id="admin-table-section" class="admin-table-section" style="margin-top: 2em;">
<h3 style="color:rgb(212, 212, 212);">Existing Minter Admins</h3>
<div id="admin-list-container" style="margin: 1em auto; max-width: 600px;"></div> <div id="admin-list-container" style="margin: 1em auto; max-width: 600px;"></div>
</div> </div>
@ -59,6 +58,13 @@ const loadAddRemoveAdminPage = async () => {
<div id="existing-proposals-section" class="proposals-section" style="margin-top: 3em; display: flex; flex-direction: column; justify-content: center; align-items: center;"> <div id="existing-proposals-section" class="proposals-section" style="margin-top: 3em; display: flex; flex-direction: column; justify-content: center; align-items: center;">
<h3 style="color: #ddd;">Existing Promotion/Demotion Proposals</h3> <h3 style="color: #ddd;">Existing Promotion/Demotion Proposals</h3>
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Proposal Cards</button> <button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Proposal Cards</button>
<select id="sort-select" style="margin-left: 10px; padding: 5px;">
<option value="newest" selected>Sort by Date</option>
<option value="name">Sort by Name</option>
<option value="recent-comments">Newest Comments</option>
<option value="least-votes">Least Votes</option>
<option value="most-votes">Most Votes</option>
</select>
</div> </div>
<div id="cards-container" class="cards-container" style="margin-top: 1rem""> <div id="cards-container" class="cards-container" style="margin-top: 1rem"">
<!-- We'll fill this with existing proposal cards --> <!-- We'll fill this with existing proposal cards -->
@ -87,10 +93,18 @@ const loadAddRemoveAdminPage = async () => {
document.getElementById("refresh-cards-button").addEventListener("click", async () => { document.getElementById("refresh-cards-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container") const cardsContainer = document.getElementById("cards-container")
cardsContainer.innerHTML = "<p>Refreshing cards...</p>" cardsContainer.innerHTML = `<p style="color:dodgerblue;">Refreshing cards...</p>`
await loadCards(addRemoveIdentifierPrefix) await loadCards(addRemoveIdentifierPrefix)
}) })
document.getElementById("sort-select").addEventListener("change", async () => {
// Optionally clear or show a message while loading
const cardsContainer = document.getElementById("cards-container");
cardsContainer.innerHTML = `<p style="color:dodgerblue;">Refreshing cards...</p>`;
// Re-load the cards using the same function that handles sorting logic
await loadCards(addRemoveIdentifierPrefix);
});
document.getElementById("cancel-publish-button").addEventListener("click", async () => { document.getElementById("cancel-publish-button").addEventListener("click", async () => {
// const cardsContainer = document.getElementById("existing-proposals-section") // const cardsContainer = document.getElementById("existing-proposals-section")
// cardsContainer.style.display = "flex" // Restore visibility // cardsContainer.style.display = "flex" // Restore visibility
@ -126,6 +140,19 @@ const toggleProposeButton = () => {
proposeButton.style.display === 'flex' ? 'none' : 'flex' proposeButton.style.display === 'flex' ? 'none' : 'flex'
} }
const toggleAdminTable = () => {
const tableContainer = document.getElementById("adminTableContainer");
const toggleBtn = document.getElementById("toggleAdminTableButton");
if (tableContainer.style.display === "none") {
tableContainer.style.display = "block";
toggleBtn.textContent = "Hide Minter Admins";
} else {
tableContainer.style.display = "none";
toggleBtn.textContent = "Show Minter Admins";
}
};
let addAdminTxs let addAdminTxs
let remAdminTxs let remAdminTxs
@ -164,8 +191,7 @@ const fetchAllARTxData = async () => {
// Filter out members with both pending and non-pending transactions // Filter out members with both pending and non-pending transactions
return Object.values(adminTxMap) return Object.values(adminTxMap)
.filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') && .filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
txs.some(tx => tx.approvalStatus !== 'PENDING')))
.flat() .flat()
} }
@ -209,6 +235,9 @@ const displayExistingMinterAdmins = async () => {
// 1) Fetch addresses // 1) Fetch addresses
const admins = await fetchMinterGroupAdmins() const admins = await fetchMinterGroupAdmins()
minterAdminAddresses = admins.map(m => m.member) minterAdminAddresses = admins.map(m => m.member)
// Compute total admin count and signatures needed (40%, rounded up)
const totalAdmins = admins.length;
const signaturesNeeded = Math.ceil(totalAdmins * 0.40);
let rowsHtml = ""; let rowsHtml = "";
for (const adminAddr of admins) { for (const adminAddr of admins) {
if (adminAddr.member === nullAddress) { if (adminAddr.member === nullAddress) {
@ -255,6 +284,22 @@ const displayExistingMinterAdmins = async () => {
} }
// 3) Build the table // 3) Build the table
const tableHtml = ` const tableHtml = `
<div style="text-align: center; margin-bottom: 1em;">
<button
id="toggleAdminTableButton"
onclick="toggleAdminTable()"
style="
padding: 10px;
background: #444;
color: #fff;
border-radius: 5px;
cursor: pointer;
"
>
Show Minter Admins
</button>
</div>
<div id="adminTableContainer" style="display: none;">
<table style="width: 100%; border-collapse: collapse;"> <table style="width: 100%; border-collapse: collapse;">
<thead> <thead>
<tr style="background:rgb(21, 36, 18); color:rgb(183, 208, 173); font-size: 1.5rem;"> <tr style="background:rgb(21, 36, 18); color:rgb(183, 208, 173); font-size: 1.5rem;">
@ -267,8 +312,13 @@ const displayExistingMinterAdmins = async () => {
${rowsHtml} ${rowsHtml}
</tbody> </tbody>
</table> </table>
</div>
` `
adminListContainer.innerHTML = tableHtml adminListContainer.innerHTML = `
<h3 style="color:rgb(212, 212, 212);">Existing Minter Admins: ${totalAdmins}</h3>
<h4 style="color:rgb(212, 212, 212);">Signatures for Group Approval (40%): ${signaturesNeeded}</h4>
${tableHtml}
`;
} catch (err) { } catch (err) {
console.error("Error fetching minter admins:", err) console.error("Error fetching minter admins:", err)
adminListContainer.innerHTML = adminListContainer.innerHTML =
@ -542,7 +592,7 @@ const checkAndDisplayActions = async (adminYes, name, cardIdentifier) => {
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){ } else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
const totalAdmins = minterAdmins.length const totalAdmins = minterAdmins.length
const fortyPercent = totalAdmins * 0.40 const fortyPercent = totalAdmins * 0.40
minAdminCount = Math.round(fortyPercent) minAdminCount = Math.ceil(fortyPercent)
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`) console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
} }
const addressInfo = await getNameInfo(name) const addressInfo = await getNameInfo(name)
@ -703,6 +753,47 @@ const handleRemoveMinterGroupAdmin = async (name, address) => {
} }
} }
// ADDED: A simple function to effectively 'delete' an AR Board card
// by publishing an empty card with the same identifier and prefix
const deleteARCard = async (cardIdentifier, prefix) => {
try {
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.");
if (!confirmed) return;
// A minimal blank object
const blankData = {
header: "",
content: "",
links: [],
creator: userState.accountName,
timestamp: Date.now(),
poll: "" // or null. This ensures it won't appear as a valid poll card
};
let base64Data = await objectToBase64(blankData);
if (!base64Data) {
base64Data = btoa(JSON.stringify(blankData));
}
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName,
service: "BLOG_POST", // same as all ARBoard content
identifier: cardIdentifier,
data64: base64Data,
});
alert("Your card has been effectively deleted.");
// Now reload the existing ARBoard cards so the UI no longer shows the old card
await loadCards(prefix);
} catch (error) {
console.error("Error deleting AR card:", error);
alert("Failed to delete the card. Check console for details.");
}
};
const fallbackMinterCheck = async (minterName, minterGroupMembers, minterAdmins) => { const fallbackMinterCheck = async (minterName, minterGroupMembers, minterAdmins) => {
// Ensure we have addresses // Ensure we have addresses
if (!minterGroupMembers) { if (!minterGroupMembers) {
@ -879,6 +970,16 @@ const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCo
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button> <button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div> </div>
</div> </div>
${creator === userState.accountName ? `
<div style="margin-top: 0.8em;">
<button
style="padding: 10px; background: darkred; color: white; border-radius: 4px; cursor: pointer;"
onclick="deleteARCard('${cardIdentifier}', '${addRemoveIdentifierPrefix}')"
>
DELETE CARD
</button>
</div>
` : ''}
<div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;"> <div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;">
<div id="comments-container-${cardIdentifier}" class="comments-container"></div> <div id="comments-container-${cardIdentifier}" class="comments-container"></div>
<textarea id="new-comment-${cardIdentifier}" placeholder="Input your comment..." style="width: 100%; margin-top: 10px;"></textarea> <textarea id="new-comment-${cardIdentifier}" placeholder="Input your comment..." style="width: 100%; margin-top: 10px;"></textarea>

View File

@ -130,7 +130,7 @@ const loadAdminBoardPage = async () => {
if (refreshCardsButton) { if (refreshCardsButton) {
refreshCardsButton.addEventListener("click", async () => { refreshCardsButton.addEventListener("click", async () => {
const encryptedCardsContainer = document.getElementById("encrypted-cards-container") const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
encryptedCardsContainer.innerHTML = "<p>Refreshing cards...</p>" encryptedCardsContainer.innerHTML = `<p style="color:dodgerblue;">Refreshing cards...</p>`
await fetchAllEncryptedCards(true) await fetchAllEncryptedCards(true)
}) })
} }
@ -325,7 +325,7 @@ const extractEncryptedCardsMinterName = (cardIdentifier) => {
const fetchAllEncryptedCards = async (isRefresh = false) => { const fetchAllEncryptedCards = async (isRefresh = false) => {
const encryptedCardsContainer = document.getElementById("encrypted-cards-container") const encryptedCardsContainer = document.getElementById("encrypted-cards-container")
encryptedCardsContainer.innerHTML = "<p>Loading cards...</p>" encryptedCardsContainer.innerHTML = `<p style="color:dodgerblue;">Loading cards...</p>`
try { try {
const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0) const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0)
@ -1049,7 +1049,7 @@ const checkAndDisplayRemoveActions = async (adminYes, name, cardIdentifier) => {
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){ } else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
const totalAdmins = minterAdmins.length const totalAdmins = minterAdmins.length
const fortyPercent = totalAdmins * 0.40 const fortyPercent = totalAdmins * 0.40
minAdminCount = Math.round(fortyPercent) minAdminCount = Math.ceil(fortyPercent)
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`) console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
} }
if (isBlockPassed && userState.isMinterAdmin) { if (isBlockPassed && userState.isMinterAdmin) {
@ -1222,6 +1222,51 @@ const getNewestAdminCommentTimestamp = async (cardIdentifier) => {
} }
} }
// ADDED: A simple function to effectively 'delete' an Admin Board card
// by publishing an empty card with the same identifier and prefix
const deleteAdminCard = async (cardIdentifier) => {
try {
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.");
if (!confirmed) return;
// A minimal blank object
const blankData = {
header: "",
content: "",
links: [],
creator: userState.accountName,
timestamp: Date.now(),
poll: "" // or null. This ensures it won't appear as a valid poll card
};
let base64Data = await objectToBase64(blankData);
if (!base64Data) {
base64Data = btoa(JSON.stringify(blankData));
}
const verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName,
service: "MAIL_PRIVATE",
identifier: cardIdentifier,
data64: base64Data,
encrypt: true,
publicKeys: verifiedAdminPublicKeys
})
alert("Your card has been effectively deleted.");
// Now reload the existing Admin Board cards so the UI no longer shows the old card
await fetchAllEncryptedCards(true);
} catch (error) {
console.error("Error deleting Admin card:", error);
alert("Failed to delete the card. Check console for details.");
}
};
// Create the overall Minter Card HTML ----------------------------------------------- // Create the overall Minter Card HTML -----------------------------------------------
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => { const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
const { minterName, header, content, links, creator, timestamp, poll, topicMode } = cardData const { minterName, header, content, links, creator, timestamp, poll, topicMode } = cardData
@ -1286,9 +1331,9 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier) const removeActionsHtml = await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
showRemoveHtml = removeActionsHtml showRemoveHtml = removeActionsHtml
if (userVote === 0) { if (userVote === 0) {
cardColorCode = "rgba(1, 65, 39, 0.41)"; // or any green you want cardColorCode = "rgba(1, 128, 20, 0.35)"; // or any green you want
} else if (userVote === 1) { } else if (userVote === 1) {
cardColorCode = "rgba(55, 12, 12, 0.61)"; // or any red you want cardColorCode = "rgba(124, 6, 6, 0.45)"; // or any red you want
} }
if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){ if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
@ -1367,6 +1412,16 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button> <button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div> </div>
</div> </div>
${creator === userState.accountName ? `
<div style="margin-top: 0.8em;">
<button
style="padding: 10px; background: darkred; color: white; border-radius: 4px; cursor: pointer;"
onclick="deleteAdminCard('${cardIdentifier}')"
>
DELETE CARD
</button>
</div>
` : ''}
<div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;"> <div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;">
<div id="comments-container-${cardIdentifier}" class="comments-container"></div> <div id="comments-container-${cardIdentifier}" class="comments-container"></div>
<textarea id="new-comment-${cardIdentifier}" placeholder="Input your comment..." style="width: 100%; margin-top: 10px;"></textarea> <textarea id="new-comment-${cardIdentifier}" placeholder="Input your comment..." style="width: 100%; margin-top: 10px;"></textarea>

View File

@ -28,10 +28,10 @@ async function loadMinterAdminToolsPage() {
<img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;"> <img src="${avatarUrl}" alt="User Avatar" class="user-avatar" style="width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;">
<span>${userState.accountName || 'Guest'}</span> <span>${userState.accountName || 'Guest'}</span>
</div> </div>
<div><h2>No Functionality Here Yet</h2></div> <div><h2>COMING SOON...</h2></div>
<div> <div>
<p>This page is still under development. Until the final Mintership proposal modifications are made, and the MINTER group is transferred to null, there is no need for this page's functionality. The page will be updated when the final modifications are made.</p> <p>This page will have functionality to assist the Minter Admins in performing their duties. It will display all pending transactions (of any kind they can approve/deny) along with that ability. It can also be utilized to obtain more in-depth information about existing accounts.</p>
<p> This page until then is simply a placeholder.</p> <p> The page will be getting a significant overhaul in the near(ish) future, as the MINTER group is now owned by null, and we are past the 'temporary state' we were in for much longer than planned.</p>
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@ const MIN_ADMIN_YES_VOTES = 9;
const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 2012800 //TODO update this to correct featureTrigger height when known, either that, or pull from core. const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 2012800 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
let featureTriggerPassed = false let featureTriggerPassed = false
let isApproved = false let isApproved = false
const spamNames = ["Exorcist"]
const loadMinterBoardPage = async () => { const loadMinterBoardPage = async () => {
// Clear existing content on the page // Clear existing content on the page
@ -101,7 +101,7 @@ const loadMinterBoardPage = async () => {
document.getElementById("refresh-cards-button").addEventListener("click", async () => { document.getElementById("refresh-cards-button").addEventListener("click", async () => {
const cardsContainer = document.getElementById("cards-container") const cardsContainer = document.getElementById("cards-container")
cardsContainer.innerHTML = "<p>Refreshing cards...</p>" cardsContainer.innerHTML = `<p style="color:dodgerblue;">Refreshing cards...</p>`
await loadCards(minterCardIdentifierPrefix) await loadCards(minterCardIdentifierPrefix)
}) })
@ -265,7 +265,7 @@ const processMinterCards = async (validMinterCards) => {
const loadCards = async (cardIdentifierPrefix) => { const loadCards = async (cardIdentifierPrefix) => {
const cardsContainer = document.getElementById("cards-container") const cardsContainer = document.getElementById("cards-container")
let isARBoard = false let isARBoard = false
cardsContainer.innerHTML = "<p>Loading cards...</p>" cardsContainer.innerHTML = `<p style="color:dodgerblue;">Loading cards...</p>`
if ((cardIdentifierPrefix.startsWith(`QM-AR-card`))) { if ((cardIdentifierPrefix.startsWith(`QM-AR-card`))) {
isARBoard = true isARBoard = true
console.warn(`ARBoard determined:`, isARBoard) console.warn(`ARBoard determined:`, isARBoard)
@ -1021,6 +1021,11 @@ const displayComments = async (cardIdentifier) => {
const commentHTMLArray = await Promise.all( const commentHTMLArray = await Promise.all(
comments.map(async (comment) => { comments.map(async (comment) => {
try { try {
// If the name of the commenter is in the "spamNames" array, return null
if (spamNames.includes(comment.name)) {
console.warn(`Commenter ${comment.name} is in the spamNames array, skipping...`)
return null
}
const commentDataResponse = await qortalRequest({ const commentDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE", action: "FETCH_QDN_RESOURCE",
name: comment.name, name: comment.name,
@ -1297,13 +1302,23 @@ const handleInviteMinter = async (minterName) => {
} }
} }
function escapeForHtmlAttribute(str) {
return str
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;');
}
const createInviteButtonHtml = (creator, cardIdentifier) => { const createInviteButtonHtml = (creator, cardIdentifier) => {
// Safely escape special chars so they won't break the HTML attribute
const safeCreator = escapeForHtmlAttribute(creator);
return ` return `
<div id="invite-button-container-${cardIdentifier}" style="margin-top: 1em;"> <div id="invite-button-container-${cardIdentifier}" style="margin-top: 1em;">
<button onclick="handleInviteMinter('${creator}')" <button
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;" onclick="handleInviteMinter('${safeCreator}')"
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '" style="padding: 10px; background:rgb(0, 109, 76); color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;"
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '" onmouseover="this.style.backgroundColor='rgb(25, 47, 39)'"
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63)'"
> >
Create Minter Invite Create Minter Invite
</button> </button>
@ -1327,17 +1342,12 @@ const featureTriggerCheck = async () => {
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => { const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
if (!userState.isMinterAdmin){
console.warn(`User is NOT an admin, not displaying invite/approve button...`)
return null
}
const isBlockPassed = await featureTriggerCheck() const isBlockPassed = await featureTriggerCheck()
const minterAdmins = await fetchMinterGroupAdmins() const minterAdmins = await fetchMinterGroupAdmins()
let minAdminCount = 9 let minAdminCount = 9
if (isBlockPassed) { if (isBlockPassed) {
minAdminCount = Math.round(minterAdmins.length * 0.4) minAdminCount = Math.ceil(minterAdmins.length * 0.4)
console.warn(`Using 40% => ${minAdminCount}`) console.warn(`Using 40% => ${minAdminCount}`)
} }
@ -1378,7 +1388,7 @@ const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) =>
console.warn(`PriorBanOrKick determination:`, priorBanOrKick) console.warn(`PriorBanOrKick determination:`, priorBanOrKick)
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier) const inviteButtonHtml = userState.isMinterAdmin ? createInviteButtonHtml(creator, cardIdentifier) : ''
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE") const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
if (!priorBanOrKick) { if (!priorBanOrKick) {
@ -1467,7 +1477,8 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
Existing ${transactionType} Approvals: ${uniqueApprovalCount} Existing ${transactionType} Approvals: ${uniqueApprovalCount}
</p> </p>
${tableHtml} ${tableHtml}
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;"> ${userState.isMinterAdmin ?
`<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
<button <button
style=" style="
padding: 8px; padding: 8px;
@ -1484,7 +1495,8 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
> >
Approve Invite Tx Approve Invite Tx
</button> </button>
</div> </div>`
: ''}
</div> </div>
` `
return approvalButtonHtml return approvalButtonHtml
@ -1644,9 +1656,19 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
// Format the transaction timestamp // Format the transaction timestamp
const dateStr = new Date(tx.timestamp).toLocaleString() const dateStr = new Date(tx.timestamp).toLocaleString()
// Check whether this is the current user
const isCurrentUser =
userState &&
userState.accountName &&
adminName &&
adminName.toLowerCase() === userState.accountName.toLowerCase();
// If it's the current user, highlight the row (change to any color/style you prefer)
const rowStyle = isCurrentUser
? "background: rgba(178, 255, 89, 0.2);" // light green highlight
: "";
return ` return `
<tr> <tr style="${rowStyle}">
<td style="border: 1px solid rgb(255, 255, 255); padding: 4px; color: #234565">${displayName}</td> <td style="border: 1px solid rgb(255, 255, 255); padding: 4px; color: dodgerblue">${displayName}</td>
<td style="border: 1px solid rgb(255, 254, 254); padding: 4px;">${dateStr}</td> <td style="border: 1px solid rgb(255, 254, 254); padding: 4px;">${dateStr}</td>
</tr> </tr>
` `
@ -1659,6 +1681,8 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
// 4) Wrap the table in a container with horizontal scroll: // 4) Wrap the table in a container with horizontal scroll:
// 1) max-width: 100% makes it fit the parent (card) width // 1) max-width: 100% makes it fit the parent (card) width
// 2) overflow-x: auto allows scrolling if the table is too wide // 2) overflow-x: auto allows scrolling if the table is too wide
// TODO - if "Admin Name" == userState.accountName, then table row should be highlighted
const containerHtml = ` const containerHtml = `
<div style="max-width: 100%; overflow-x: auto;"> <div style="max-width: 100%; overflow-x: auto;">
<table style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;"> <table style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;">
@ -1788,6 +1812,47 @@ const getNewestCommentTimestamp = async (cardIdentifier) => {
} }
} }
// ADDED: A simple function to effectively 'delete' a Minter Board card
// by publishing an empty card with the same identifier and prefix
const deleteCard = async (cardIdentifier, prefix) => {
try {
const confirmed = confirm("Are you sure you want to delete this card? This action cannot be undone.");
if (!confirmed) return;
// A minimal blank object
const blankData = {
header: "",
content: "",
links: [],
creator: userState.accountName,
timestamp: Date.now(),
poll: "" // or null. This ensures it won't appear as a valid poll card
};
let base64Data = await objectToBase64(blankData);
if (!base64Data) {
base64Data = btoa(JSON.stringify(blankData));
}
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: userState.accountName,
service: "BLOG_POST",
identifier: cardIdentifier,
data64: base64Data,
});
alert("Your card has been effectively deleted.");
// Now reload the existing Minter Board cards so the UI no longer shows the old card
await loadCards(prefix);
} catch (error) {
console.error("Error deleting Minter card:", error);
alert("Failed to delete the card. Check console for details.");
}
};
// Create the overall Minter Card HTML ----------------------------------------------- // Create the overall Minter Card HTML -----------------------------------------------
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, address) => { const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, address) => {
const { header, content, links, creator, timestamp, poll } = cardData const { header, content, links, creator, timestamp, poll } = cardData
@ -1895,6 +1960,16 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button> <button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div> </div>
</div> </div>
${creator === userState.accountName ? `
<div style="margin-top: 0.8em;">
<button
style="padding: 10px; background: darkred; color: white; border-radius: 4px; cursor: pointer;"
onclick="deleteCard('${cardIdentifier}', '${minterCardIdentifierPrefix}')"
>
DELETE CARD
</button>
</div>
` : ''}
<div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;"> <div id="comments-section-${cardIdentifier}" class="comments-section" style="display: none; margin-top: 20px;">
<div id="comments-container-${cardIdentifier}" class="comments-container"></div> <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> <textarea id="new-comment-${cardIdentifier}" placeholder="Write a comment..." style="width: 100%; margin-top: 10px;"></textarea>

View File

@ -331,7 +331,8 @@ const getNameInfo = async (name) => {
console.log('getNameInfo called') console.log('getNameInfo called')
console.log('name:', name) console.log('name:', name)
try { try {
const response = await fetch(`${baseUrl}/names/${name}`) // Encode the name for URL safety
const response = await fetch(`${baseUrl}/names/${encodeURIComponent(name)}`)
if (!response.ok) { if (!response.ok) {
console.warn(`Failed to fetch name info for: ${name}, status: ${response.status}`) console.warn(`Failed to fetch name info for: ${name}, status: ${response.status}`)

View File

@ -30,6 +30,10 @@
<link href="./assets/quill/quill.snow.css" rel="stylesheet"> <link href="./assets/quill/quill.snow.css" rel="stylesheet">
</head> </head>
<body> <body>
<script>
// Define one variable for your version string:
const Q_MINTERSHIP_VERSION = "1.03"; // Update here in the future
</script>
<section data-bs-version="5.1" class="menu menu1 boldm5 cid-ttRnktJ11Q" once="menu" id="menu1-0"> <section data-bs-version="5.1" class="menu menu1 boldm5 cid-ttRnktJ11Q" once="menu" id="menu1-0">
@ -42,7 +46,7 @@
</a> </a>
</span> </span>
<span class="navbar-caption-wrap"> <span class="navbar-caption-wrap">
<a class="navbar-caption display-4" href="index.html">Q-Mintership (v1.02b) <a class="navbar-caption display-4" href="index.html"><span id="versionString1" class="navbar-caption display-4"></span>
</a> </a>
</span> </span>
</div> </div>
@ -61,7 +65,7 @@
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt=""> <img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
</a> </a>
</span> </span>
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership v1.02b<br></a></span> <span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html"><span id="versionString2" class="navbar-caption text-primary display-4"></span></a></span>
</div> </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> <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>
@ -191,6 +195,24 @@
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6"> <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">
v1.03beta 01-23-2025</h2>
</div>
</div>
<div class="col-12 col-lg-5 card">
<div class="text-wrapper">
<p class="mbr-text mbr-fonts-style display-7">
<b><u>v1.03b Fixes</u></b>- <b>Filtering issue resolved </b> - Version 1.02 had a filtering logic modification applied to add and remove admin transactions. However, it was not changed on the REMOVE filtering, so REMOVE transactions that were PENDING were showing as COMPLETE and thus the board was displaying cards as already removed when there was only a PENDING tx. This has been resolved in 1.03b.
</p>
</div>
</div>
</div>
</div>
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-lg-7 card"> <div class="col-12 col-lg-7 card">
@ -536,12 +558,12 @@
<div class="title-wrapper"> <div class="title-wrapper">
<div class="title-wrap"> <div class="title-wrap">
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt=""> <img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
<h2 class="mbr-section-title mbr-fonts-style display-5">Q-Mintership (v1.02b)</h2> <h2 class="mbr-section-title mbr-fonts-style display-5"><span id="versionString3" class="mbr-section-title mbr-fonts-style display-5"></span></h2>
</div> </div>
</div> </div>
<a class="link-wrap" href="#"> <a class="link-wrap" href="#">
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v1.02beta</p> <p class="mbr-link mbr-fonts-style display-4"><span id="versionString4" class="mbr-link mbr-fonts-style display-4"></span></p>
</a> </a>
</div> </div>
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
@ -554,6 +576,12 @@
</div> </div>
</section> </section>
<script>
document.getElementById("versionString1").textContent = `Q-Mintership (v${Q_MINTERSHIP_VERSION}b)`;
document.getElementById("versionString2").textContent = `Q-Mintership v${Q_MINTERSHIP_VERSION}b`;
document.getElementById("versionString3").textContent = `Q-Mintership (v${Q_MINTERSHIP_VERSION}b)`;
document.getElementById("versionString4").textContent = `Q-Mintership v${Q_MINTERSHIP_VERSION}beta`;
</script>
<script src="./assets/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="./assets/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="./assets/parallax/jarallax.js"></script> <script src="./assets/parallax/jarallax.js"></script>