Version 1.05b - see release notes published on Q-Mintership forum in General room for details. Many fixes and new features added.
This commit is contained in:
parent
5a35fb0d07
commit
021c99c119
@ -461,6 +461,27 @@
|
||||
/* General Page Styles */
|
||||
/* Main Container for Minter Board */
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 25px; /* Adjust width */
|
||||
height: 25px; /* Adjust height */
|
||||
appearance: none; /* Remove default styling */
|
||||
background-color: rgb(15, 21, 22);
|
||||
border: 2px solid rgb(98, 99, 106);
|
||||
border-radius: 4px; /* Optional rounded corners */
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked::after {
|
||||
content: "✔"; /* Custom checkmark */
|
||||
font-size: 20px;
|
||||
color: rgb(201, 201, 201);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
@ -555,6 +576,65 @@ body {
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
|
||||
.blocklist-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #1b1b1b; /* or your dark color */
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
margin: 20px auto; /* center horizontally */
|
||||
max-width: 600px; /* limit width */
|
||||
color: #ddd; /* text color */
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.blocklist-display {
|
||||
/* This holds the list of blocked names */
|
||||
border: 1px dashed;
|
||||
background-color:#000000;
|
||||
width: 90%;
|
||||
font-size: 1.8rem;
|
||||
color: #4d0000;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
/* you could style the list items or bullet if you like */
|
||||
}
|
||||
|
||||
.blocklist-button-container {
|
||||
/* This area will hold the text input + the row for buttons */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.blocklist-form input.blocklist-input {
|
||||
padding: 1rem;
|
||||
font-size: 2rem;
|
||||
line-height: 2;
|
||||
background-color: #14161a;
|
||||
border: 1px solid #8caeb0;
|
||||
border-radius: 4px;
|
||||
color: #5c0101;
|
||||
}
|
||||
|
||||
.publish-card-button {
|
||||
background-color: #529d8d84;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.publish-card-button:hover {
|
||||
background-color: #3b5c71; /* a darker variant */
|
||||
}
|
||||
|
||||
|
||||
.publish-card-view textarea {
|
||||
min-height: 15vh;
|
||||
resize: vertical;
|
||||
@ -570,9 +650,9 @@ body {
|
||||
margin-bottom: 1.5vh;
|
||||
}
|
||||
|
||||
/* Buttons Inside Form */
|
||||
#publish-card-form button {
|
||||
background-color: #76c7c0;
|
||||
/* Generic: all buttons inside .publish-card-form */
|
||||
.publish-card-form button {
|
||||
background-color: #359f4a;
|
||||
color: #1e1e1e;
|
||||
border: none;
|
||||
border-radius: 0.5vh;
|
||||
@ -583,30 +663,76 @@ body {
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#publish-card-form button:hover {
|
||||
.publish-card-form button:hover {
|
||||
background-color: #5e92a8;
|
||||
}
|
||||
|
||||
#publish-card-form #add-link-button {
|
||||
/* Then specifically override the add button */
|
||||
.publish-card-form #blocklist-add-button {
|
||||
background-color: #233748;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.publish-card-form #blocklist-add-button:hover {
|
||||
background-color: #1b1936;
|
||||
}
|
||||
|
||||
.publish-card-form #add-link-button {
|
||||
background-color: #233748;
|
||||
color: #ffffff;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
|
||||
#publish-card-form #add-link-button:hover {
|
||||
.publish-card-form #add-link-button:hover {
|
||||
background-color: #1b1936;
|
||||
}
|
||||
|
||||
/* Cancel Button */
|
||||
#publish-card-form #cancel-publish-button {
|
||||
/* And specifically override the remove button */
|
||||
.publish-card-form #blocklist-remove-button {
|
||||
background-color: #463737;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#publish-card-form #cancel-publish-button:hover {
|
||||
.publish-card-form #blocklist-remove-button:hover {
|
||||
background-color: #281e1e;
|
||||
}
|
||||
|
||||
|
||||
.publish-card-form #cancel-publish-button {
|
||||
background-color: #463737;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.publish-card-form #cancel-publish-button:hover {
|
||||
background-color: #281e1e;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.publish-card-view {
|
||||
width: 90%;
|
||||
padding: 2vh;
|
||||
}
|
||||
|
||||
.publish-card-button {
|
||||
font-size: 1.8vh;
|
||||
padding: 1.5vh;
|
||||
}
|
||||
|
||||
.publish-card-form button {
|
||||
font-size: 1.8vh;
|
||||
padding: 1.2vh;
|
||||
}
|
||||
}
|
||||
|
||||
.refresh-cards-button {
|
||||
border-color: white;
|
||||
border-radius: 1.5vh;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.publish-card-view {
|
||||
@ -619,7 +745,7 @@ body {
|
||||
padding: 1.5vh;
|
||||
}
|
||||
|
||||
#publish-card-form button {
|
||||
.publish-card-form button {
|
||||
font-size: 1.8vh;
|
||||
padding: 1.2vh;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ const loadAddRemoveAdminPage = async () => {
|
||||
Propose a Minter for Admin Position
|
||||
</button>
|
||||
<div id="promotion-form-container" class="publish-card-view" style="display: none; margin-top: 1em;">
|
||||
<form id="publish-card-form">
|
||||
<form id="publish-card-form" class="publish-card-form">
|
||||
<h3>Create or Update Promotion/Demotion Proposal Card</h3>
|
||||
<label for="minter-name-input">Input NAME (promotion):</label>
|
||||
<input type="text" id="minter-name-input" maxlength="100" placeholder="input NAME of MINTER for PROMOTION" required>
|
||||
@ -59,7 +59,7 @@ 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;">
|
||||
<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>
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px;">
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;">
|
||||
<option value="0">Show All</option>
|
||||
<option value="1">Last 1 day</option>
|
||||
<option value="7">Last 7 days</option>
|
||||
@ -176,7 +176,7 @@ const fetchAllARTxData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
function partitionAddTransactions(rawTransactions) {
|
||||
const partitionAddTransactions = (rawTransactions) => {
|
||||
const finalAddTxs = []
|
||||
const pendingAddTxs = []
|
||||
|
||||
@ -191,7 +191,7 @@ function partitionAddTransactions(rawTransactions) {
|
||||
return { finalAddTxs, pendingAddTxs };
|
||||
}
|
||||
|
||||
function partitionRemoveTransactions(rawTransactions) {
|
||||
const partitionRemoveTransactions = (rawTransactions) => {
|
||||
const finalRemTxs = []
|
||||
const pendingRemTxs = []
|
||||
|
||||
@ -434,14 +434,16 @@ const publishARCard = async (cardIdentifierPrefix) => {
|
||||
|
||||
if (exists) {
|
||||
alert(`An existing card was found, you must update it, two cards for the samme name cannot be published! Loading card data...`)
|
||||
if (exists.creator != userState.accountName) {
|
||||
alert(`You are not the original publisher of this card, exiting.`)
|
||||
return
|
||||
}else {
|
||||
await loadCardIntoForm(existingCardData)
|
||||
minterName = exists.minterName
|
||||
const nameInfo = await getNameInfo(exists.minterName)
|
||||
address = nameInfo.owner
|
||||
isExistingCard = true
|
||||
} else if (otherPublisher){
|
||||
alert(`An existing card was found, but you are NOT the publisher, you may not publish duplicates, and you may not update a non-owned card! Please try again with another name, or use the existing card for ${minterNameInput}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const minterGroupData = await fetchMinterGroupMembers()
|
||||
@ -481,6 +483,7 @@ const publishARCard = async (cardIdentifierPrefix) => {
|
||||
|
||||
const cardData = {
|
||||
minterName,
|
||||
minterAddress: address,
|
||||
header,
|
||||
content,
|
||||
links,
|
||||
@ -550,7 +553,7 @@ const checkAndDisplayActions = async (adminYes, name, cardIdentifier) => {
|
||||
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
||||
const totalAdmins = minterAdmins.length
|
||||
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}`)
|
||||
}
|
||||
const addressInfo = await getNameInfo(name)
|
||||
@ -735,7 +738,7 @@ const fallbackMinterCheck = async (minterName, minterGroupMembers, minterAdmins)
|
||||
|
||||
|
||||
const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, cardPublisherAddress, illegalDuplicate) => {
|
||||
const { minterName, minterAddress='priorToAddition', header, content, links, creator, timestamp, poll, promotionCard } = cardData
|
||||
const { minterName, minterAddress='', header, content, links, creator, timestamp, poll, promotionCard } = cardData
|
||||
const formattedDate = new Date(timestamp).toLocaleString()
|
||||
const minterAvatar = await getMinterAvatar(minterName)
|
||||
const creatorAvatar = await getMinterAvatar(creator)
|
||||
@ -744,6 +747,14 @@ const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCo
|
||||
${`Link ${index + 1} - ${link}`}
|
||||
</button>
|
||||
`).join("")
|
||||
// Adding fix for accidental code in 1.04b
|
||||
let publishedMinterAddress
|
||||
if (!minterAddress || minterAddress ==='priorToAddition'){
|
||||
publishedMinterAddress = ''
|
||||
} else if (minterAddress){
|
||||
console.log(`minter address found in card info: ${minterAddress}`)
|
||||
publishedMinterAddress = minterAddress
|
||||
}
|
||||
|
||||
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||
const minterAdmins = await fetchMinterGroupAdmins()
|
||||
|
@ -10,8 +10,8 @@ let isTopic = false
|
||||
let attemptLoadAdminDataCount = 0
|
||||
let adminMemberCount = 0
|
||||
let adminPublicKeys = []
|
||||
let kickTransactions = []
|
||||
let banTransactions = []
|
||||
// let kickTransactions = []
|
||||
// let banTransactions = []
|
||||
let adminBoardState = {
|
||||
kickedCards: new Set(), // store identifiers
|
||||
bannedCards: new Set(), // likewise
|
||||
@ -76,14 +76,14 @@ const loadAdminBoardPage = async () => {
|
||||
<p> More functionality will be added over time. One of the first features will be the ability to output the existing card data 'decisions', to a json formatted list in order to allow crowetic to run his script easily until the final Mintership proposal changes are completed, and the MINTER group is transferred to 'null'.</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>
|
||||
<select id="sort-select" style="margin-left: 10px; padding: 5px;">
|
||||
<select id="sort-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color:rgb(70, 106, 105); background-color: black;">
|
||||
<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>
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px;">
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;">
|
||||
<option value="0">Show All</option>
|
||||
<option value="1">Last 1 day</option>
|
||||
<option value="7">Last 7 days</option>
|
||||
@ -98,7 +98,7 @@ const loadAdminBoardPage = async () => {
|
||||
</div>
|
||||
<div id="encrypted-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">
|
||||
<form id="publish-card-form" class="publish-card-form">
|
||||
<h3>Create or Update an Admin Card</h3>
|
||||
<div class="publish-card-checkbox" style="margin-top: 1em;">
|
||||
<input type="checkbox" id="topic-checkbox" name="topicMode" />
|
||||
@ -195,68 +195,67 @@ const loadAdminBoardPage = async () => {
|
||||
createScrollToTopButton()
|
||||
// await fetchAndValidateAllAdminCards()
|
||||
await updateOrSaveAdminGroupsDataLocally()
|
||||
await fetchAllKicKBanTxData()
|
||||
await fetchAllEncryptedCards()
|
||||
}
|
||||
|
||||
const fetchAllKicKBanTxData = async () => {
|
||||
const kickTxType = "GROUP_KICK"
|
||||
const banTxType = "GROUP_BAN"
|
||||
// const fetchAllKicKBanTxData = async () => {
|
||||
// const kickTxType = "GROUP_KICK"
|
||||
// const banTxType = "GROUP_BAN"
|
||||
|
||||
// Helper function to filter transactions
|
||||
const filterTransactions = (rawTransactions) => {
|
||||
// Group transactions by member
|
||||
const memberTxMap = rawTransactions.reduce((map, tx) => {
|
||||
if (!map[tx.member]) {
|
||||
map[tx.member] = []
|
||||
}
|
||||
map[tx.member].push(tx)
|
||||
return map
|
||||
}, {})
|
||||
// // Helper function to filter transactions
|
||||
// const filterTransactions = (rawTransactions) => {
|
||||
// // Group transactions by member
|
||||
// const memberTxMap = rawTransactions.reduce((map, tx) => {
|
||||
// if (!map[tx.member]) {
|
||||
// map[tx.member] = []
|
||||
// }
|
||||
// map[tx.member].push(tx)
|
||||
// return map
|
||||
// }, {})
|
||||
|
||||
// Filter out members with both pending and non-pending transactions
|
||||
return Object.values(memberTxMap)
|
||||
.filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
|
||||
.flat()
|
||||
// .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
|
||||
// txs.some(tx => tx.approvalStatus !== 'PENDING')))
|
||||
// // Filter out members with both pending and non-pending transactions
|
||||
// return Object.values(memberTxMap)
|
||||
// .filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
|
||||
// .flat()
|
||||
}
|
||||
// // .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
|
||||
// // txs.some(tx => tx.approvalStatus !== 'PENDING')))
|
||||
// // .flat()
|
||||
// }
|
||||
|
||||
// Fetch ban transactions
|
||||
const rawBanTransactions = await searchTransactions({
|
||||
txTypes: [banTxType],
|
||||
address: '',
|
||||
confirmationStatus: 'CONFIRMED',
|
||||
limit: 0,
|
||||
reverse: true,
|
||||
offset: 0,
|
||||
startBlock: 1990000,
|
||||
blockLimit: 0,
|
||||
txGroupId: 0,
|
||||
})
|
||||
// // Fetch ban transactions
|
||||
// const rawBanTransactions = await searchTransactions({
|
||||
// txTypes: [banTxType],
|
||||
// address: '',
|
||||
// confirmationStatus: 'CONFIRMED',
|
||||
// limit: 0,
|
||||
// reverse: true,
|
||||
// offset: 0,
|
||||
// startBlock: 1990000,
|
||||
// blockLimit: 0,
|
||||
// txGroupId: 0,
|
||||
// })
|
||||
|
||||
// Filter transactions for bans
|
||||
banTransactions = filterTransactions(rawBanTransactions)
|
||||
console.warn('banTxData (filtered):', banTransactions)
|
||||
// // Filter transactions for bans
|
||||
// banTransactions = filterTransactions(rawBanTransactions)
|
||||
// console.warn('banTxData (filtered):', banTransactions)
|
||||
|
||||
// Fetch kick transactions
|
||||
const rawKickTransactions = await searchTransactions({
|
||||
txTypes: [kickTxType],
|
||||
address: '',
|
||||
confirmationStatus: 'CONFIRMED',
|
||||
limit: 0,
|
||||
reverse: true,
|
||||
offset: 0,
|
||||
startBlock: 1990000,
|
||||
blockLimit: 0,
|
||||
txGroupId: 0,
|
||||
})
|
||||
// // Fetch kick transactions
|
||||
// const rawKickTransactions = await searchTransactions({
|
||||
// txTypes: [kickTxType],
|
||||
// address: '',
|
||||
// confirmationStatus: 'CONFIRMED',
|
||||
// limit: 0,
|
||||
// reverse: true,
|
||||
// offset: 0,
|
||||
// startBlock: 1990000,
|
||||
// blockLimit: 0,
|
||||
// txGroupId: 0,
|
||||
// })
|
||||
|
||||
// Filter transactions for kicks
|
||||
kickTransactions = filterTransactions(rawKickTransactions)
|
||||
console.warn('kickTxData (filtered):', kickTransactions)
|
||||
}
|
||||
// // Filter transactions for kicks
|
||||
// kickTransactions = filterTransactions(rawKickTransactions)
|
||||
// console.warn('kickTxData (filtered):', kickTransactions)
|
||||
// }
|
||||
|
||||
|
||||
// Example: fetch and save admin public keys and count
|
||||
@ -744,16 +743,30 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
||||
|
||||
// If not topic mode, validate the user actually entered a valid Minter name
|
||||
if (!isTopic) {
|
||||
let minterAddress
|
||||
publishedMinterName = await validateMinterName(minterNameInput)
|
||||
if (!publishedMinterName) {
|
||||
alert(`"${minterNameInput}" doesn't seem to be a valid name. Please check or use topic mode.`)
|
||||
try {
|
||||
const addressInfo = await getAddressInfo(minterNameInput)
|
||||
if (addressInfo) {
|
||||
console.warn(`checked minterNameInput and found it to be an address... proceeding accordingly.`)
|
||||
minterAddress = addressInfo.address
|
||||
publishedMinterName = addressInfo.address
|
||||
} else {
|
||||
alert(`"${minterNameInput}" doesn't seem to be a valid name or address. Please check or use topic mode.`)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`error checking for address...?`, error)
|
||||
alert(`Failed to verify name/address. Please try again, or change to topicMode to publish anything else.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Also check for existing card if not topic
|
||||
if (!isUpdateCard && existingCardMinterNames.some(item => item.minterName === publishedMinterName)) {
|
||||
const duplicateCardData = existingCardMinterNames.find(item => item.minterName === publishedMinterName)
|
||||
const updateCard = confirm(
|
||||
`Minter Name: ${publishedMinterName} already has a card. Duplicate name-based cards are not allowed. You can OVERWRITE it or Cancel publishing. UPDATE CARD?`
|
||||
`Minter Name: ${publishedMinterName} already has a card. (NOTE this update functionality is no longer functional, it may or may not come back. Even if you update the card you won't see it. It is suggested to CANCEL and use topic mode.`
|
||||
)
|
||||
if (updateCard) {
|
||||
existingEncryptedCardIdentifier = duplicateCardData.identifier
|
||||
@ -763,6 +776,9 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!publishedMinterName && minterAddress){
|
||||
console.log(`No name was found, but an address was, publishing address in cardData, and using address as name for card.`)
|
||||
}
|
||||
|
||||
// Determine final card identifier
|
||||
const currentTimestamp = Date.now()
|
||||
@ -1067,7 +1083,7 @@ const checkAndDisplayRemoveActions = async (adminYes, name, cardIdentifier, name
|
||||
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
||||
const totalAdmins = minterAdmins.length
|
||||
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}`)
|
||||
}
|
||||
if (isBlockPassed && (userState.isMinterAdmin || userState.isAdmin)) {
|
||||
@ -1260,7 +1276,7 @@ const getNewestAdminCommentTimestamp = async (cardIdentifier) => {
|
||||
|
||||
// Create the overall Minter Card HTML -----------------------------------------------
|
||||
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
|
||||
const { minterName, header, content, links, creator, timestamp, poll, topicMode } = cardData
|
||||
const { minterName, minterAddress = '', header, content, links, creator, timestamp, poll, topicMode } = cardData
|
||||
const formattedDate = new Date(timestamp).toLocaleString()
|
||||
const minterAvatar = !topicMode ? await getMinterAvatar(minterName) : null
|
||||
const creatorAvatar = await getMinterAvatar(creator)
|
||||
@ -1278,6 +1294,8 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
||||
|
||||
let showTopic = false
|
||||
|
||||
const { finalKickTxs, pendingKickTxs, finalBanTxs, pendingBanTxs } = await fetchAllKickBanTxData()
|
||||
|
||||
if (hasTopicMode) {
|
||||
const modeVal = cardData.topicMode
|
||||
showTopic = (modeVal === true || modeVal === 'true')
|
||||
@ -1321,20 +1339,53 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
||||
|
||||
const accountAddress = verifiedAddress ? addressVerification.address : accountInfo.owner
|
||||
const addressInfo = verifiedAddress ? addressVerification : await getAddressInfo(accountAddress)
|
||||
const minterGroupAddresses = minterGroupMembers.map(m => m.member)
|
||||
const adminAddresses = minterAdmins.map(m => m.member)
|
||||
const existingAdmin = adminAddresses.includes(accountAddress)
|
||||
const existingMinter = minterGroupAddresses.includes(accountAddress)
|
||||
|
||||
levelText = ` - Level ${addressInfo.level}</h3>`
|
||||
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
||||
penaltyText = addressInfo.blocksMintedPenalty == 0 ? '' : '<p>(has Blocks Penalty)<p>'
|
||||
adjustmentText = addressInfo.blocksMintedAdjustment == 0 ? '' : '<p>(has Blocks Adjustment)<p>'
|
||||
const removeActionsHtml = verifiedAddress ? await checkAndDisplayRemoveActions(adminYes, verifiedAddress, cardIdentifier) : await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
||||
const removeActionsHtml = verifiedAddress ? await checkAndDisplayRemoveActions(adminYes, verifiedAddress, cardIdentifier, true) : await checkAndDisplayRemoveActions(adminYes, verifiedName, cardIdentifier)
|
||||
showRemoveHtml = removeActionsHtml
|
||||
|
||||
if (userVote === 0) {
|
||||
cardColorCode = "rgba(1, 65, 39, 0.41)"; // or any green you want
|
||||
} else if (userVote === 1) {
|
||||
cardColorCode = "rgba(55, 12, 12, 0.61)"; // or any red you want
|
||||
}
|
||||
|
||||
if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
||||
const confirmedKick = finalKickTxs.some(
|
||||
(tx) => tx.groupId === 694 && tx.member === accountAddress
|
||||
)
|
||||
const pendingKick = pendingKickTxs.some(
|
||||
(tx) => tx.groupId === 694 && tx.member === accountAddress
|
||||
)
|
||||
const confirmedBan = finalBanTxs.some(
|
||||
(tx) => tx.groupId === 694 && tx.offender === accountAddress
|
||||
)
|
||||
const pendingBan = pendingBanTxs.some(
|
||||
(tx) => tx.groupId === 694 && tx.offender === accountAddress
|
||||
)
|
||||
|
||||
// If user is definitely admin (finalAdd) and not pending removal
|
||||
if (confirmedKick && !pendingKick && !existingMinter) {
|
||||
console.warn(`account was already kicked, displaying as such...`)
|
||||
cardColorCode = 'rgb(29, 7, 4)'
|
||||
altText = `<h4 style="color:rgb(143, 117, 21); margin-bottom: 0.5em;">KICKED From MINTER Group</h4>`
|
||||
showRemoveHtml = ''
|
||||
if (!adminBoardState.kickedCards.has(cardIdentifier)){
|
||||
adminBoardState.kickedCards.add(cardIdentifier)
|
||||
}
|
||||
if (!showKickedBanned) {
|
||||
console.warn(`kick/ban checkbox is unchecked, card is kicked, not displaying...`)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmedBan && !pendingBan && !pendingKick && !existingMinter) {
|
||||
console.warn(`account was already banned, displaying as such...`)
|
||||
cardColorCode = 'rgb(24, 3, 3)'
|
||||
altText = `<h4 style="color:rgb(106, 2, 2); margin-bottom: 0.5em;">BANNED From MINTER Group</h4>`
|
||||
@ -1348,19 +1399,33 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
||||
}
|
||||
}
|
||||
|
||||
if (kickTransactions.some((kickTx) => kickTx.groupId === 694 && kickTx.member === accountAddress)){
|
||||
console.warn(`account was already kicked, displaying as such...`)
|
||||
cardColorCode = 'rgb(29, 7, 4)'
|
||||
altText = `<h4 style="color:rgb(143, 117, 21); margin-bottom: 0.5em;">KICKED From MINTER Group</h4>`
|
||||
showRemoveHtml = ''
|
||||
if (!adminBoardState.kickedCards.has(cardIdentifier)){
|
||||
adminBoardState.kickedCards.add(cardIdentifier)
|
||||
}
|
||||
if (!showKickedBanned) {
|
||||
console.warn(`kick/ban checkbox is unchecked, card is kicked, not displaying...`)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
||||
// console.warn(`account was already banned, displaying as such...`)
|
||||
// cardColorCode = 'rgb(24, 3, 3)'
|
||||
// altText = `<h4 style="color:rgb(106, 2, 2); margin-bottom: 0.5em;">BANNED From MINTER Group</h4>`
|
||||
// showRemoveHtml = ''
|
||||
// if (!adminBoardState.bannedCards.has(cardIdentifier)){
|
||||
// adminBoardState.bannedCards.add(cardIdentifier)
|
||||
// }
|
||||
// if (!showKickedBanned){
|
||||
// console.warn(`kick/bank checkbox is unchecked, and card is banned, not displaying...`)
|
||||
// return ''
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (kickTransactions.some((kickTx) => kickTx.groupId === 694 && kickTx.member === accountAddress)){
|
||||
// console.warn(`account was already kicked, displaying as such...`)
|
||||
// cardColorCode = 'rgb(29, 7, 4)'
|
||||
// altText = `<h4 style="color:rgb(143, 117, 21); margin-bottom: 0.5em;">KICKED From MINTER Group</h4>`
|
||||
// showRemoveHtml = ''
|
||||
// if (!adminBoardState.kickedCards.has(cardIdentifier)){
|
||||
// adminBoardState.kickedCards.add(cardIdentifier)
|
||||
// }
|
||||
// if (!showKickedBanned) {
|
||||
// console.warn(`kick/ban checkbox is unchecked, card is kicked, not displaying...`)
|
||||
// return ''
|
||||
// }
|
||||
// }
|
||||
|
||||
} else {
|
||||
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
|
||||
|
@ -1,121 +1,134 @@
|
||||
let currentMinterToolPage = 'overview'; // Track the current page
|
||||
|
||||
// Load latest state for admin verification
|
||||
async function verifyMinterAdminState() {
|
||||
const minterGroupAdmins = await fetchMinterGroupAdmins();
|
||||
return minterGroupAdmins.members.some(admin => admin.member === userState.accountAddress && admin.isAdmin);
|
||||
}
|
||||
|
||||
async function loadMinterAdminToolsPage() {
|
||||
const loadMinterAdminToolsPage = async () => {
|
||||
// Remove all body content except for menu elements
|
||||
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();
|
||||
child.remove()
|
||||
}
|
||||
}
|
||||
|
||||
const avatarUrl = `/arbitrary/THUMBNAIL/${userState.accountName}/qortal_avatar`;
|
||||
const avatarUrl = `/arbitrary/THUMBNAIL/${userState.accountName}/qortal_avatar`
|
||||
|
||||
// Set the background image directly from a file
|
||||
const mainContent = document.createElement('div');
|
||||
const mainContent = document.createElement('div')
|
||||
// In your 'AdminTools' code
|
||||
mainContent.innerHTML = `
|
||||
<div class="tools-main mbr-parallax-background cid-ttRnlSkg2R">
|
||||
<div class="tools-header" style="color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 10px;">
|
||||
<div> <h1 style="font-size: 50px; margin: 0;">MINTER ADMIN TOOLS </h1><a style="color: red;">Under Construction...</a></div>
|
||||
<div><h1 style="font-size: 50px; margin: 0;">Admin Tools</h1></div>
|
||||
<div class="user-info" style="border: 1px solid lightblue; padding: 5px; color: lightblue; display: flex; align-items: center; justify-content: center;">
|
||||
<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>
|
||||
</div>
|
||||
<div><h2>COMING SOON...</h2></div>
|
||||
<div><h2>Welcome to Admin Tools</h2></div>
|
||||
<div>
|
||||
<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> 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>
|
||||
<p>On this page you will find admin functionality for the Q-Mintership App. Including the 'blockList' for blocking comments from certain names, and manual creation of invite transactions.</p>
|
||||
<p>More features will be added as time goes on. This is the start of the functionality here.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tools-submenu" class="tools-submenu">
|
||||
<div class="tools-buttons">
|
||||
<button id="display-pending" class="tools-button">Display Pending Approval Transactions</button>
|
||||
<button id="create-group-invite" class="tools-button">Create Pending Group Invite</button>
|
||||
<button id="create-promotion" class="tools-button">Create Pending Promotion</button>
|
||||
<div class="tools-buttons" style="display: flex; gap: 1em; justify-content: center;">
|
||||
<button id="toggle-blocklist-button" class="publish-card-button">Add/Remove blockedUsers</button>
|
||||
<button id="create-group-invite" class="publish-card-button">Create Pending Group Invite</button>
|
||||
</div>
|
||||
<div id="tools-window" class="tools-window"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(mainContent);
|
||||
|
||||
addToolsPageEventListeners();
|
||||
<div id="tools-window" class="tools-window" style="margin-top: 2em;">
|
||||
|
||||
<div id="blocklist-container" class="blocklist-form" style="display: none;">
|
||||
<h3 style="margin-top: 0;">Comment Block List</h3>
|
||||
<div id="blocklist-display" class="blocklist-display" style="margin-bottom: 1em;"></div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
id="blocklist-input"
|
||||
class="blocklist-input"
|
||||
placeholder="Enter name to block/unblock"
|
||||
style="margin-bottom: 1em;"
|
||||
/>
|
||||
|
||||
<div class="blocklist-button-container publish-card-form">
|
||||
<button id="blocklist-add-button" class="publish-card-button">Add</button>
|
||||
<button id="blocklist-remove-button" class="publish-card-button">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
document.body.appendChild(mainContent)
|
||||
|
||||
addToolsPageEventListeners()
|
||||
}
|
||||
|
||||
function addToolsPageEventListeners() {
|
||||
document.getElementById("display-pending").addEventListener("click", async () => {
|
||||
await displayPendingApprovals();
|
||||
});
|
||||
document.getElementById("toggle-blocklist-button").addEventListener("click", async () => {
|
||||
const container = document.getElementById("blocklist-container")
|
||||
// toggle show/hide
|
||||
container.style.display = (container.style.display === "none" ? "flex" : "none")
|
||||
|
||||
document.getElementById("create-group-invite").addEventListener("click", async () => {
|
||||
createPendingGroupInvite();
|
||||
});
|
||||
// if showing, load the block list
|
||||
if (container.style.display === "flex") {
|
||||
const currentBlockList = await fetchBlockList()
|
||||
displayBlockList(currentBlockList)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById("create-promotion").addEventListener("click", async () => {
|
||||
createPendingPromotion();
|
||||
});
|
||||
document.getElementById("blocklist-add-button").addEventListener("click", async () => {
|
||||
const blocklistInput = document.getElementById("blocklist-input")
|
||||
const nameToAdd = blocklistInput.value.trim()
|
||||
if (!nameToAdd) return
|
||||
|
||||
// fetch existing
|
||||
const currentBlockList = await fetchBlockList()
|
||||
// add if not already in list
|
||||
if (!currentBlockList.includes(nameToAdd)) {
|
||||
currentBlockList.push(nameToAdd)
|
||||
}
|
||||
|
||||
// Fetch and display pending approvals
|
||||
async function displayPendingApprovals() {
|
||||
console.log("Fetching pending approval transactions...");
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_TRANSACTIONS",
|
||||
txGroupId: 694,
|
||||
txType: [
|
||||
"ADD_GROUP_ADMIN",
|
||||
"GROUP_INVITE"
|
||||
],
|
||||
confirmationStatus: "UNCONFIRMED",
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
reverse: false
|
||||
});
|
||||
// publish updated
|
||||
await publishBlockList(currentBlockList)
|
||||
displayBlockList(currentBlockList)
|
||||
blocklistInput.value = ""
|
||||
alert(`"${nameToAdd}" added to the block list!`)
|
||||
})
|
||||
|
||||
console.log("Fetched pending approvals: ", response);
|
||||
// Remove
|
||||
document.getElementById("blocklist-remove-button").addEventListener("click", async () => {
|
||||
const blocklistInput = document.getElementById("blocklist-input")
|
||||
const nameToRemove = blocklistInput.value.trim()
|
||||
if (!nameToRemove) return
|
||||
|
||||
// fetch existing
|
||||
let currentBlockList = await fetchBlockList()
|
||||
// remove if present
|
||||
currentBlockList = currentBlockList.filter(name => name !== nameToRemove)
|
||||
|
||||
// publish updated
|
||||
await publishBlockList(currentBlockList)
|
||||
displayBlockList(currentBlockList)
|
||||
blocklistInput.value = ""
|
||||
alert(`"${nameToRemove}" removed from the block list (if it was present).`)
|
||||
})
|
||||
|
||||
const toolsWindow = document.getElementById('tools-window');
|
||||
if (response && response.length > 0) {
|
||||
toolsWindow.innerHTML = response.map(tx => `
|
||||
<div class="message-item" style="border: 1px solid lightblue; padding: 10px; margin-bottom: 10px;">
|
||||
<p><strong>Transaction Type:</strong> ${tx.type}</p>
|
||||
<p><strong>Amount:</strong> ${tx.amount}</p>
|
||||
<p><strong>Creator Address:</strong> ${tx.creatorAddress}</p>
|
||||
<p><strong>Recipient:</strong> ${tx.recipient}</p>
|
||||
<p><strong>Timestamp:</strong> ${new Date(tx.timestamp).toLocaleString()}</p>
|
||||
<button onclick="approveTransaction('${tx.signature}')">Approve</button>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
toolsWindow.innerHTML = '<div class="message-item" style="border: 1px solid lightblue; padding: 10px; margin-bottom: 10px;"><p>No pending approvals found.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder function to create a pending group invite
|
||||
async function createPendingGroupInvite() {
|
||||
console.log("Creating a pending group invite...");
|
||||
// Placeholder code for creating a pending group invite
|
||||
alert('Pending group invite created (placeholder).');
|
||||
const displayBlockList = (blockedNames) => {
|
||||
const blocklistDisplay = document.getElementById("blocklist-display")
|
||||
if (!blockedNames || blockedNames.length === 0) {
|
||||
blocklistDisplay.innerHTML = "<p>No blocked users currently.</p>"
|
||||
return
|
||||
}
|
||||
blocklistDisplay.innerHTML = `
|
||||
<ul>
|
||||
${blockedNames.map(name => `<li>${name}</li>`).join("")}
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
|
||||
// Placeholder function to create a pending promotion
|
||||
async function createPendingPromotion() {
|
||||
console.log("Creating a pending promotion...");
|
||||
// Placeholder code for creating a pending promotion
|
||||
alert('Pending promotion created (placeholder).');
|
||||
}
|
||||
|
||||
// Placeholder function for approving a transaction
|
||||
function approveTransaction(signature) {
|
||||
console.log("Approving transaction with signature: ", signature);
|
||||
// Placeholder code for approving transaction
|
||||
alert(`Transaction with signature ${signature} approved (placeholder).`);
|
||||
}
|
||||
|
@ -30,14 +30,14 @@ const loadMinterBoardPage = async () => {
|
||||
<p style="font-size: 1.25em;"> Publish a Minter Card with Information, and obtain and view the support of the community. Welcome to the Minter Board!</p>
|
||||
<button id="publish-card-button" class="publish-card-button" style="margin: 20px; padding: 10px; background-color: ${publishButtonColor}">Publish Minter Card</button>
|
||||
<button id="refresh-cards-button" class="refresh-cards-button" style="padding: 10px;">Refresh Cards</button>
|
||||
<select id="sort-select" style="margin-left: 10px; padding: 5px;">
|
||||
<select id="sort-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color:rgb(38, 106, 106); background-color: black;">
|
||||
<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>
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px;">
|
||||
<select id="time-range-select" style="margin-left: 10px; padding: 5px; font-size: 1.25rem; color: white; background-color: black;">
|
||||
<option value="0">Show All</option>
|
||||
<option value="1">Last 1 day</option>
|
||||
<option value="7">Last 7 days</option>
|
||||
@ -46,7 +46,7 @@ const loadMinterBoardPage = async () => {
|
||||
</select>
|
||||
<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">
|
||||
<form id="publish-card-form" class="publish-card-form">
|
||||
<h3>Create or Update Your Card</h3>
|
||||
<label for="card-header">Header:</label>
|
||||
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
||||
@ -148,6 +148,7 @@ const loadMinterBoardPage = async () => {
|
||||
await loadCards(minterCardIdentifierPrefix)
|
||||
}
|
||||
|
||||
|
||||
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
||||
// Ensure the identifier starts with the prefix
|
||||
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
||||
@ -197,7 +198,6 @@ const groupAndLabelByIdentifier = (allCards) => {
|
||||
}
|
||||
mapById.get(card.identifier).push(card)
|
||||
})
|
||||
|
||||
// For each identifier's group, sort oldest->newest so the first is "master"
|
||||
const output = []
|
||||
for (const [identifier, group] of mapById.entries()) {
|
||||
@ -206,14 +206,12 @@ const groupAndLabelByIdentifier = (allCards) => {
|
||||
const bTime = b.created || 0
|
||||
return aTime - bTime // oldest first
|
||||
})
|
||||
|
||||
// Mark the first as master
|
||||
group[0].isMaster = true
|
||||
// The rest are updates
|
||||
for (let i = 1; i < group.length; i++) {
|
||||
group[i].isMaster = false
|
||||
}
|
||||
|
||||
// push them all to output
|
||||
output.push(...group)
|
||||
}
|
||||
@ -221,7 +219,6 @@ const groupAndLabelByIdentifier = (allCards) => {
|
||||
return output
|
||||
}
|
||||
|
||||
// no semicolons, using arrow functions
|
||||
const groupByIdentifierOldestFirst = (allCards) => {
|
||||
// map of identifier => array of cards
|
||||
const mapById = new Map()
|
||||
@ -232,7 +229,6 @@ const groupByIdentifierOldestFirst = (allCards) => {
|
||||
}
|
||||
mapById.get(card.identifier).push(card)
|
||||
})
|
||||
|
||||
// sort each group oldest->newest
|
||||
for (const [identifier, group] of mapById.entries()) {
|
||||
group.sort((a, b) => {
|
||||
@ -245,22 +241,18 @@ const groupByIdentifierOldestFirst = (allCards) => {
|
||||
return mapById
|
||||
}
|
||||
|
||||
// no semicolons, arrow functions
|
||||
const buildMinterNameGroups = async (mapById) => {
|
||||
// We'll build an array of objects: { minterName, cards }
|
||||
// Then we can combine any that share the same minterName.
|
||||
|
||||
const nameGroups = []
|
||||
|
||||
for (let [identifier, group] of mapById.entries()) {
|
||||
// group[0] is the oldest => "master" card
|
||||
let masterCard = group[0]
|
||||
|
||||
// Filter out any cards that are not published by the 'masterPublisher'
|
||||
const masterPublisherName = masterCard.name
|
||||
// Remove any cards in this identifier group that have a different publisherName
|
||||
const filteredGroup = group.filter(c => c.name === masterPublisherName)
|
||||
|
||||
// If filtering left zero cards, skip entire group
|
||||
if (!filteredGroup.length) {
|
||||
console.warn(`All cards removed for identifier=${identifier} (different publishers). Skipping.`)
|
||||
@ -269,7 +261,6 @@ const buildMinterNameGroups = async (mapById) => {
|
||||
// Reassign group to the filtered version, then re-define masterCard
|
||||
group = filteredGroup
|
||||
masterCard = group[0] // oldest after filtering
|
||||
|
||||
// attempt to obtain minterName from the master card
|
||||
let masterMinterName
|
||||
try {
|
||||
@ -278,14 +269,12 @@ const buildMinterNameGroups = async (mapById) => {
|
||||
console.warn(`Skipping entire group ${identifier}, no valid minterName from master`, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Store an object with the minterName we extracted, plus all cards in that group
|
||||
nameGroups.push({
|
||||
minterName: masterMinterName,
|
||||
cards: group // includes the master & updates
|
||||
})
|
||||
}
|
||||
|
||||
// Combine them: minterName => array of *all* cards from all matching groups
|
||||
const combinedMap = new Map()
|
||||
for (const entry of nameGroups) {
|
||||
@ -373,7 +362,6 @@ const processARBoardCards = async (allValidCards) => {
|
||||
return finalOutput
|
||||
}
|
||||
|
||||
|
||||
//Main function to load the Minter Cards ----------------------------------------
|
||||
const loadCards = async (cardIdentifierPrefix) => {
|
||||
const cardsContainer = document.getElementById("cards-container")
|
||||
@ -452,9 +440,11 @@ const loadCards = async (cardIdentifierPrefix) => {
|
||||
// else 'newest' => do nothing (already sorted newest-first by your process functions).
|
||||
// Create the 'finalCardsArray' that includes the data, etc.
|
||||
let finalCardsArray = []
|
||||
|
||||
cardsContainer.innerHTML = ''
|
||||
for (const card of finalCards) {
|
||||
try {
|
||||
const skeletonHTML = createSkeletonCardHTML(card.identifier)
|
||||
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||
const cardDataResponse = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: card.name,
|
||||
@ -465,6 +455,7 @@ const loadCards = async (cardIdentifierPrefix) => {
|
||||
if (!cardDataResponse || !cardDataResponse.poll) {
|
||||
// skip
|
||||
console.warn(`Skipping card: missing data/poll. identifier=${card.identifier}`)
|
||||
removeSkeleton(card.identifier)
|
||||
continue
|
||||
}
|
||||
// Extra validation: check poll ownership matches card publisher
|
||||
@ -472,13 +463,22 @@ const loadCards = async (cardIdentifierPrefix) => {
|
||||
const cardPublisherAddress = await fetchOwnerAddressFromName(card.name)
|
||||
if (pollPublisherAddress !== cardPublisherAddress) {
|
||||
console.warn(`Poll hijack attack found, discarding card ${card.identifier}`)
|
||||
removeSkeleton(card.identifier)
|
||||
continue
|
||||
}
|
||||
// If ARBoard, do a quick address check
|
||||
if (isARBoard) {
|
||||
const ok = await verifyARBoardAddress(cardDataResponse.minterName)
|
||||
const ok = await verifyMinter(cardDataResponse.minterName)
|
||||
if (!ok) {
|
||||
console.warn(`Invalid minter address for AR board. identifier=${card.identifier}`)
|
||||
console.warn(`Card is not a minter nor an admin, not including in ARBoard. identifier: ${card.identifier}`)
|
||||
removeSkeleton(card.identifier)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
const isAlreadyMinter = await verifyMinter(cardDataResponse.minterName)
|
||||
if (isAlreadyMinter) {
|
||||
console.warn(`card IS ALREADY a minter, NOT displaying following identifier on the MinterBoard: ${card.identifier}`)
|
||||
removeSkeleton(card.identifier)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -491,18 +491,16 @@ const loadCards = async (cardIdentifierPrefix) => {
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`Error preparing card ${card.identifier}`, err)
|
||||
removeSkeleton(card.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
// This will now allow duplicates to be displayed, but also mark the duplicates correctly. There is one bugged identifier that must be handled opposite.
|
||||
// finalCardsArray = markDuplicates(finalCardsArray)
|
||||
|
||||
// Next, do the actual rendering:
|
||||
cardsContainer.innerHTML = ""
|
||||
// cardsContainer.innerHTML = ""
|
||||
for (const cardObj of finalCardsArray) {
|
||||
// Insert a skeleton first if you like
|
||||
const skeletonHTML = createSkeletonCardHTML(cardObj.identifier)
|
||||
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||
// const skeletonHTML = createSkeletonCardHTML(cardObj.identifier)
|
||||
// cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||
// Build final HTML
|
||||
const pollResults = await fetchPollResults(cardObj.cardDataResponse.poll)
|
||||
const commentCount = await countComments(cardObj.identifier)
|
||||
@ -539,7 +537,7 @@ const loadCards = async (cardIdentifierPrefix) => {
|
||||
}
|
||||
}
|
||||
|
||||
const verifyARBoardAddress = async (minterName) => {
|
||||
const verifyMinter = async (minterName) => {
|
||||
try {
|
||||
const nameInfo = await getNameInfo(minterName)
|
||||
|
||||
@ -557,7 +555,7 @@ const verifyARBoardAddress = async (minterName) => {
|
||||
return (minterGroupAddresses.includes(minterAddress) ||
|
||||
adminGroupAddresses.includes(minterAddress))
|
||||
} catch (err) {
|
||||
console.warn("verifyARBoardAddress error:", err)
|
||||
console.warn("verifyMinter error:", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -781,6 +779,7 @@ const publishCard = async (cardIdentifierPrefix) => {
|
||||
content,
|
||||
links,
|
||||
creator: userState.accountName,
|
||||
creatorAddress: userState.accountAddress,
|
||||
timestamp: Date.now(),
|
||||
poll: pollName,
|
||||
}
|
||||
@ -812,7 +811,7 @@ const publishCard = async (cardIdentifierPrefix) => {
|
||||
}
|
||||
|
||||
if (isExistingCard){
|
||||
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
|
||||
alert("Card Updated Successfully! (No poll updates possible)")
|
||||
isExistingCard = false
|
||||
}
|
||||
|
||||
@ -1067,34 +1066,44 @@ const postComment = async (cardIdentifier) => {
|
||||
alert('Comment cannot be empty!')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
//Ensure the user is not on the blockList prior to allowing them to publish a comment.
|
||||
const blockedNames = await fetchBlockList()
|
||||
|
||||
if (blockedNames.includes(userState.accountName)) {
|
||||
alert('You are on the block list and cannot publish comments.')
|
||||
return
|
||||
}
|
||||
const commentData = {
|
||||
content: commentText,
|
||||
creator: userState.accountName,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
||||
|
||||
try {
|
||||
const base64CommentData = await objectToBase64(commentData)
|
||||
const uniqueCommentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
||||
let base64CommentData = await objectToBase64(commentData)
|
||||
if (!base64CommentData) {
|
||||
console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`)
|
||||
console.log('objectToBase64 failed, fallback to btoa()')
|
||||
base64CommentData = btoa(JSON.stringify(commentData))
|
||||
}
|
||||
|
||||
await qortalRequest({
|
||||
action: 'PUBLISH_QDN_RESOURCE',
|
||||
name: userState.accountName,
|
||||
service: 'BLOG_POST',
|
||||
identifier: commentIdentifier,
|
||||
identifier: uniqueCommentIdentifier,
|
||||
data64: base64CommentData,
|
||||
})
|
||||
|
||||
commentInput.value = ''
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error posting comment:', error)
|
||||
alert('Failed to post comment.')
|
||||
alert('Failed to post comment. Error: ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||
const fetchCommentsForCard = async (cardIdentifier) => {
|
||||
try {
|
||||
@ -1110,9 +1119,9 @@ const displayComments = async (cardIdentifier) => {
|
||||
try {
|
||||
const comments = await fetchCommentsForCard(cardIdentifier)
|
||||
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`)
|
||||
|
||||
commentsContainer.innerHTML = ''
|
||||
|
||||
commentsContainer.innerHTML = ""
|
||||
const blockedNames = await fetchBlockList()
|
||||
console.log("Loaded block list:", blockedNames)
|
||||
const voterMap = globalVoterMap.get(cardIdentifier) || new Map()
|
||||
|
||||
const commentHTMLArray = await Promise.all(
|
||||
@ -1122,33 +1131,38 @@ const displayComments = async (cardIdentifier) => {
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: comment.name,
|
||||
service: "BLOG_POST",
|
||||
identifier: comment.identifier,
|
||||
identifier: comment.identifier
|
||||
})
|
||||
|
||||
const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp);
|
||||
|
||||
const commenter = commentDataResponse.creator
|
||||
const voterInfo = voterMap.get(commenter)
|
||||
|
||||
if (!commentDataResponse || !commentDataResponse.creator) {
|
||||
return null
|
||||
}
|
||||
const commenterName = commentDataResponse.creator
|
||||
const voterInfo = voterMap.get(commenterName)
|
||||
let commentColor = "transparent"
|
||||
let adminBadge = ""
|
||||
|
||||
if (blockedNames.includes(commenterName)) {
|
||||
console.warn(`Skipping blocked commenter: ${commenterName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
if (voterInfo) {
|
||||
if (voterInfo.voterType === "Admin") {
|
||||
|
||||
commentColor = voterInfo.vote === "yes" ? "rgba(21, 150, 21, 0.6)" : "rgba(212, 37, 64, 0.6)" // Light green for yes, light red for no
|
||||
const badgeColor = voterInfo.vote === "yes" ? "green" : "red"
|
||||
const badgeColor = voterInfo.vote === "yes" ? "rgb(206, 195, 77)" : "rgb(121, 119, 90)"
|
||||
adminBadge = `<span style="color: ${badgeColor}; font-weight: bold; margin-left: 0.5em;">(Admin)</span>`
|
||||
} else {
|
||||
|
||||
commentColor = voterInfo.vote === "yes" ? "rgba(0, 100, 0, 0.3)" : "rgba(100, 0, 0, 0.3)" // Darker green for yes, darker red for no
|
||||
}
|
||||
}
|
||||
|
||||
const timestamp = new Date(commentDataResponse.timestamp).toLocaleString()
|
||||
return `
|
||||
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: ${commentColor};">
|
||||
<p>
|
||||
<strong><u>${commentDataResponse.creator}</u></strong>
|
||||
<strong>${commenterName}</strong>
|
||||
${adminBadge}
|
||||
</p>
|
||||
<p>${commentDataResponse.content}</p>
|
||||
@ -1156,22 +1170,23 @@ const displayComments = async (cardIdentifier) => {
|
||||
</div>
|
||||
`
|
||||
} catch (err) {
|
||||
console.error(`Error processing comment ${comment.identifier}:`, err)
|
||||
console.error(`Error with comment ${comment.identifier}:`, err)
|
||||
return null
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
commentHTMLArray
|
||||
.filter((html) => html !== null) // Filter out failed comments
|
||||
.forEach((commentHTML) => {
|
||||
.filter(html => html !== null)
|
||||
.forEach(commentHTML => {
|
||||
commentsContainer.insertAdjacentHTML('beforeend', commentHTML)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error)
|
||||
|
||||
} catch (err) {
|
||||
console.error(`Error displaying comments for ${cardIdentifier}:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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}`)
|
||||
@ -1206,12 +1221,10 @@ const countComments = async (cardIdentifier) => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
const createModal = (modalType='') => {
|
||||
if (document.getElementById(`${modalType}-modal`)) {
|
||||
return
|
||||
}
|
||||
|
||||
const isIframe = (modalType === 'links')
|
||||
|
||||
const modalHTML = `
|
||||
@ -1259,8 +1272,8 @@ const createModal = (modalType='') => {
|
||||
</div>
|
||||
`
|
||||
document.body.insertAdjacentHTML('beforeend', modalHTML)
|
||||
|
||||
const modal = document.getElementById(`${modalType}-modal`)
|
||||
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === modal) {
|
||||
closeModal(modalType)
|
||||
@ -1268,7 +1281,6 @@ const createModal = (modalType='') => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const openLinksModal = async (link) => {
|
||||
const processedLink = await processLink(link)
|
||||
const modal = document.getElementById('links-modal')
|
||||
@ -1311,9 +1323,9 @@ const togglePollDetails = (cardIdentifier) => {
|
||||
|
||||
if (!detailsDiv || !modal || !modalContent) return
|
||||
|
||||
// modalContent.appendChild(detailsDiv)
|
||||
modalContent.innerHTML = detailsDiv.innerHTML
|
||||
modal.style.display = 'block'
|
||||
|
||||
window.onclick = (event) => {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none'
|
||||
@ -1394,10 +1406,17 @@ const handleInviteMinter = async (minterName) => {
|
||||
}
|
||||
}
|
||||
|
||||
const escapeHTML = (str) => {
|
||||
return str
|
||||
.replace(/'/g, ''')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
const createInviteButtonHtml = (creator, cardIdentifier) => {
|
||||
const escapedCreator = escapeHTML(creator)
|
||||
return `
|
||||
<div id="invite-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||
<button onclick="handleInviteMinter('${creator}')"
|
||||
<button onclick="handleInviteMinter('${escapedCreator}')"
|
||||
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;"
|
||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
|
||||
@ -1423,73 +1442,59 @@ const featureTriggerCheck = async () => {
|
||||
}
|
||||
|
||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||
|
||||
if (!userState.isMinterAdmin){
|
||||
console.warn(`User is NOT an admin, not displaying invite/approve button...`)
|
||||
return null
|
||||
}
|
||||
|
||||
const isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
||||
const isBlockPassed = await featureTriggerCheck()
|
||||
const minterAdmins = await fetchMinterGroupAdmins()
|
||||
|
||||
// default needed admin count = 9, or 40% if block has passed
|
||||
let minAdminCount = 9
|
||||
if (isBlockPassed) {
|
||||
minAdminCount = Math.round(minterAdmins.length * 0.4)
|
||||
minAdminCount = Math.ceil(minterAdmins.length * 0.4)
|
||||
console.warn(`Using 40% => ${minAdminCount}`)
|
||||
}
|
||||
|
||||
// if not enough adminYes votes, no invite button
|
||||
if (adminYes < minAdminCount) {
|
||||
console.warn(`Admin votes not high enough (have=${adminYes}, need=${minAdminCount}). No button.`)
|
||||
return null
|
||||
}
|
||||
console.log(`passed initial button creation checks, pulling additional data...`)
|
||||
|
||||
console.log(`passed initial button creation checks (adminYes >= ${minAdminCount})`)
|
||||
// get user's address from 'creator' name
|
||||
const minterNameInfo = await getNameInfo(creator)
|
||||
const minterAddress = await minterNameInfo.owner
|
||||
if (!minterNameInfo || !minterNameInfo.owner) {
|
||||
console.warn(`No valid nameInfo for ${creator}, skipping invite button.`)
|
||||
return null
|
||||
}
|
||||
const minterAddress = minterNameInfo.owner
|
||||
// fetch all final KICK/BAN tx
|
||||
const { finalKickTxs, finalBanTxs } = await fetchAllKickBanTxData()
|
||||
// check if there's a final (non-pending) KICK or BAN for this user
|
||||
const priorKick = finalKickTxs.some(tx => tx.member === minterAddress)
|
||||
const priorBan = finalBanTxs.some(tx => tx.offender === minterAddress)
|
||||
const priorBanOrKick = (priorBan || priorKick)
|
||||
console.warn(`PriorBanOrKick determination for ${minterAddress}:`, priorBanOrKick)
|
||||
|
||||
const previousBanTx = await searchTransactions({
|
||||
txTypes: ['GROUP_BAN'],
|
||||
address: `${minterAddress}`,
|
||||
confirmationStatus: 'CONFIRMED',
|
||||
limit: 0,
|
||||
reverse: true,
|
||||
offset: 0,
|
||||
startBlock: 1990000,
|
||||
blockLimit: 0,
|
||||
txGroupId: 0,
|
||||
})
|
||||
const previousBan = previousBanTx.filter((tx) => tx.approvalStatus !== 'PENDING')
|
||||
const previousKickTx = await searchTransactions({
|
||||
txTypes: ['GROUP_KICK'],
|
||||
address: `${minterAddress}`,
|
||||
confirmationStatus: 'CONFIRMED',
|
||||
limit: 0,
|
||||
reverse: true,
|
||||
offset: 0,
|
||||
startBlock: 1990000,
|
||||
blockLimit: 0,
|
||||
txGroupId: 0,
|
||||
})
|
||||
const previousKick = previousKickTx.filter((tx) => tx.approvalStatus !== 'PENDING')
|
||||
const priorBanOrKick = (previousKick.length > 0 || previousBan.length > 0)
|
||||
|
||||
console.warn(`PriorBanOrKick determination:`, priorBanOrKick)
|
||||
|
||||
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
||||
// build the normal invite button & groupApprovalHtml
|
||||
const inviteButtonHtml = isSomeTypaAdmin ? createInviteButtonHtml(creator, cardIdentifier) : ""
|
||||
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
|
||||
|
||||
// if user had no prior KICK/BAN
|
||||
if (!priorBanOrKick) {
|
||||
console.log(`No prior kick/ban found, creating invite (or approve) button...`)
|
||||
console.warn(`Existing Numbers - adminYes/minAdminCount: ${adminYes}/${minAdminCount}`)
|
||||
|
||||
// if there's already a pending GROUP_INVITE, return that approval button
|
||||
if (groupApprovalHtml) {
|
||||
console.warn(`groupApprovalCheck found existing groupApproval, returning approval button instead of invite button...`)
|
||||
return groupApprovalHtml
|
||||
}
|
||||
console.warn(`No pending approvals or prior kick/ban found, but votes are high enough, returning invite button...`)
|
||||
|
||||
console.warn(`No pending approvals or prior kick/ban found, returning invite button...`)
|
||||
return inviteButtonHtml
|
||||
|
||||
} else if (priorBanOrKick){
|
||||
console.warn(`Prior kick/ban found! Including BOTH buttons (due to complexities in checking, displaying both buttons is simpler than attempting to display only one)...`)
|
||||
} else {
|
||||
// priorBanOrKick is true => show both
|
||||
console.warn(`Prior kick/ban found! Including BOTH buttons...`)
|
||||
return inviteButtonHtml + groupApprovalHtml
|
||||
}
|
||||
}
|
||||
@ -1540,12 +1545,13 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
blockLimit: 0,
|
||||
txGroupId: 0
|
||||
})
|
||||
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType);
|
||||
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType)
|
||||
let isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
||||
|
||||
// If no pending transaction found, return null
|
||||
if (!pendingApprovals || pendingApprovals.length === 0) {
|
||||
console.warn("no pending approval transactions found, returning null...")
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
const txSig = pendingApprovals[0].signature
|
||||
// Among the already-confirmed GROUP_APPROVAL, filter for those referencing this txSig
|
||||
@ -1557,7 +1563,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
getNameFromAddress
|
||||
)
|
||||
|
||||
if (transactionType === "GROUP_INVITE") {
|
||||
if (transactionType === "GROUP_INVITE" && isSomeTypaAdmin) {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(181, 214, 100);">
|
||||
@ -1587,7 +1593,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "GROUP_KICK") {
|
||||
if (transactionType === "GROUP_KICK" && isSomeTypaAdmin) {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(199, 100, 64);">
|
||||
@ -1617,7 +1623,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "GROUP_BAN") {
|
||||
if (transactionType === "GROUP_BAN" && isSomeTypaAdmin) {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(189, 40, 40);">
|
||||
@ -1647,7 +1653,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "ADD_GROUP_ADMIN") {
|
||||
if (transactionType === "ADD_GROUP_ADMIN" && isSomeTypaAdmin) {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(40, 144, 189);">
|
||||
@ -1677,7 +1683,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
return approvalButtonHtml
|
||||
}
|
||||
|
||||
if (transactionType === "REMOVE_GROUP_ADMIN") {
|
||||
if (transactionType === "REMOVE_GROUP_ADMIN" && isSomeTypaAdmin) {
|
||||
const approvalButtonHtml = `
|
||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||
<p style="color: rgb(189, 40, 40);">
|
||||
@ -1709,7 +1715,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
||||
|
||||
}
|
||||
|
||||
async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
const buildApprovalTableHtml = async (approvalTxs, getNameFunc) => {
|
||||
// Build a Map of adminAddress => one transaction (to handle multiple approvals from same admin)
|
||||
const approvalMap = new Map()
|
||||
for (const tx of approvalTxs) {
|
||||
@ -1718,10 +1724,8 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
approvalMap.set(adminAddr, tx)
|
||||
}
|
||||
}
|
||||
|
||||
// Turn the map into an array for iteration
|
||||
const approvalArray = Array.from(approvalMap, ([adminAddr, tx]) => ({ adminAddr, tx }))
|
||||
|
||||
// Build table rows asynchronously, since we need getNameFromAddress
|
||||
const tableRows = await Promise.all(
|
||||
approvalArray.map(async ({ adminAddr, tx }) => {
|
||||
@ -1732,15 +1736,12 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
console.warn(`Error fetching name for ${adminAddr}:`, err)
|
||||
adminName = null
|
||||
}
|
||||
|
||||
const displayName =
|
||||
adminName && adminName !== adminAddr
|
||||
? adminName
|
||||
: "(No registered name)"
|
||||
|
||||
// Format the transaction timestamp
|
||||
const dateStr = new Date(tx.timestamp).toLocaleString()
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td style="border: 1px solid rgb(255, 255, 255); padding: 4px; color: #234565">${displayName}</td>
|
||||
@ -1749,11 +1750,9 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
`
|
||||
})
|
||||
)
|
||||
|
||||
// The total unique approvals = number of entries in approvalMap
|
||||
const uniqueApprovalCount = approvalMap.size;
|
||||
|
||||
// 4) Wrap the table in a container with horizontal scroll:
|
||||
// Wrap the table in a container with horizontal scroll:
|
||||
// 1) max-width: 100% makes it fit the parent (card) width
|
||||
// 2) overflow-x: auto allows scrolling if the table is too wide
|
||||
const containerHtml = `
|
||||
@ -1771,7 +1770,6 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Return both the container-wrapped table and the count of unique approvals
|
||||
return {
|
||||
tableHtml: containerHtml,
|
||||
@ -1887,7 +1885,7 @@ const getNewestCommentTimestamp = async (cardIdentifier) => {
|
||||
|
||||
// Create the overall Minter Card HTML -----------------------------------------------
|
||||
const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, address) => {
|
||||
const { header, content, links, creator, timestamp, poll } = cardData
|
||||
const { header, content, links, creator, creatorAddress, timestamp, poll } = cardData
|
||||
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
||||
const avatarHtml = await getMinterAvatar(creator)
|
||||
const linksHTML = links.map((link, index) => `
|
||||
@ -1915,9 +1913,9 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
||||
const invites = await fetchGroupInvitesByAddress(address)
|
||||
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
||||
if (userVote === 0) {
|
||||
finalBgColor = "rgba(0, 192, 0, 0.3)"; // or any green you want
|
||||
finalBgColor = "rgba(1, 65, 39, 0.41)"; // or any green you want
|
||||
} else if (userVote === 1) {
|
||||
finalBgColor = "rgba(192, 0, 0, 0.3)"; // or any red you want
|
||||
finalBgColor = "rgba(107, 3, 3, 0.3)"; // or any red you want
|
||||
} else if (hasMinterInvite) {
|
||||
// If so, override background color & add an "INVITED" label
|
||||
finalBgColor = "black";
|
||||
|
@ -1,3 +1,5 @@
|
||||
const Q_MINTERSHIP_VERSION = "1.05"
|
||||
|
||||
const messageIdentifierPrefix = `mintership-forum-message`
|
||||
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
||||
|
||||
@ -66,6 +68,10 @@ if (localStorage.getItem("latestMessageIdentifiers")) {
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
console.log("DOMContentLoaded fired!")
|
||||
createScrollToTopButton()
|
||||
document.querySelectorAll(".version").forEach(el => {
|
||||
el.textContent = `Q-Mintership (v${Q_MINTERSHIP_VERSION}b)`
|
||||
})
|
||||
|
||||
// --- GENERAL LINKS (MINTERSHIP-FORUM and MINTER-BOARD) ---
|
||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]')
|
||||
@ -94,7 +100,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
await loadScript("./assets/js/MinterBoard.js")
|
||||
}
|
||||
await loadMinterBoardPage()
|
||||
createScrollToTopButton()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -53,7 +53,7 @@ const timestampToHumanReadableDate = async(timestamp) => {
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
const formattedDate = `${year}.${month}.${day}@${hours}:${minutes}:${seconds}`
|
||||
const formattedDate = `${day}.${month}.${year}..@${hours}:${minutes}:${seconds}`
|
||||
console.log('Formatted date:', formattedDate)
|
||||
return formattedDate
|
||||
}
|
||||
@ -331,7 +331,7 @@ const getNameInfo = async (name) => {
|
||||
console.log('getNameInfo called')
|
||||
console.log('name:', name)
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/names/${name}`)
|
||||
const response = await fetch(`${baseUrl}/names/${encodeURIComponent(name)}`)
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`Failed to fetch name info for: ${name}, status: ${response.status}`)
|
||||
@ -491,28 +491,6 @@ const fetchMinterGroupAdmins = async () => {
|
||||
//use what is returned .member to obtain each member... {"member": "memberAddress", "isAdmin": "true"}
|
||||
}
|
||||
|
||||
// const fetchAllAdminGroupsMembers = async () => {
|
||||
// try {
|
||||
// let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results
|
||||
// for (const groupID of adminGroupIDs) {
|
||||
// const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, {
|
||||
// method: 'GET',
|
||||
// headers: { 'Accept': 'application/json' },
|
||||
// })
|
||||
|
||||
// const groupData = await response.json()
|
||||
// if (groupData.members && Array.isArray(groupData.members)) {
|
||||
// adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array
|
||||
// } else {
|
||||
// console.warn(`Group ${groupID} did not return valid members.`)
|
||||
// }
|
||||
// }
|
||||
// return adminGroupMemberAddresses
|
||||
// } catch (error) {
|
||||
// console.log('Error fetching admin group members', error)
|
||||
// }
|
||||
// }
|
||||
|
||||
const fetchAllAdminGroupsMembers = async () => {
|
||||
try {
|
||||
// We'll track addresses so we don't duplicate the same .member
|
||||
|
28
index.html
28
index.html
@ -28,8 +28,14 @@
|
||||
<link rel="preload" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap"></noscript>
|
||||
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// Variable-based versioning (credit: QuickMythril)
|
||||
; // Update here in the future
|
||||
</script>
|
||||
|
||||
<section data-bs-version="5.1" class="menu menu1 boldm5 cid-ttRnktJ11Q" once="menu" id="menu1-0">
|
||||
|
||||
@ -42,7 +48,7 @@
|
||||
</a>
|
||||
</span>
|
||||
<span class="navbar-caption-wrap">
|
||||
<a class="navbar-caption display-4" href="index.html">Q-Mintership (v1.04b)
|
||||
<a class="navbar-caption display-4" href="index.html"><span class="navbar-caption display-4 version"></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
@ -61,7 +67,7 @@
|
||||
<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 v1.04b<br></a></span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html"><span class="navbar-caption display-4 version"></span><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>
|
||||
|
||||
@ -84,7 +90,7 @@
|
||||
<img src="assets/images/mbr-1623x1082.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">
|
||||
Admin Board</h2>
|
||||
ADMIN BOARD</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -93,7 +99,7 @@
|
||||
<div class="item-wrapper">
|
||||
<img src="assets/images/mbr-1623x1112.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">MinterBoard</h2>
|
||||
<h2 class="card-title mbr-fonts-style display-2">MINTER BOARD</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -109,7 +115,7 @@
|
||||
<img src="assets/images/mbr-1818x1212.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">
|
||||
Minter Admin Management (MAM) Board</h2>
|
||||
MAM BOARD</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -118,7 +124,7 @@
|
||||
<div class="item-wrapper">
|
||||
<img src="assets/images/mbr-1-1818x1212.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||
<div class="item-content">
|
||||
<h2 class="card-title mbr-fonts-style display-2">Mintership Forum</h2>
|
||||
<h2 class="card-title mbr-fonts-style display-2">Q-MINTERSHIP FORUM</h2>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -572,12 +578,12 @@
|
||||
<div class="title-wrapper">
|
||||
<div class="title-wrap">
|
||||
<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.04b)</h2>
|
||||
<h2 class="mbr-section-title mbr-fonts-style display-5"><span class="navbar-caption display-4 version"></span></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="link-wrap" href="#">
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v1.04beta</p>
|
||||
<p class="mbr-link mbr-fonts-style display-4"><span class="navbar-caption display-4 version"></span></p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
@ -590,7 +596,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<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> -->
|
||||
@ -598,12 +603,13 @@
|
||||
<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>
|
||||
|
||||
<!-- Order here MATTERS, we MUST load the scripts in the correct order so that all the functions will work properly -->
|
||||
<script src="./assets/js/QortalApi.js"></script>
|
||||
<script src="./assets/js/Shared.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/ARBoard.js"></script>
|
||||
<script src="./assets/js/AdminTools.js"></script>
|
||||
<script src="./assets/js/Q-Mintership.js"></script>
|
||||
<input name="animation" type="hidden">
|
||||
</body>
|
||||
|
Loading…
Reference in New Issue
Block a user