testing-20250123 #6
@ -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,9 +191,8 @@ 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch ban transactions
|
// Fetch ban transactions
|
||||||
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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,14 +1302,24 @@ const handleInviteMinter = async (minterName) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeForHtmlAttribute(str) {
|
||||||
|
return str
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
@ -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>
|
||||||
|
@ -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}`)
|
||||||
|
36
index.html
36
index.html
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user