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 */
|
/* General Page Styles */
|
||||||
/* Main Container for Minter Board */
|
/* 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 {
|
body {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
@ -555,6 +576,65 @@ body {
|
|||||||
margin-bottom: 2vh;
|
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 {
|
.publish-card-view textarea {
|
||||||
min-height: 15vh;
|
min-height: 15vh;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
@ -570,9 +650,9 @@ body {
|
|||||||
margin-bottom: 1.5vh;
|
margin-bottom: 1.5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons Inside Form */
|
/* Generic: all buttons inside .publish-card-form */
|
||||||
#publish-card-form button {
|
.publish-card-form button {
|
||||||
background-color: #76c7c0;
|
background-color: #359f4a;
|
||||||
color: #1e1e1e;
|
color: #1e1e1e;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.5vh;
|
border-radius: 0.5vh;
|
||||||
@ -583,30 +663,76 @@ body {
|
|||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#publish-card-form button:hover {
|
.publish-card-form button:hover {
|
||||||
background-color: #5e92a8;
|
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;
|
background-color: #233748;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
margin-bottom: 2vh;
|
margin-bottom: 2vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#publish-card-form #add-link-button:hover {
|
.publish-card-form #add-link-button:hover {
|
||||||
background-color: #1b1936;
|
background-color: #1b1936;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cancel Button */
|
/* And specifically override the remove button */
|
||||||
#publish-card-form #cancel-publish-button {
|
.publish-card-form #blocklist-remove-button {
|
||||||
background-color: #463737;
|
background-color: #463737;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
#publish-card-form #cancel-publish-button:hover {
|
.publish-card-form #blocklist-remove-button:hover {
|
||||||
background-color: #281e1e;
|
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 */
|
/* Responsive Design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.publish-card-view {
|
.publish-card-view {
|
||||||
@ -619,7 +745,7 @@ body {
|
|||||||
padding: 1.5vh;
|
padding: 1.5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#publish-card-form button {
|
.publish-card-form button {
|
||||||
font-size: 1.8vh;
|
font-size: 1.8vh;
|
||||||
padding: 1.2vh;
|
padding: 1.2vh;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ const loadAddRemoveAdminPage = async () => {
|
|||||||
Propose a Minter for Admin Position
|
Propose a Minter for Admin Position
|
||||||
</button>
|
</button>
|
||||||
<div id="promotion-form-container" class="publish-card-view" style="display: none; margin-top: 1em;">
|
<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>
|
<h3>Create or Update Promotion/Demotion Proposal Card</h3>
|
||||||
<label for="minter-name-input">Input NAME (promotion):</label>
|
<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>
|
<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;">
|
<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="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="0">Show All</option>
|
||||||
<option value="1">Last 1 day</option>
|
<option value="1">Last 1 day</option>
|
||||||
<option value="7">Last 7 days</option>
|
<option value="7">Last 7 days</option>
|
||||||
@ -176,7 +176,7 @@ const fetchAllARTxData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function partitionAddTransactions(rawTransactions) {
|
const partitionAddTransactions = (rawTransactions) => {
|
||||||
const finalAddTxs = []
|
const finalAddTxs = []
|
||||||
const pendingAddTxs = []
|
const pendingAddTxs = []
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ function partitionAddTransactions(rawTransactions) {
|
|||||||
return { finalAddTxs, pendingAddTxs };
|
return { finalAddTxs, pendingAddTxs };
|
||||||
}
|
}
|
||||||
|
|
||||||
function partitionRemoveTransactions(rawTransactions) {
|
const partitionRemoveTransactions = (rawTransactions) => {
|
||||||
const finalRemTxs = []
|
const finalRemTxs = []
|
||||||
const pendingRemTxs = []
|
const pendingRemTxs = []
|
||||||
|
|
||||||
@ -434,15 +434,17 @@ const publishARCard = async (cardIdentifierPrefix) => {
|
|||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
alert(`An existing card was found, you must update it, two cards for the samme name cannot be published! Loading card data...`)
|
alert(`An existing card was found, you must update it, two cards for the samme name cannot be published! Loading card data...`)
|
||||||
await loadCardIntoForm(existingCardData)
|
if (exists.creator != userState.accountName) {
|
||||||
minterName = exists.minterName
|
alert(`You are not the original publisher of this card, exiting.`)
|
||||||
const nameInfo = await getNameInfo(exists.minterName)
|
return
|
||||||
address = nameInfo.owner
|
}else {
|
||||||
isExistingCard = true
|
await loadCardIntoForm(existingCardData)
|
||||||
} else if (otherPublisher){
|
minterName = exists.minterName
|
||||||
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}`)
|
const nameInfo = await getNameInfo(exists.minterName)
|
||||||
return
|
address = nameInfo.owner
|
||||||
}
|
isExistingCard = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const minterGroupData = await fetchMinterGroupMembers()
|
const minterGroupData = await fetchMinterGroupMembers()
|
||||||
minterGroupAddresses = minterGroupData.map(m => m.member)
|
minterGroupAddresses = minterGroupData.map(m => m.member)
|
||||||
@ -481,6 +483,7 @@ const publishARCard = async (cardIdentifierPrefix) => {
|
|||||||
|
|
||||||
const cardData = {
|
const cardData = {
|
||||||
minterName,
|
minterName,
|
||||||
|
minterAddress: address,
|
||||||
header,
|
header,
|
||||||
content,
|
content,
|
||||||
links,
|
links,
|
||||||
@ -550,7 +553,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)
|
||||||
@ -735,7 +738,7 @@ const fallbackMinterCheck = async (minterName, minterGroupMembers, minterAdmins)
|
|||||||
|
|
||||||
|
|
||||||
const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCount, cardUpdatedTime, bgColor, cardPublisherAddress, illegalDuplicate) => {
|
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 formattedDate = new Date(timestamp).toLocaleString()
|
||||||
const minterAvatar = await getMinterAvatar(minterName)
|
const minterAvatar = await getMinterAvatar(minterName)
|
||||||
const creatorAvatar = await getMinterAvatar(creator)
|
const creatorAvatar = await getMinterAvatar(creator)
|
||||||
@ -744,6 +747,14 @@ const createARCardHTML = async (cardData, pollResults, cardIdentifier, commentCo
|
|||||||
${`Link ${index + 1} - ${link}`}
|
${`Link ${index + 1} - ${link}`}
|
||||||
</button>
|
</button>
|
||||||
`).join("")
|
`).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 minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
const minterAdmins = await fetchMinterGroupAdmins()
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
@ -10,8 +10,8 @@ let isTopic = false
|
|||||||
let attemptLoadAdminDataCount = 0
|
let attemptLoadAdminDataCount = 0
|
||||||
let adminMemberCount = 0
|
let adminMemberCount = 0
|
||||||
let adminPublicKeys = []
|
let adminPublicKeys = []
|
||||||
let kickTransactions = []
|
// let kickTransactions = []
|
||||||
let banTransactions = []
|
// let banTransactions = []
|
||||||
let adminBoardState = {
|
let adminBoardState = {
|
||||||
kickedCards: new Set(), // store identifiers
|
kickedCards: new Set(), // store identifiers
|
||||||
bannedCards: new Set(), // likewise
|
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>
|
<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="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>
|
<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="newest" selected>Sort by Date</option>
|
||||||
<option value="name">Sort by Name</option>
|
<option value="name">Sort by Name</option>
|
||||||
<option value="recent-comments">Newest Comments</option>
|
<option value="recent-comments">Newest Comments</option>
|
||||||
<option value="least-votes">Least Votes</option>
|
<option value="least-votes">Least Votes</option>
|
||||||
<option value="most-votes">Most Votes</option>
|
<option value="most-votes">Most Votes</option>
|
||||||
</select>
|
</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="0">Show All</option>
|
||||||
<option value="1">Last 1 day</option>
|
<option value="1">Last 1 day</option>
|
||||||
<option value="7">Last 7 days</option>
|
<option value="7">Last 7 days</option>
|
||||||
@ -98,7 +98,7 @@ const loadAdminBoardPage = async () => {
|
|||||||
</div>
|
</div>
|
||||||
<div id="encrypted-cards-container" class="cards-container" style="margin-top: 20px;"></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;">
|
<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>
|
<h3>Create or Update an Admin Card</h3>
|
||||||
<div class="publish-card-checkbox" style="margin-top: 1em;">
|
<div class="publish-card-checkbox" style="margin-top: 1em;">
|
||||||
<input type="checkbox" id="topic-checkbox" name="topicMode" />
|
<input type="checkbox" id="topic-checkbox" name="topicMode" />
|
||||||
@ -195,68 +195,67 @@ const loadAdminBoardPage = async () => {
|
|||||||
createScrollToTopButton()
|
createScrollToTopButton()
|
||||||
// await fetchAndValidateAllAdminCards()
|
// await fetchAndValidateAllAdminCards()
|
||||||
await updateOrSaveAdminGroupsDataLocally()
|
await updateOrSaveAdminGroupsDataLocally()
|
||||||
await fetchAllKicKBanTxData()
|
|
||||||
await fetchAllEncryptedCards()
|
await fetchAllEncryptedCards()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllKicKBanTxData = async () => {
|
// const fetchAllKicKBanTxData = async () => {
|
||||||
const kickTxType = "GROUP_KICK"
|
// const kickTxType = "GROUP_KICK"
|
||||||
const banTxType = "GROUP_BAN"
|
// const banTxType = "GROUP_BAN"
|
||||||
|
|
||||||
// Helper function to filter transactions
|
// // Helper function to filter transactions
|
||||||
const filterTransactions = (rawTransactions) => {
|
// const filterTransactions = (rawTransactions) => {
|
||||||
// Group transactions by member
|
// // Group transactions by member
|
||||||
const memberTxMap = rawTransactions.reduce((map, tx) => {
|
// const memberTxMap = rawTransactions.reduce((map, tx) => {
|
||||||
if (!map[tx.member]) {
|
// if (!map[tx.member]) {
|
||||||
map[tx.member] = []
|
// map[tx.member] = []
|
||||||
}
|
// }
|
||||||
map[tx.member].push(tx)
|
// map[tx.member].push(tx)
|
||||||
return map
|
// return map
|
||||||
}, {})
|
// }, {})
|
||||||
|
|
||||||
// Filter out members with both pending and non-pending transactions
|
// // Filter out members with both pending and non-pending transactions
|
||||||
return Object.values(memberTxMap)
|
// return Object.values(memberTxMap)
|
||||||
.filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
|
// .filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
|
||||||
.flat()
|
// .flat()
|
||||||
// .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
|
// // .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
|
||||||
// txs.some(tx => tx.approvalStatus !== 'PENDING')))
|
// // txs.some(tx => tx.approvalStatus !== 'PENDING')))
|
||||||
// .flat()
|
// // .flat()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fetch ban transactions
|
// // Fetch ban transactions
|
||||||
const rawBanTransactions = await searchTransactions({
|
// const rawBanTransactions = await searchTransactions({
|
||||||
txTypes: [banTxType],
|
// txTypes: [banTxType],
|
||||||
address: '',
|
// address: '',
|
||||||
confirmationStatus: 'CONFIRMED',
|
// confirmationStatus: 'CONFIRMED',
|
||||||
limit: 0,
|
// limit: 0,
|
||||||
reverse: true,
|
// reverse: true,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
startBlock: 1990000,
|
// startBlock: 1990000,
|
||||||
blockLimit: 0,
|
// blockLimit: 0,
|
||||||
txGroupId: 0,
|
// txGroupId: 0,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Filter transactions for bans
|
// // Filter transactions for bans
|
||||||
banTransactions = filterTransactions(rawBanTransactions)
|
// banTransactions = filterTransactions(rawBanTransactions)
|
||||||
console.warn('banTxData (filtered):', banTransactions)
|
// console.warn('banTxData (filtered):', banTransactions)
|
||||||
|
|
||||||
// Fetch kick transactions
|
// // Fetch kick transactions
|
||||||
const rawKickTransactions = await searchTransactions({
|
// const rawKickTransactions = await searchTransactions({
|
||||||
txTypes: [kickTxType],
|
// txTypes: [kickTxType],
|
||||||
address: '',
|
// address: '',
|
||||||
confirmationStatus: 'CONFIRMED',
|
// confirmationStatus: 'CONFIRMED',
|
||||||
limit: 0,
|
// limit: 0,
|
||||||
reverse: true,
|
// reverse: true,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
startBlock: 1990000,
|
// startBlock: 1990000,
|
||||||
blockLimit: 0,
|
// blockLimit: 0,
|
||||||
txGroupId: 0,
|
// txGroupId: 0,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Filter transactions for kicks
|
// // Filter transactions for kicks
|
||||||
kickTransactions = filterTransactions(rawKickTransactions)
|
// kickTransactions = filterTransactions(rawKickTransactions)
|
||||||
console.warn('kickTxData (filtered):', kickTransactions)
|
// console.warn('kickTxData (filtered):', kickTransactions)
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
// Example: fetch and save admin public keys and count
|
// 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 not topic mode, validate the user actually entered a valid Minter name
|
||||||
if (!isTopic) {
|
if (!isTopic) {
|
||||||
|
let minterAddress
|
||||||
publishedMinterName = await validateMinterName(minterNameInput)
|
publishedMinterName = await validateMinterName(minterNameInput)
|
||||||
if (!publishedMinterName) {
|
if (!publishedMinterName) {
|
||||||
alert(`"${minterNameInput}" doesn't seem to be a valid name. Please check or use topic mode.`)
|
try {
|
||||||
return
|
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
|
// Also check for existing card if not topic
|
||||||
if (!isUpdateCard && existingCardMinterNames.some(item => item.minterName === publishedMinterName)) {
|
if (!isUpdateCard && existingCardMinterNames.some(item => item.minterName === publishedMinterName)) {
|
||||||
const duplicateCardData = existingCardMinterNames.find(item => item.minterName === publishedMinterName)
|
const duplicateCardData = existingCardMinterNames.find(item => item.minterName === publishedMinterName)
|
||||||
const updateCard = confirm(
|
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) {
|
if (updateCard) {
|
||||||
existingEncryptedCardIdentifier = duplicateCardData.identifier
|
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
|
// Determine final card identifier
|
||||||
const currentTimestamp = Date.now()
|
const currentTimestamp = Date.now()
|
||||||
@ -1067,7 +1083,7 @@ const checkAndDisplayRemoveActions = async (adminYes, name, cardIdentifier, name
|
|||||||
} 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 || userState.isAdmin)) {
|
if (isBlockPassed && (userState.isMinterAdmin || userState.isAdmin)) {
|
||||||
@ -1260,7 +1276,7 @@ const getNewestAdminCommentTimestamp = async (cardIdentifier) => {
|
|||||||
|
|
||||||
// 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, minterAddress = '', header, content, links, creator, timestamp, poll, topicMode } = cardData
|
||||||
const formattedDate = new Date(timestamp).toLocaleString()
|
const formattedDate = new Date(timestamp).toLocaleString()
|
||||||
const minterAvatar = !topicMode ? await getMinterAvatar(minterName) : null
|
const minterAvatar = !topicMode ? await getMinterAvatar(minterName) : null
|
||||||
const creatorAvatar = await getMinterAvatar(creator)
|
const creatorAvatar = await getMinterAvatar(creator)
|
||||||
@ -1278,6 +1294,8 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
|
|
||||||
let showTopic = false
|
let showTopic = false
|
||||||
|
|
||||||
|
const { finalKickTxs, pendingKickTxs, finalBanTxs, pendingBanTxs } = await fetchAllKickBanTxData()
|
||||||
|
|
||||||
if (hasTopicMode) {
|
if (hasTopicMode) {
|
||||||
const modeVal = cardData.topicMode
|
const modeVal = cardData.topicMode
|
||||||
showTopic = (modeVal === true || modeVal === 'true')
|
showTopic = (modeVal === true || modeVal === 'true')
|
||||||
@ -1319,22 +1337,55 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
|
|||||||
accountInfo = await getNameInfo(verifiedName)
|
accountInfo = await getNameInfo(verifiedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountAddress = verifiedAddress ? addressVerification.address : accountInfo.owner
|
const accountAddress = verifiedAddress ? addressVerification.address : accountInfo.owner
|
||||||
const addressInfo = verifiedAddress ? addressVerification : await getAddressInfo(accountAddress)
|
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>`
|
levelText = ` - Level ${addressInfo.level}</h3>`
|
||||||
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
console.log(`name is validated, utilizing for removal features...${verifiedName}`)
|
||||||
penaltyText = addressInfo.blocksMintedPenalty == 0 ? '' : '<p>(has Blocks Penalty)<p>'
|
penaltyText = addressInfo.blocksMintedPenalty == 0 ? '' : '<p>(has Blocks Penalty)<p>'
|
||||||
adjustmentText = addressInfo.blocksMintedAdjustment == 0 ? '' : '<p>(has Blocks Adjustment)<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
|
showRemoveHtml = removeActionsHtml
|
||||||
|
|
||||||
if (userVote === 0) {
|
if (userVote === 0) {
|
||||||
cardColorCode = "rgba(1, 65, 39, 0.41)"; // or any green you want
|
cardColorCode = "rgba(1, 65, 39, 0.41)"; // 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(55, 12, 12, 0.61)"; // or any red you want
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.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...`)
|
console.warn(`account was already banned, displaying as such...`)
|
||||||
cardColorCode = 'rgb(24, 3, 3)'
|
cardColorCode = 'rgb(24, 3, 3)'
|
||||||
altText = `<h4 style="color:rgb(106, 2, 2); margin-bottom: 0.5em;">BANNED From MINTER Group</h4>`
|
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)){
|
// if (banTransactions.some((banTx) => banTx.groupId === 694 && banTx.offender === accountAddress)){
|
||||||
console.warn(`account was already kicked, displaying as such...`)
|
// console.warn(`account was already banned, displaying as such...`)
|
||||||
cardColorCode = 'rgb(29, 7, 4)'
|
// cardColorCode = 'rgb(24, 3, 3)'
|
||||||
altText = `<h4 style="color:rgb(143, 117, 21); margin-bottom: 0.5em;">KICKED From MINTER Group</h4>`
|
// altText = `<h4 style="color:rgb(106, 2, 2); margin-bottom: 0.5em;">BANNED From MINTER Group</h4>`
|
||||||
showRemoveHtml = ''
|
// showRemoveHtml = ''
|
||||||
if (!adminBoardState.kickedCards.has(cardIdentifier)){
|
// if (!adminBoardState.bannedCards.has(cardIdentifier)){
|
||||||
adminBoardState.kickedCards.add(cardIdentifier)
|
// adminBoardState.bannedCards.add(cardIdentifier)
|
||||||
}
|
// }
|
||||||
if (!showKickedBanned) {
|
// if (!showKickedBanned){
|
||||||
console.warn(`kick/ban checkbox is unchecked, card is kicked, not displaying...`)
|
// console.warn(`kick/bank checkbox is unchecked, and card is banned, not displaying...`)
|
||||||
return ''
|
// 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 {
|
} else {
|
||||||
console.log(`name could not be validated, assuming topic card (or some other issue with name validation) for removalActions`)
|
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
|
let currentMinterToolPage = 'overview'; // Track the current page
|
||||||
|
|
||||||
// Load latest state for admin verification
|
const loadMinterAdminToolsPage = async () => {
|
||||||
async function verifyMinterAdminState() {
|
|
||||||
const minterGroupAdmins = await fetchMinterGroupAdmins();
|
|
||||||
return minterGroupAdmins.members.some(admin => admin.member === userState.accountAddress && admin.isAdmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMinterAdminToolsPage() {
|
|
||||||
// Remove all body content except for menu elements
|
// Remove all body content except for menu elements
|
||||||
const bodyChildren = document.body.children;
|
const bodyChildren = document.body.children;
|
||||||
for (let i = bodyChildren.length - 1; i >= 0; i--) {
|
for (let i = bodyChildren.length - 1; i >= 0; i--) {
|
||||||
const child = bodyChildren[i];
|
const child = bodyChildren[i];
|
||||||
if (!child.classList.contains('menu')) {
|
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
|
// Set the background image directly from a file
|
||||||
const mainContent = document.createElement('div');
|
const mainContent = document.createElement('div')
|
||||||
|
// In your 'AdminTools' code
|
||||||
mainContent.innerHTML = `
|
mainContent.innerHTML = `
|
||||||
<div class="tools-main mbr-parallax-background cid-ttRnlSkg2R">
|
<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 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; ">
|
<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;">
|
<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><h2>COMING SOON...</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div><h2>Welcome to Admin Tools</h2></div>
|
||||||
<div id="tools-submenu" class="tools-submenu">
|
<div>
|
||||||
<div class="tools-buttons">
|
<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>
|
||||||
<button id="display-pending" class="tools-button">Display Pending Approval Transactions</button>
|
<p>More features will be added as time goes on. This is the start of the functionality here.</p>
|
||||||
<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>
|
|
||||||
<div id="tools-window" class="tools-window"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
|
||||||
document.body.appendChild(mainContent);
|
<div id="tools-submenu" class="tools-submenu">
|
||||||
|
<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" 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();
|
addToolsPageEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToolsPageEventListeners() {
|
function addToolsPageEventListeners() {
|
||||||
document.getElementById("display-pending").addEventListener("click", async () => {
|
document.getElementById("toggle-blocklist-button").addEventListener("click", async () => {
|
||||||
await displayPendingApprovals();
|
const container = document.getElementById("blocklist-container")
|
||||||
});
|
// toggle show/hide
|
||||||
|
container.style.display = (container.style.display === "none" ? "flex" : "none")
|
||||||
|
|
||||||
|
// if showing, load the block list
|
||||||
|
if (container.style.display === "flex") {
|
||||||
|
const currentBlockList = await fetchBlockList()
|
||||||
|
displayBlockList(currentBlockList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
document.getElementById("create-group-invite").addEventListener("click", async () => {
|
document.getElementById("blocklist-add-button").addEventListener("click", async () => {
|
||||||
createPendingGroupInvite();
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish updated
|
||||||
|
await publishBlockList(currentBlockList)
|
||||||
|
displayBlockList(currentBlockList)
|
||||||
|
blocklistInput.value = ""
|
||||||
|
alert(`"${nameToAdd}" added to the block list!`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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).`)
|
||||||
|
})
|
||||||
|
|
||||||
document.getElementById("create-promotion").addEventListener("click", async () => {
|
|
||||||
createPendingPromotion();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and display pending approvals
|
const displayBlockList = (blockedNames) => {
|
||||||
async function displayPendingApprovals() {
|
const blocklistDisplay = document.getElementById("blocklist-display")
|
||||||
console.log("Fetching pending approval transactions...");
|
if (!blockedNames || blockedNames.length === 0) {
|
||||||
const response = await qortalRequest({
|
blocklistDisplay.innerHTML = "<p>No blocked users currently.</p>"
|
||||||
action: "SEARCH_TRANSACTIONS",
|
return
|
||||||
txGroupId: 694,
|
|
||||||
txType: [
|
|
||||||
"ADD_GROUP_ADMIN",
|
|
||||||
"GROUP_INVITE"
|
|
||||||
],
|
|
||||||
confirmationStatus: "UNCONFIRMED",
|
|
||||||
limit: 0,
|
|
||||||
offset: 0,
|
|
||||||
reverse: false
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Fetched pending approvals: ", response);
|
|
||||||
|
|
||||||
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>';
|
|
||||||
}
|
}
|
||||||
|
blocklistDisplay.innerHTML = `
|
||||||
|
<ul>
|
||||||
|
${blockedNames.map(name => `<li>${name}</li>`).join("")}
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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).');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>
|
<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="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>
|
<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="newest" selected>Sort by Date</option>
|
||||||
<option value="name">Sort by Name</option>
|
<option value="name">Sort by Name</option>
|
||||||
<option value="recent-comments">Newest Comments</option>
|
<option value="recent-comments">Newest Comments</option>
|
||||||
<option value="least-votes">Least Votes</option>
|
<option value="least-votes">Least Votes</option>
|
||||||
<option value="most-votes">Most Votes</option>
|
<option value="most-votes">Most Votes</option>
|
||||||
</select>
|
</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="0">Show All</option>
|
||||||
<option value="1">Last 1 day</option>
|
<option value="1">Last 1 day</option>
|
||||||
<option value="7">Last 7 days</option>
|
<option value="7">Last 7 days</option>
|
||||||
@ -46,7 +46,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
</select>
|
</select>
|
||||||
<div id="cards-container" class="cards-container" style="margin-top: 20px;"></div>
|
<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;">
|
<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>
|
<h3>Create or Update Your Card</h3>
|
||||||
<label for="card-header">Header:</label>
|
<label for="card-header">Header:</label>
|
||||||
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
<input type="text" id="card-header" maxlength="100" placeholder="Enter card header" required>
|
||||||
@ -148,6 +148,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
await loadCards(minterCardIdentifierPrefix)
|
await loadCards(minterCardIdentifierPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
||||||
// Ensure the identifier starts with the prefix
|
// Ensure the identifier starts with the prefix
|
||||||
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
||||||
@ -197,7 +198,6 @@ const groupAndLabelByIdentifier = (allCards) => {
|
|||||||
}
|
}
|
||||||
mapById.get(card.identifier).push(card)
|
mapById.get(card.identifier).push(card)
|
||||||
})
|
})
|
||||||
|
|
||||||
// For each identifier's group, sort oldest->newest so the first is "master"
|
// For each identifier's group, sort oldest->newest so the first is "master"
|
||||||
const output = []
|
const output = []
|
||||||
for (const [identifier, group] of mapById.entries()) {
|
for (const [identifier, group] of mapById.entries()) {
|
||||||
@ -206,14 +206,12 @@ const groupAndLabelByIdentifier = (allCards) => {
|
|||||||
const bTime = b.created || 0
|
const bTime = b.created || 0
|
||||||
return aTime - bTime // oldest first
|
return aTime - bTime // oldest first
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mark the first as master
|
// Mark the first as master
|
||||||
group[0].isMaster = true
|
group[0].isMaster = true
|
||||||
// The rest are updates
|
// The rest are updates
|
||||||
for (let i = 1; i < group.length; i++) {
|
for (let i = 1; i < group.length; i++) {
|
||||||
group[i].isMaster = false
|
group[i].isMaster = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// push them all to output
|
// push them all to output
|
||||||
output.push(...group)
|
output.push(...group)
|
||||||
}
|
}
|
||||||
@ -221,7 +219,6 @@ const groupAndLabelByIdentifier = (allCards) => {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// no semicolons, using arrow functions
|
|
||||||
const groupByIdentifierOldestFirst = (allCards) => {
|
const groupByIdentifierOldestFirst = (allCards) => {
|
||||||
// map of identifier => array of cards
|
// map of identifier => array of cards
|
||||||
const mapById = new Map()
|
const mapById = new Map()
|
||||||
@ -232,7 +229,6 @@ const groupByIdentifierOldestFirst = (allCards) => {
|
|||||||
}
|
}
|
||||||
mapById.get(card.identifier).push(card)
|
mapById.get(card.identifier).push(card)
|
||||||
})
|
})
|
||||||
|
|
||||||
// sort each group oldest->newest
|
// sort each group oldest->newest
|
||||||
for (const [identifier, group] of mapById.entries()) {
|
for (const [identifier, group] of mapById.entries()) {
|
||||||
group.sort((a, b) => {
|
group.sort((a, b) => {
|
||||||
@ -245,22 +241,18 @@ const groupByIdentifierOldestFirst = (allCards) => {
|
|||||||
return mapById
|
return mapById
|
||||||
}
|
}
|
||||||
|
|
||||||
// no semicolons, arrow functions
|
|
||||||
const buildMinterNameGroups = async (mapById) => {
|
const buildMinterNameGroups = async (mapById) => {
|
||||||
// We'll build an array of objects: { minterName, cards }
|
// We'll build an array of objects: { minterName, cards }
|
||||||
// Then we can combine any that share the same minterName.
|
// Then we can combine any that share the same minterName.
|
||||||
|
|
||||||
const nameGroups = []
|
const nameGroups = []
|
||||||
|
|
||||||
for (let [identifier, group] of mapById.entries()) {
|
for (let [identifier, group] of mapById.entries()) {
|
||||||
// group[0] is the oldest => "master" card
|
// group[0] is the oldest => "master" card
|
||||||
let masterCard = group[0]
|
let masterCard = group[0]
|
||||||
|
|
||||||
// Filter out any cards that are not published by the 'masterPublisher'
|
// Filter out any cards that are not published by the 'masterPublisher'
|
||||||
const masterPublisherName = masterCard.name
|
const masterPublisherName = masterCard.name
|
||||||
// Remove any cards in this identifier group that have a different publisherName
|
// Remove any cards in this identifier group that have a different publisherName
|
||||||
const filteredGroup = group.filter(c => c.name === masterPublisherName)
|
const filteredGroup = group.filter(c => c.name === masterPublisherName)
|
||||||
|
|
||||||
// If filtering left zero cards, skip entire group
|
// If filtering left zero cards, skip entire group
|
||||||
if (!filteredGroup.length) {
|
if (!filteredGroup.length) {
|
||||||
console.warn(`All cards removed for identifier=${identifier} (different publishers). Skipping.`)
|
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
|
// Reassign group to the filtered version, then re-define masterCard
|
||||||
group = filteredGroup
|
group = filteredGroup
|
||||||
masterCard = group[0] // oldest after filtering
|
masterCard = group[0] // oldest after filtering
|
||||||
|
|
||||||
// attempt to obtain minterName from the master card
|
// attempt to obtain minterName from the master card
|
||||||
let masterMinterName
|
let masterMinterName
|
||||||
try {
|
try {
|
||||||
@ -278,14 +269,12 @@ const buildMinterNameGroups = async (mapById) => {
|
|||||||
console.warn(`Skipping entire group ${identifier}, no valid minterName from master`, err)
|
console.warn(`Skipping entire group ${identifier}, no valid minterName from master`, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store an object with the minterName we extracted, plus all cards in that group
|
// Store an object with the minterName we extracted, plus all cards in that group
|
||||||
nameGroups.push({
|
nameGroups.push({
|
||||||
minterName: masterMinterName,
|
minterName: masterMinterName,
|
||||||
cards: group // includes the master & updates
|
cards: group // includes the master & updates
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine them: minterName => array of *all* cards from all matching groups
|
// Combine them: minterName => array of *all* cards from all matching groups
|
||||||
const combinedMap = new Map()
|
const combinedMap = new Map()
|
||||||
for (const entry of nameGroups) {
|
for (const entry of nameGroups) {
|
||||||
@ -373,7 +362,6 @@ const processARBoardCards = async (allValidCards) => {
|
|||||||
return finalOutput
|
return finalOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Main function to load the Minter Cards ----------------------------------------
|
//Main function to load the Minter Cards ----------------------------------------
|
||||||
const loadCards = async (cardIdentifierPrefix) => {
|
const loadCards = async (cardIdentifierPrefix) => {
|
||||||
const cardsContainer = document.getElementById("cards-container")
|
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).
|
// else 'newest' => do nothing (already sorted newest-first by your process functions).
|
||||||
// Create the 'finalCardsArray' that includes the data, etc.
|
// Create the 'finalCardsArray' that includes the data, etc.
|
||||||
let finalCardsArray = []
|
let finalCardsArray = []
|
||||||
|
cardsContainer.innerHTML = ''
|
||||||
for (const card of finalCards) {
|
for (const card of finalCards) {
|
||||||
try {
|
try {
|
||||||
|
const skeletonHTML = createSkeletonCardHTML(card.identifier)
|
||||||
|
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||||
const cardDataResponse = await qortalRequest({
|
const cardDataResponse = await qortalRequest({
|
||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
name: card.name,
|
name: card.name,
|
||||||
@ -465,6 +455,7 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
if (!cardDataResponse || !cardDataResponse.poll) {
|
if (!cardDataResponse || !cardDataResponse.poll) {
|
||||||
// skip
|
// skip
|
||||||
console.warn(`Skipping card: missing data/poll. identifier=${card.identifier}`)
|
console.warn(`Skipping card: missing data/poll. identifier=${card.identifier}`)
|
||||||
|
removeSkeleton(card.identifier)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Extra validation: check poll ownership matches card publisher
|
// Extra validation: check poll ownership matches card publisher
|
||||||
@ -472,13 +463,22 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
const cardPublisherAddress = await fetchOwnerAddressFromName(card.name)
|
const cardPublisherAddress = await fetchOwnerAddressFromName(card.name)
|
||||||
if (pollPublisherAddress !== cardPublisherAddress) {
|
if (pollPublisherAddress !== cardPublisherAddress) {
|
||||||
console.warn(`Poll hijack attack found, discarding card ${card.identifier}`)
|
console.warn(`Poll hijack attack found, discarding card ${card.identifier}`)
|
||||||
|
removeSkeleton(card.identifier)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If ARBoard, do a quick address check
|
// If ARBoard, do a quick address check
|
||||||
if (isARBoard) {
|
if (isARBoard) {
|
||||||
const ok = await verifyARBoardAddress(cardDataResponse.minterName)
|
const ok = await verifyMinter(cardDataResponse.minterName)
|
||||||
if (!ok) {
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -491,18 +491,16 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error preparing card ${card.identifier}`, 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:
|
// Next, do the actual rendering:
|
||||||
cardsContainer.innerHTML = ""
|
// cardsContainer.innerHTML = ""
|
||||||
for (const cardObj of finalCardsArray) {
|
for (const cardObj of finalCardsArray) {
|
||||||
// Insert a skeleton first if you like
|
// Insert a skeleton first if you like
|
||||||
const skeletonHTML = createSkeletonCardHTML(cardObj.identifier)
|
// const skeletonHTML = createSkeletonCardHTML(cardObj.identifier)
|
||||||
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
// cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML)
|
||||||
// Build final HTML
|
// Build final HTML
|
||||||
const pollResults = await fetchPollResults(cardObj.cardDataResponse.poll)
|
const pollResults = await fetchPollResults(cardObj.cardDataResponse.poll)
|
||||||
const commentCount = await countComments(cardObj.identifier)
|
const commentCount = await countComments(cardObj.identifier)
|
||||||
@ -539,7 +537,7 @@ const loadCards = async (cardIdentifierPrefix) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifyARBoardAddress = async (minterName) => {
|
const verifyMinter = async (minterName) => {
|
||||||
try {
|
try {
|
||||||
const nameInfo = await getNameInfo(minterName)
|
const nameInfo = await getNameInfo(minterName)
|
||||||
|
|
||||||
@ -557,7 +555,7 @@ const verifyARBoardAddress = async (minterName) => {
|
|||||||
return (minterGroupAddresses.includes(minterAddress) ||
|
return (minterGroupAddresses.includes(minterAddress) ||
|
||||||
adminGroupAddresses.includes(minterAddress))
|
adminGroupAddresses.includes(minterAddress))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("verifyARBoardAddress error:", err)
|
console.warn("verifyMinter error:", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -781,6 +779,7 @@ const publishCard = async (cardIdentifierPrefix) => {
|
|||||||
content,
|
content,
|
||||||
links,
|
links,
|
||||||
creator: userState.accountName,
|
creator: userState.accountName,
|
||||||
|
creatorAddress: userState.accountAddress,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
poll: pollName,
|
poll: pollName,
|
||||||
}
|
}
|
||||||
@ -812,7 +811,7 @@ const publishCard = async (cardIdentifierPrefix) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isExistingCard){
|
if (isExistingCard){
|
||||||
alert("Card Updated Successfully! (No poll updates are possible at this time...)")
|
alert("Card Updated Successfully! (No poll updates possible)")
|
||||||
isExistingCard = false
|
isExistingCard = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1058,7 +1057,7 @@ const buildVotersTableHtml = (voters, tableColor) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Post a comment on a card. ---------------------------------
|
// Post a comment on a card. ---------------------------------
|
||||||
const postComment = async (cardIdentifier) => {
|
const postComment = async (cardIdentifier) => {
|
||||||
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`)
|
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`)
|
||||||
const commentText = commentInput.value.trim()
|
const commentText = commentInput.value.trim()
|
||||||
@ -1067,34 +1066,44 @@ const postComment = async (cardIdentifier) => {
|
|||||||
alert('Comment cannot be empty!')
|
alert('Comment cannot be empty!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const commentData = {
|
|
||||||
content: commentText,
|
|
||||||
creator: userState.accountName,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const base64CommentData = await objectToBase64(commentData)
|
//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 uniqueCommentIdentifier = `comment-${cardIdentifier}-${await uid()}`
|
||||||
|
let base64CommentData = await objectToBase64(commentData)
|
||||||
if (!base64CommentData) {
|
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))
|
base64CommentData = btoa(JSON.stringify(commentData))
|
||||||
}
|
}
|
||||||
|
|
||||||
await qortalRequest({
|
await qortalRequest({
|
||||||
action: 'PUBLISH_QDN_RESOURCE',
|
action: 'PUBLISH_QDN_RESOURCE',
|
||||||
name: userState.accountName,
|
name: userState.accountName,
|
||||||
service: 'BLOG_POST',
|
service: 'BLOG_POST',
|
||||||
identifier: commentIdentifier,
|
identifier: uniqueCommentIdentifier,
|
||||||
data64: base64CommentData,
|
data64: base64CommentData,
|
||||||
})
|
})
|
||||||
|
|
||||||
commentInput.value = ''
|
commentInput.value = ''
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error posting comment:', 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 ----------------------------
|
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||||
const fetchCommentsForCard = async (cardIdentifier) => {
|
const fetchCommentsForCard = async (cardIdentifier) => {
|
||||||
try {
|
try {
|
||||||
@ -1110,9 +1119,9 @@ const displayComments = async (cardIdentifier) => {
|
|||||||
try {
|
try {
|
||||||
const comments = await fetchCommentsForCard(cardIdentifier)
|
const comments = await fetchCommentsForCard(cardIdentifier)
|
||||||
const commentsContainer = document.getElementById(`comments-container-${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 voterMap = globalVoterMap.get(cardIdentifier) || new Map()
|
||||||
|
|
||||||
const commentHTMLArray = await Promise.all(
|
const commentHTMLArray = await Promise.all(
|
||||||
@ -1122,33 +1131,38 @@ const displayComments = async (cardIdentifier) => {
|
|||||||
action: "FETCH_QDN_RESOURCE",
|
action: "FETCH_QDN_RESOURCE",
|
||||||
name: comment.name,
|
name: comment.name,
|
||||||
service: "BLOG_POST",
|
service: "BLOG_POST",
|
||||||
identifier: comment.identifier,
|
identifier: comment.identifier
|
||||||
})
|
})
|
||||||
|
|
||||||
const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp);
|
if (!commentDataResponse || !commentDataResponse.creator) {
|
||||||
|
return null
|
||||||
const commenter = commentDataResponse.creator
|
}
|
||||||
const voterInfo = voterMap.get(commenter)
|
const commenterName = commentDataResponse.creator
|
||||||
|
const voterInfo = voterMap.get(commenterName)
|
||||||
let commentColor = "transparent"
|
let commentColor = "transparent"
|
||||||
let adminBadge = ""
|
let adminBadge = ""
|
||||||
|
|
||||||
|
if (blockedNames.includes(commenterName)) {
|
||||||
|
console.warn(`Skipping blocked commenter: ${commenterName}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (voterInfo) {
|
if (voterInfo) {
|
||||||
if (voterInfo.voterType === "Admin") {
|
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
|
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>`
|
adminBadge = `<span style="color: ${badgeColor}; font-weight: bold; margin-left: 0.5em;">(Admin)</span>`
|
||||||
} else {
|
} 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
|
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 `
|
return `
|
||||||
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: ${commentColor};">
|
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: ${commentColor};">
|
||||||
<p>
|
<p>
|
||||||
<strong><u>${commentDataResponse.creator}</u></strong>
|
<strong>${commenterName}</strong>
|
||||||
${adminBadge}
|
${adminBadge}
|
||||||
</p>
|
</p>
|
||||||
<p>${commentDataResponse.content}</p>
|
<p>${commentDataResponse.content}</p>
|
||||||
@ -1156,22 +1170,23 @@ const displayComments = async (cardIdentifier) => {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error processing comment ${comment.identifier}:`, err)
|
console.error(`Error with comment ${comment.identifier}:`, err)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
commentHTMLArray
|
commentHTMLArray
|
||||||
.filter((html) => html !== null) // Filter out failed comments
|
.filter(html => html !== null)
|
||||||
.forEach((commentHTML) => {
|
.forEach(commentHTML => {
|
||||||
commentsContainer.insertAdjacentHTML('beforeend', 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 --------------------
|
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
|
||||||
const toggleComments = async (cardIdentifier) => {
|
const toggleComments = async (cardIdentifier) => {
|
||||||
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
|
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
|
||||||
@ -1206,12 +1221,10 @@ const countComments = async (cardIdentifier) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const createModal = (modalType='') => {
|
const createModal = (modalType='') => {
|
||||||
if (document.getElementById(`${modalType}-modal`)) {
|
if (document.getElementById(`${modalType}-modal`)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const isIframe = (modalType === 'links')
|
const isIframe = (modalType === 'links')
|
||||||
|
|
||||||
const modalHTML = `
|
const modalHTML = `
|
||||||
@ -1259,8 +1272,8 @@ const createModal = (modalType='') => {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
document.body.insertAdjacentHTML('beforeend', modalHTML)
|
document.body.insertAdjacentHTML('beforeend', modalHTML)
|
||||||
|
|
||||||
const modal = document.getElementById(`${modalType}-modal`)
|
const modal = document.getElementById(`${modalType}-modal`)
|
||||||
|
|
||||||
window.addEventListener('click', (event) => {
|
window.addEventListener('click', (event) => {
|
||||||
if (event.target === modal) {
|
if (event.target === modal) {
|
||||||
closeModal(modalType)
|
closeModal(modalType)
|
||||||
@ -1268,7 +1281,6 @@ const createModal = (modalType='') => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const openLinksModal = async (link) => {
|
const openLinksModal = async (link) => {
|
||||||
const processedLink = await processLink(link)
|
const processedLink = await processLink(link)
|
||||||
const modal = document.getElementById('links-modal')
|
const modal = document.getElementById('links-modal')
|
||||||
@ -1311,9 +1323,9 @@ const togglePollDetails = (cardIdentifier) => {
|
|||||||
|
|
||||||
if (!detailsDiv || !modal || !modalContent) return
|
if (!detailsDiv || !modal || !modalContent) return
|
||||||
|
|
||||||
// modalContent.appendChild(detailsDiv)
|
|
||||||
modalContent.innerHTML = detailsDiv.innerHTML
|
modalContent.innerHTML = detailsDiv.innerHTML
|
||||||
modal.style.display = 'block'
|
modal.style.display = 'block'
|
||||||
|
|
||||||
window.onclick = (event) => {
|
window.onclick = (event) => {
|
||||||
if (event.target === modal) {
|
if (event.target === modal) {
|
||||||
modal.style.display = 'none'
|
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 createInviteButtonHtml = (creator, cardIdentifier) => {
|
||||||
|
const escapedCreator = escapeHTML(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 onclick="handleInviteMinter('${escapedCreator}')"
|
||||||
style="padding: 10px; background:rgb(0, 109, 76) ; color: white; border: dotted; border-color: white; cursor: pointer; border-radius: 5px;"
|
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) '"
|
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||||
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
|
onmouseout="this.style.backgroundColor='rgba(7, 122, 101, 0.63) '"
|
||||||
@ -1423,73 +1442,59 @@ const featureTriggerCheck = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||||
|
const isSomeTypaAdmin = userState.isAdmin || userState.isMinterAdmin
|
||||||
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()
|
||||||
|
|
||||||
|
// default needed admin count = 9, or 40% if block has passed
|
||||||
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}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if not enough adminYes votes, no invite button
|
||||||
if (adminYes < minAdminCount) {
|
if (adminYes < minAdminCount) {
|
||||||
console.warn(`Admin votes not high enough (have=${adminYes}, need=${minAdminCount}). No button.`)
|
console.warn(`Admin votes not high enough (have=${adminYes}, need=${minAdminCount}). No button.`)
|
||||||
return null
|
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 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({
|
// build the normal invite button & groupApprovalHtml
|
||||||
txTypes: ['GROUP_BAN'],
|
const inviteButtonHtml = isSomeTypaAdmin ? createInviteButtonHtml(creator, cardIdentifier) : ""
|
||||||
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)
|
|
||||||
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
|
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
|
||||||
|
|
||||||
|
// if user had no prior KICK/BAN
|
||||||
if (!priorBanOrKick) {
|
if (!priorBanOrKick) {
|
||||||
console.log(`No prior kick/ban found, creating invite (or approve) button...` )
|
console.log(`No prior kick/ban found, creating invite (or approve) button...`)
|
||||||
console.warn(`Existing Numbers - adminYes/minAdminCount: ${adminYes}/${minAdminCount}`)
|
console.warn(`Existing Numbers - adminYes/minAdminCount: ${adminYes}/${minAdminCount}`)
|
||||||
if (groupApprovalHtml){
|
|
||||||
|
// 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...`)
|
console.warn(`groupApprovalCheck found existing groupApproval, returning approval button instead of invite button...`)
|
||||||
return groupApprovalHtml
|
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
|
return inviteButtonHtml
|
||||||
|
|
||||||
} else if (priorBanOrKick){
|
} else {
|
||||||
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)...`)
|
// priorBanOrKick is true => show both
|
||||||
|
console.warn(`Prior kick/ban found! Including BOTH buttons...`)
|
||||||
return inviteButtonHtml + groupApprovalHtml
|
return inviteButtonHtml + groupApprovalHtml
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1540,12 +1545,13 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
blockLimit: 0,
|
blockLimit: 0,
|
||||||
txGroupId: 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 no pending transaction found, return null
|
||||||
if (!pendingApprovals || pendingApprovals.length === 0) {
|
if (!pendingApprovals || pendingApprovals.length === 0) {
|
||||||
console.warn("no pending approval transactions found, returning null...")
|
console.warn("no pending approval transactions found, returning null...")
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
const txSig = pendingApprovals[0].signature
|
const txSig = pendingApprovals[0].signature
|
||||||
// Among the already-confirmed GROUP_APPROVAL, filter for those referencing this txSig
|
// Among the already-confirmed GROUP_APPROVAL, filter for those referencing this txSig
|
||||||
@ -1557,7 +1563,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
getNameFromAddress
|
getNameFromAddress
|
||||||
)
|
)
|
||||||
|
|
||||||
if (transactionType === "GROUP_INVITE") {
|
if (transactionType === "GROUP_INVITE" && isSomeTypaAdmin) {
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<p style="color: rgb(181, 214, 100);">
|
<p style="color: rgb(181, 214, 100);">
|
||||||
@ -1587,7 +1593,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === "GROUP_KICK") {
|
if (transactionType === "GROUP_KICK" && isSomeTypaAdmin) {
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<p style="color: rgb(199, 100, 64);">
|
<p style="color: rgb(199, 100, 64);">
|
||||||
@ -1617,7 +1623,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === "GROUP_BAN") {
|
if (transactionType === "GROUP_BAN" && isSomeTypaAdmin) {
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<p style="color: rgb(189, 40, 40);">
|
<p style="color: rgb(189, 40, 40);">
|
||||||
@ -1647,7 +1653,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === "ADD_GROUP_ADMIN") {
|
if (transactionType === "ADD_GROUP_ADMIN" && isSomeTypaAdmin) {
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<p style="color: rgb(40, 144, 189);">
|
<p style="color: rgb(40, 144, 189);">
|
||||||
@ -1677,7 +1683,7 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === "REMOVE_GROUP_ADMIN") {
|
if (transactionType === "REMOVE_GROUP_ADMIN" && isSomeTypaAdmin) {
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<p style="color: rgb(189, 40, 40);">
|
<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)
|
// Build a Map of adminAddress => one transaction (to handle multiple approvals from same admin)
|
||||||
const approvalMap = new Map()
|
const approvalMap = new Map()
|
||||||
for (const tx of approvalTxs) {
|
for (const tx of approvalTxs) {
|
||||||
@ -1718,10 +1724,8 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
|||||||
approvalMap.set(adminAddr, tx)
|
approvalMap.set(adminAddr, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn the map into an array for iteration
|
// Turn the map into an array for iteration
|
||||||
const approvalArray = Array.from(approvalMap, ([adminAddr, tx]) => ({ adminAddr, tx }))
|
const approvalArray = Array.from(approvalMap, ([adminAddr, tx]) => ({ adminAddr, tx }))
|
||||||
|
|
||||||
// Build table rows asynchronously, since we need getNameFromAddress
|
// Build table rows asynchronously, since we need getNameFromAddress
|
||||||
const tableRows = await Promise.all(
|
const tableRows = await Promise.all(
|
||||||
approvalArray.map(async ({ adminAddr, tx }) => {
|
approvalArray.map(async ({ adminAddr, tx }) => {
|
||||||
@ -1732,15 +1736,12 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
|||||||
console.warn(`Error fetching name for ${adminAddr}:`, err)
|
console.warn(`Error fetching name for ${adminAddr}:`, err)
|
||||||
adminName = null
|
adminName = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayName =
|
const displayName =
|
||||||
adminName && adminName !== adminAddr
|
adminName && adminName !== adminAddr
|
||||||
? adminName
|
? adminName
|
||||||
: "(No registered name)"
|
: "(No registered name)"
|
||||||
|
|
||||||
// Format the transaction timestamp
|
|
||||||
const dateStr = new Date(tx.timestamp).toLocaleString()
|
const dateStr = new Date(tx.timestamp).toLocaleString()
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<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: #234565">${displayName}</td>
|
||||||
@ -1749,11 +1750,9 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
|||||||
`
|
`
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// The total unique approvals = number of entries in approvalMap
|
// The total unique approvals = number of entries in approvalMap
|
||||||
const uniqueApprovalCount = approvalMap.size;
|
const uniqueApprovalCount = approvalMap.size;
|
||||||
|
// 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
|
||||||
const containerHtml = `
|
const containerHtml = `
|
||||||
@ -1771,7 +1770,6 @@ async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
// Return both the container-wrapped table and the count of unique approvals
|
// Return both the container-wrapped table and the count of unique approvals
|
||||||
return {
|
return {
|
||||||
tableHtml: containerHtml,
|
tableHtml: containerHtml,
|
||||||
@ -1887,7 +1885,7 @@ const getNewestCommentTimestamp = async (cardIdentifier) => {
|
|||||||
|
|
||||||
// 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, creatorAddress, timestamp, poll } = cardData
|
||||||
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
const formattedDate = cardUpdatedTime ? new Date(cardUpdatedTime).toLocaleString() : new Date(timestamp).toLocaleString()
|
||||||
const avatarHtml = await getMinterAvatar(creator)
|
const avatarHtml = await getMinterAvatar(creator)
|
||||||
const linksHTML = links.map((link, index) => `
|
const linksHTML = links.map((link, index) => `
|
||||||
@ -1915,9 +1913,9 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
const invites = await fetchGroupInvitesByAddress(address)
|
const invites = await fetchGroupInvitesByAddress(address)
|
||||||
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
const hasMinterInvite = invites.some((invite) => invite.groupId === 694)
|
||||||
if (userVote === 0) {
|
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) {
|
} 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) {
|
} else if (hasMinterInvite) {
|
||||||
// If so, override background color & add an "INVITED" label
|
// If so, override background color & add an "INVITED" label
|
||||||
finalBgColor = "black";
|
finalBgColor = "black";
|
||||||
@ -1926,7 +1924,7 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
|
|||||||
inviteHtmlAdd = `
|
inviteHtmlAdd = `
|
||||||
<div id="join-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
<div id="join-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
<button
|
<button
|
||||||
style="padding: 8px; background:rgb(37, 99, 44); color:rgb(240, 240, 240); border: 1px solid rgb(255, 255, 255); border-radius: 5px; cursor: pointer;"
|
style="padding: 8px; background: rgb(37, 99, 44); color:rgb(240, 240, 240); border: 1px solid rgb(255, 255, 255); border-radius: 5px; cursor: pointer;"
|
||||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
||||||
onmouseout="this.style.backgroundColor='rgb(37, 99, 44) '"
|
onmouseout="this.style.backgroundColor='rgb(37, 99, 44) '"
|
||||||
onclick="handleJoinGroup('${userState.accountAddress}')">
|
onclick="handleJoinGroup('${userState.accountAddress}')">
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
const Q_MINTERSHIP_VERSION = "1.05"
|
||||||
|
|
||||||
const messageIdentifierPrefix = `mintership-forum-message`
|
const messageIdentifierPrefix = `mintership-forum-message`
|
||||||
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`
|
||||||
|
|
||||||
@ -66,6 +68,10 @@ if (localStorage.getItem("latestMessageIdentifiers")) {
|
|||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
console.log("DOMContentLoaded fired!")
|
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) ---
|
// --- GENERAL LINKS (MINTERSHIP-FORUM and MINTER-BOARD) ---
|
||||||
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]')
|
const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]')
|
||||||
@ -94,7 +100,6 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
await loadScript("./assets/js/MinterBoard.js")
|
await loadScript("./assets/js/MinterBoard.js")
|
||||||
}
|
}
|
||||||
await loadMinterBoardPage()
|
await loadMinterBoardPage()
|
||||||
createScrollToTopButton()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ const timestampToHumanReadableDate = async(timestamp) => {
|
|||||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
const seconds = String(date.getSeconds()).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)
|
console.log('Formatted date:', formattedDate)
|
||||||
return formattedDate
|
return formattedDate
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ const base64ToUint8Array = async (base64) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8ArrayToObject = async (uint8Array) => {
|
const uint8ArrayToObject = async (uint8Array) => {
|
||||||
// Decode the byte array using TextDecoder
|
// Decode the byte array using TextDecoder
|
||||||
@ -157,7 +157,7 @@ const uint8ArrayToObject = async (uint8Array) => {
|
|||||||
const obj = JSON.parse(jsonString)
|
const obj = JSON.parse(jsonString)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const objectToBase64 = async (obj) => {
|
const objectToBase64 = async (obj) => {
|
||||||
@ -331,7 +331,7 @@ 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}`)
|
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}`)
|
||||||
@ -438,17 +438,17 @@ const login = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getNameFromAddress = async (address) => {
|
const getNameFromAddress = async (address) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, {
|
const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { 'Accept': 'application/json' }
|
headers: { 'Accept': 'application/json' }
|
||||||
})
|
})
|
||||||
const names = await response.json()
|
const names = await response.json()
|
||||||
return names.length > 0 ? names[0].name : address // Return name if found, else return address
|
return names.length > 0 ? names[0].name : address // Return name if found, else return address
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching names for address ${address}:`, error)
|
console.error(`Error fetching names for address ${address}:`, error)
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -491,28 +491,6 @@ const fetchMinterGroupAdmins = async () => {
|
|||||||
//use what is returned .member to obtain each member... {"member": "memberAddress", "isAdmin": "true"}
|
//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 () => {
|
const fetchAllAdminGroupsMembers = async () => {
|
||||||
try {
|
try {
|
||||||
// We'll track addresses so we don't duplicate the same .member
|
// We'll track addresses so we don't duplicate the same .member
|
||||||
@ -545,7 +523,7 @@ const fetchAllAdminGroupsMembers = async () => {
|
|||||||
console.error('Error fetching admin group members', error)
|
console.error('Error fetching admin group members', error)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMinterGroupMembers = async () => {
|
const fetchMinterGroupMembers = async () => {
|
||||||
try {
|
try {
|
||||||
@ -651,7 +629,7 @@ const fetchGroupInvitesByAddress = async (address) => {
|
|||||||
console.error('Error fetching address group invites:', error)
|
console.error('Error fetching address group invites:', error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// QDN data calls --------------------------------------------------------------------------------------------------
|
// QDN data calls --------------------------------------------------------------------------------------------------
|
||||||
const searchLatestDataByIdentifier = async (identifier) => {
|
const searchLatestDataByIdentifier = async (identifier) => {
|
||||||
@ -1141,7 +1119,7 @@ const base64ToBlob = (base64String, mimeType) => {
|
|||||||
}
|
}
|
||||||
// Create a blob from the Uint8Array
|
// Create a blob from the Uint8Array
|
||||||
return new Blob([bytes], { type: mimeType })
|
return new Blob([bytes], { type: mimeType })
|
||||||
}
|
}
|
||||||
|
|
||||||
const base64ToBlobUrl = (base64, mimeType) => {
|
const base64ToBlobUrl = (base64, mimeType) => {
|
||||||
const binary = atob(base64)
|
const binary = atob(base64)
|
||||||
@ -1193,7 +1171,7 @@ const base64ToBlobUrl = (base64, mimeType) => {
|
|||||||
console.error("Skipping file due to error in fetchEncryptedImageBase64:", error)
|
console.error("Skipping file due to error in fetchEncryptedImageBase64:", error)
|
||||||
return null // indicates "missing or failed"
|
return null // indicates "missing or failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1363,7 +1341,7 @@ const processTransaction = async (signedTransaction) => {
|
|||||||
console.error("Error processing transaction:", error)
|
console.error("Error processing transaction:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
// Create a group invite transaction. This will utilize a default timeToLive (which is how long the tx will be alive, not the time until it IS live...) of 10 days in seconds, as the legacy UI has a bug that doesn't display invites older than 10 days.
|
||||||
@ -1860,7 +1838,7 @@ const searchTransactions = async ({
|
|||||||
console.error("Error in searchTransactions:", error)
|
console.error("Error in searchTransactions:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchPendingTransactions = async (limit = 20, offset = 0) => {
|
const searchPendingTransactions = async (limit = 20, offset = 0) => {
|
||||||
try {
|
try {
|
||||||
@ -1891,5 +1869,5 @@ const searchPendingTransactions = async (limit = 20, offset = 0) => {
|
|||||||
console.error("Error in searchPendingTransactions:", error)
|
console.error("Error in searchPendingTransactions:", error)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'">
|
<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>
|
<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">
|
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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">
|
<section data-bs-version="5.1" class="menu menu1 boldm5 cid-ttRnktJ11Q" once="menu" id="menu1-0">
|
||||||
|
|
||||||
@ -42,7 +48,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.04b)
|
<a class="navbar-caption display-4" href="index.html"><span class="navbar-caption display-4 version"></span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -61,7 +67,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.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>
|
</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>
|
||||||
|
|
||||||
@ -84,7 +90,7 @@
|
|||||||
<img src="assets/images/mbr-1623x1082.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
<img src="assets/images/mbr-1623x1082.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<h2 class="card-title mbr-fonts-style display-2">
|
<h2 class="card-title mbr-fonts-style display-2">
|
||||||
Admin Board</h2>
|
ADMIN BOARD</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -93,7 +99,7 @@
|
|||||||
<div class="item-wrapper">
|
<div class="item-wrapper">
|
||||||
<img src="assets/images/mbr-1623x1112.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
<img src="assets/images/mbr-1623x1112.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||||
<div class="item-content">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -109,7 +115,7 @@
|
|||||||
<img src="assets/images/mbr-1818x1212.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
<img src="assets/images/mbr-1818x1212.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<h2 class="card-title mbr-fonts-style display-2">
|
<h2 class="card-title mbr-fonts-style display-2">
|
||||||
Minter Admin Management (MAM) Board</h2>
|
MAM BOARD</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -118,7 +124,7 @@
|
|||||||
<div class="item-wrapper">
|
<div class="item-wrapper">
|
||||||
<img src="assets/images/mbr-1-1818x1212.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
<img src="assets/images/mbr-1-1818x1212.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||||
<div class="item-content">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -572,12 +578,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.04b)</h2>
|
<h2 class="mbr-section-title mbr-fonts-style display-5"><span class="navbar-caption display-4 version"></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.04beta</p>
|
<p class="mbr-link mbr-fonts-style display-4"><span class="navbar-caption display-4 version"></span></p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
@ -590,7 +596,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
<!-- <script src="./assets/smoothscroll/smooth-scroll.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/dropdown/js/navbar-dropdown.js"></script>
|
||||||
<script src="./assets/theme/js/script.js"></script>
|
<script src="./assets/theme/js/script.js"></script>
|
||||||
<script src="./assets/quill/quill.min.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/QortalApi.js"></script>
|
||||||
|
<script src="./assets/js/Shared.js"></script>
|
||||||
<script src="./assets/js/MinterBoard.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/AdminBoard.js"></script>
|
||||||
<script src="./assets/js/ARBoard.js"></script>
|
<script src="./assets/js/ARBoard.js"></script>
|
||||||
|
<script src="./assets/js/AdminTools.js"></script>
|
||||||
<script src="./assets/js/Q-Mintership.js"></script>
|
<script src="./assets/js/Q-Mintership.js"></script>
|
||||||
<input name="animation" type="hidden">
|
<input name="animation" type="hidden">
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user