All featureTrigger functionality for new core and bump to v1.0b
This commit is contained in:
parent
7f4848342d
commit
070653d675
@ -515,6 +515,16 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.promotion-section{
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Form Heading */
|
/* Form Heading */
|
||||||
.publish-card-view h3 {
|
.publish-card-view h3 {
|
||||||
@ -726,7 +736,7 @@ body {
|
|||||||
|
|
||||||
.minter-card-results div {
|
.minter-card-results div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.minter-card-results span {
|
.minter-card-results span {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{"Vn2IvHf07wyKYZiqFbKdyA==":"image15.jpg","tr8+8zJLW6gL2hgztHPxGA==":"qortector-by-itself-back-1136x940.png","1c5eh/fGPHPcoR//H+fMFw==":"qortector-by-itself-back-1-1136x940.png","1XXCnXt4wgUqJ31QK84ILg==":"qortector-by-itself-back-2-1136x940.png","0r3mBnBwn9G6GckwOiIF9w==":"qortector-by-itself-back-3-1136x940.png","fDUJaob7KfPxKBSLvIHo7A==":"wallpaper-qortal-darkest-2000x1333.jpg","byAqpaYKeHDBS3MDCvXHNA==":"background1.jpg","ZNDFbTjNPpnwXY6SJmH+Pg==":"image7.jpg","20tXYuAvQ8cCWSfVJ29Xog==":"image9.jpg","G0BdAlJezcIP2wV6CN9Y7A==":"image1.jpg","mAQyI8jPFlEmdmzmXlZibA==":"image14.jpg","rRdnwqn5O6lQx/lniiGUNA==":"mbr-1920x1280.jpg","sRD0qybUsOSHBp21gr2THw==":"mbr-1920x1152.jpg","OD73VTiBY7I+hZSg7Qndgg==":"modded-circle-2-new-100x100.png","nDMsvlHYt+gwcsRrGOKqzw==":"chd-circle-with-letters-100x100.png","KjibQCxRAQK0AAcPVGRyqA==":"qcloud-4-1920x1080.png","eNwLDioCZHd0wl3+/CkvSw==":"mbr-1623x1112.jpg","I6N1Z4qWlbHgBNW0yZdFqA==":"mbr-1623x1082.jpg","Oy2MMqCPK1Fdznz8QkHStw==":"again-edited-qortal-minting-icon-156x156.png","RMdq6M8IxyqkmFOKAv9xlQ==":"qortal_cube_original_by-100x100.png","Uh0/Evlsn5kppPdhSgz23w==":"qortal_ui_tray_minting-32x32.png","jM4Hh1L4b8PFO5ZreHRe8w==":"coin-815x815.jpg","tLev6c8MzefvKwbPhoSYuA==":"qortal-think-tank-logo-new-multiply-test-800x800.png"}
|
|
@ -1,4 +1,4 @@
|
|||||||
// NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards.
|
|
||||||
const isEncryptedTestMode = false
|
const isEncryptedTestMode = false
|
||||||
const encryptedCardIdentifierPrefix = "card-MAC"
|
const encryptedCardIdentifierPrefix = "card-MAC"
|
||||||
let isUpdateCard = false
|
let isUpdateCard = false
|
||||||
@ -13,7 +13,7 @@ let adminPublicKeys = []
|
|||||||
let kickTransactions = []
|
let kickTransactions = []
|
||||||
let banTransactions = []
|
let banTransactions = []
|
||||||
let adminBoardState = {
|
let adminBoardState = {
|
||||||
kickedCards: new Set(), // store identifiers or addresses
|
kickedCards: new Set(), // store identifiers
|
||||||
bannedCards: new Set(), // likewise
|
bannedCards: new Set(), // likewise
|
||||||
hiddenList: new Set(), // user-hidden
|
hiddenList: new Set(), // user-hidden
|
||||||
// ... we can add other things to state if needed...
|
// ... we can add other things to state if needed...
|
||||||
@ -107,6 +107,7 @@ const loadAdminBoardPage = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
document.body.appendChild(mainContent)
|
document.body.appendChild(mainContent)
|
||||||
const publishCardButton = document.getElementById("publish-card-button")
|
const publishCardButton = document.getElementById("publish-card-button")
|
||||||
|
|
||||||
@ -149,13 +150,21 @@ const loadAdminBoardPage = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('show-kicked-banned-checkbox')?.addEventListener('change', () => {
|
const showKickedBannedCheckbox = document.getElementById('admin-show-kicked-banned-checkbox')
|
||||||
fetchAllEncryptedCards()
|
|
||||||
})
|
|
||||||
|
|
||||||
document.getElementById('show-admin-hidden-checkbox')?.addEventListener('change', () => {
|
if (showKickedBannedCheckbox) {
|
||||||
fetchAllEncryptedCards()
|
showKickedBannedCheckbox.addEventListener('change', async (event) => {
|
||||||
})
|
await fetchAllEncryptedCards(true);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showHiddenCardsCheckbox = document.getElementById('admin-show-hidden-checkbox')
|
||||||
|
if (showHiddenCardsCheckbox) {
|
||||||
|
showHiddenCardsCheckbox.addEventListener('change', async (event) => {
|
||||||
|
await fetchAllEncryptedCards(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@ -172,10 +181,30 @@ const loadAdminBoardPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllKicKBanTxData = async () => {
|
const fetchAllKicKBanTxData = async () => {
|
||||||
const kickTxType = "GROUP_KICK";
|
const kickTxType = "GROUP_KICK"
|
||||||
const banTxType = "GROUP_BAN";
|
const banTxType = "GROUP_BAN"
|
||||||
|
|
||||||
// 1) Fetch ban transactions
|
// Helper function to filter transactions
|
||||||
|
const filterTransactions = (rawTransactions) => {
|
||||||
|
// Group transactions by member
|
||||||
|
const memberTxMap = rawTransactions.reduce((map, tx) => {
|
||||||
|
if (!map[tx.member]) {
|
||||||
|
map[tx.member] = []
|
||||||
|
}
|
||||||
|
map[tx.member].push(tx)
|
||||||
|
return map
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
// Filter out members with both pending and non-pending transactions
|
||||||
|
return Object.values(memberTxMap)
|
||||||
|
.filter(txs => txs.every(tx => tx.approvalStatus !== 'PENDING'))
|
||||||
|
.flat()
|
||||||
|
// .filter((txs) => !(txs.some(tx => tx.approvalStatus === 'PENDING') &&
|
||||||
|
// txs.some(tx => tx.approvalStatus !== 'PENDING')))
|
||||||
|
// .flat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch ban transactions
|
||||||
const rawBanTransactions = await searchTransactions({
|
const rawBanTransactions = await searchTransactions({
|
||||||
txTypes: [banTxType],
|
txTypes: [banTxType],
|
||||||
address: '',
|
address: '',
|
||||||
@ -186,12 +215,13 @@ const fetchAllKicKBanTxData = async () => {
|
|||||||
startBlock: 1990000,
|
startBlock: 1990000,
|
||||||
blockLimit: 0,
|
blockLimit: 0,
|
||||||
txGroupId: 0,
|
txGroupId: 0,
|
||||||
});
|
})
|
||||||
// Filter out 'PENDING'
|
|
||||||
banTransactions = rawBanTransactions.filter((tx) => tx.approvalStatus !== 'PENDING');
|
|
||||||
console.warn('banTxData (no PENDING):', banTransactions);
|
|
||||||
|
|
||||||
// 2) Fetch kick transactions
|
// Filter transactions for bans
|
||||||
|
banTransactions = filterTransactions(rawBanTransactions)
|
||||||
|
console.warn('banTxData (filtered):', banTransactions)
|
||||||
|
|
||||||
|
// Fetch kick transactions
|
||||||
const rawKickTransactions = await searchTransactions({
|
const rawKickTransactions = await searchTransactions({
|
||||||
txTypes: [kickTxType],
|
txTypes: [kickTxType],
|
||||||
address: '',
|
address: '',
|
||||||
@ -202,12 +232,12 @@ const fetchAllKicKBanTxData = async () => {
|
|||||||
startBlock: 1990000,
|
startBlock: 1990000,
|
||||||
blockLimit: 0,
|
blockLimit: 0,
|
||||||
txGroupId: 0,
|
txGroupId: 0,
|
||||||
});
|
})
|
||||||
// Filter out 'PENDING'
|
|
||||||
kickTransactions = rawKickTransactions.filter((tx) => tx.approvalStatus !== 'PENDING');
|
|
||||||
console.warn('kickTxData (no PENDING):', kickTransactions);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Filter transactions for kicks
|
||||||
|
kickTransactions = filterTransactions(rawKickTransactions)
|
||||||
|
console.warn('kickTxData (filtered):', kickTransactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Example: fetch and save admin public keys and count
|
// Example: fetch and save admin public keys and count
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards.
|
// // NOTE - Change isTestMode to false prior to actual release ---- !important - You may also change identifier if you want to not show older cards.
|
||||||
const testMode = false
|
const testMode = false
|
||||||
const cardIdentifierPrefix = "Minter-board-card"
|
const minterCardIdentifierPrefix = "Minter-board-card"
|
||||||
let isExistingCard = false
|
let isExistingCard = false
|
||||||
let existingCardData = {}
|
let existingCardData = {}
|
||||||
let existingCardIdentifier = {}
|
let existingCardIdentifier = {}
|
||||||
const MIN_ADMIN_YES_VOTES = 9;
|
const MIN_ADMIN_YES_VOTES = 9;
|
||||||
const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 9999950 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
|
const GROUP_APPROVAL_FEATURE_TRIGGER_HEIGHT = 2012800 //TODO update this to correct featureTrigger height when known, either that, or pull from core.
|
||||||
let featureTriggerPassed = false
|
let featureTriggerPassed = false
|
||||||
let isApproved = false
|
let isApproved = false
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
|
|
||||||
document.getElementById("publish-card-button").addEventListener("click", async () => {
|
document.getElementById("publish-card-button").addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
const fetchedCard = await fetchExistingCard()
|
const fetchedCard = await fetchExistingCard(minterCardIdentifierPrefix)
|
||||||
if (fetchedCard) {
|
if (fetchedCard) {
|
||||||
// An existing card is found
|
// An existing card is found
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
@ -84,7 +84,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
|
|
||||||
// Show the form
|
// Show the form
|
||||||
const publishCardView = document.getElementById("publish-card-view")
|
const publishCardView = document.getElementById("publish-card-view")
|
||||||
publishCardView.style.display = "flex";
|
publishCardView.style.display = "flex"
|
||||||
document.getElementById("cards-container").style.display = "none"
|
document.getElementById("cards-container").style.display = "none"
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking for existing card:", error)
|
console.error("Error checking for existing card:", error)
|
||||||
@ -95,7 +95,7 @@ const loadMinterBoardPage = async () => {
|
|||||||
document.getElementById("refresh-cards-button").addEventListener("click", async () => {
|
document.getElementById("refresh-cards-button").addEventListener("click", async () => {
|
||||||
const cardsContainer = document.getElementById("cards-container")
|
const cardsContainer = document.getElementById("cards-container")
|
||||||
cardsContainer.innerHTML = "<p>Refreshing cards...</p>"
|
cardsContainer.innerHTML = "<p>Refreshing cards...</p>"
|
||||||
await loadCards();
|
await loadCards(minterCardIdentifierPrefix)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -117,17 +117,17 @@ const loadMinterBoardPage = async () => {
|
|||||||
|
|
||||||
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
document.getElementById("publish-card-form").addEventListener("submit", async (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
await publishCard()
|
await publishCard(minterCardIdentifierPrefix)
|
||||||
})
|
})
|
||||||
await featureTriggerCheck()
|
await featureTriggerCheck()
|
||||||
await loadCards()
|
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(`${cardIdentifierPrefix}-`)) {
|
if ((!cardIdentifier.startsWith(minterCardIdentifierPrefix)) && (!cardIdentifier.startsWith(addRemoveIdentifierPrefix))) {
|
||||||
throw new Error('Invalid identifier format or prefix mismatch')
|
throw new Error('minterCard does not match identifier check')
|
||||||
}
|
}
|
||||||
// Split the identifier into parts
|
// Split the identifier into parts
|
||||||
const parts = cardIdentifier.split('-')
|
const parts = cardIdentifier.split('-')
|
||||||
// Ensure the format has at least 3 parts
|
// Ensure the format has at least 3 parts
|
||||||
@ -135,9 +135,22 @@ const extractMinterCardsMinterName = async (cardIdentifier) => {
|
|||||||
throw new Error('Invalid identifier format')
|
throw new Error('Invalid identifier format')
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1)
|
if (cardIdentifier.startsWith(minterCardIdentifierPrefix)){
|
||||||
const minterName = await searchSimpleResults.name
|
const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1)
|
||||||
return minterName
|
const minterName = await searchSimpleResults.name
|
||||||
|
return minterName
|
||||||
|
} else if (cardIdentifier.startsWith(addRemoveIdentifierPrefix)) {
|
||||||
|
const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1)
|
||||||
|
const publisherName = searchSimpleResults.name
|
||||||
|
const cardDataResponse = await qortalRequest({
|
||||||
|
action: "FETCH_QDN_RESOURCE",
|
||||||
|
name: publisherName,
|
||||||
|
service: "BLOG_POST",
|
||||||
|
identifier: cardIdentifier,
|
||||||
|
})
|
||||||
|
const minterName = cardDataResponse.minterName
|
||||||
|
return minterName
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -146,6 +159,7 @@ const extractMinterCardsMinterName = async (cardIdentifier) => {
|
|||||||
const processMinterCards = async (validMinterCards) => {
|
const processMinterCards = async (validMinterCards) => {
|
||||||
const latestCardsMap = new Map()
|
const latestCardsMap = new Map()
|
||||||
|
|
||||||
|
// Deduplicate by identifier, keeping the most recent
|
||||||
validMinterCards.forEach(card => {
|
validMinterCards.forEach(card => {
|
||||||
const timestamp = card.updated || card.created || 0
|
const timestamp = card.updated || card.created || 0
|
||||||
const existingCard = latestCardsMap.get(card.identifier)
|
const existingCard = latestCardsMap.get(card.identifier)
|
||||||
@ -155,28 +169,54 @@ const processMinterCards = async (validMinterCards) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Convert Map back to array
|
||||||
|
const uniqueValidCards = Array.from(latestCardsMap.values())
|
||||||
|
|
||||||
const minterGroupMembers = await fetchMinterGroupMembers()
|
const minterGroupMembers = await fetchMinterGroupMembers()
|
||||||
const minterGroupAddresses = minterGroupMembers.map(m => m.member)
|
const minterGroupAddresses = minterGroupMembers.map(m => m.member)
|
||||||
const minterNameMap = new Map()
|
const minterNameMap = new Map()
|
||||||
|
|
||||||
for (const card of validMinterCards) {
|
// For each card, extract minterName safely
|
||||||
const minterName = await extractMinterCardsMinterName(card.identifier)
|
for (const card of uniqueValidCards) {
|
||||||
|
let minterName
|
||||||
|
try {
|
||||||
|
// If this throws, we catch below and skip
|
||||||
|
minterName = await extractMinterCardsMinterName(card.identifier)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
`Skipping card ${card.identifier} because extractMinterCardsMinterName failed:`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
continue // Skip this card and move on
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`minterName`, minterName)
|
console.log(`minterName`, minterName)
|
||||||
|
// Next, get minterNameInfo
|
||||||
const minterNameInfo = await getNameInfo(minterName)
|
const minterNameInfo = await getNameInfo(minterName)
|
||||||
if (!minterNameInfo) {
|
if (!minterNameInfo) {
|
||||||
console.warn(`minterNameInfo is null for minter: ${minterName}`)
|
console.warn(`minterNameInfo is null for minter: ${minterName}, skipping card.`)
|
||||||
continue
|
|
||||||
}
|
|
||||||
const minterAddress = await minterNameInfo.owner
|
|
||||||
|
|
||||||
if (!minterAddress) {
|
|
||||||
console.warn(`minterAddress is FAKE or INVALID in some way! minter: ${minterName}`)
|
|
||||||
continue
|
|
||||||
} else if (minterGroupAddresses.includes(minterAddress)){
|
|
||||||
console.log(`existing minter FOUND and/or FAKE NAME FOUND (if following is null then fake name: ${minterAddress}), not including minter card: ${card.identifier}`)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minterAddress = minterNameInfo.owner
|
||||||
|
// Validate the address
|
||||||
|
const addressValid = await getAddressInfo(minterAddress)
|
||||||
|
if (!minterAddress || !addressValid) {
|
||||||
|
console.warn(`minterAddress invalid or missing for: ${minterName}, skipping card.`, minterAddress)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a 'regular' minter card, skip if user is already a minter
|
||||||
|
if (!card.identifier.includes('QM-AR-card')) {
|
||||||
|
if (minterGroupAddresses.includes(minterAddress)) {
|
||||||
|
console.log(
|
||||||
|
`existing minter found or fake name detected. Not including minter card: ${card.identifier}`
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep only the most recent card for each minterName
|
||||||
const existingCard = minterNameMap.get(minterName)
|
const existingCard = minterNameMap.get(minterName)
|
||||||
const cardTimestamp = card.updated || card.created || 0
|
const cardTimestamp = card.updated || card.created || 0
|
||||||
const existingTimestamp = existingCard?.updated || existingCard?.created || 0
|
const existingTimestamp = existingCard?.updated || existingCard?.created || 0
|
||||||
@ -186,6 +226,7 @@ const processMinterCards = async (validMinterCards) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert minterNameMap to final array
|
||||||
const finalCards = []
|
const finalCards = []
|
||||||
const seenMinterNames = new Set()
|
const seenMinterNames = new Set()
|
||||||
|
|
||||||
@ -196,6 +237,7 @@ const processMinterCards = async (validMinterCards) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by timestamp descending
|
||||||
finalCards.sort((a, b) => {
|
finalCards.sort((a, b) => {
|
||||||
const timestampA = a.updated || a.created || 0
|
const timestampA = a.updated || a.created || 0
|
||||||
const timestampB = b.updated || b.created || 0
|
const timestampB = b.updated || b.created || 0
|
||||||
@ -205,35 +247,37 @@ const processMinterCards = async (validMinterCards) => {
|
|||||||
return finalCards
|
return finalCards
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Main function to load the Minter Cards ----------------------------------------
|
//Main function to load the Minter Cards ----------------------------------------
|
||||||
const loadCards = async () => {
|
const loadCards = async (cardIdentifierPrefix) => {
|
||||||
const cardsContainer = document.getElementById("cards-container")
|
const cardsContainer = document.getElementById("cards-container")
|
||||||
|
let isARBoard = false
|
||||||
cardsContainer.innerHTML = "<p>Loading cards...</p>"
|
cardsContainer.innerHTML = "<p>Loading cards...</p>"
|
||||||
|
if ((cardIdentifierPrefix.startsWith(`QM-AR-card`))) {
|
||||||
|
isARBoard = true
|
||||||
|
console.warn(`ARBoard determined:`, isARBoard)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, '' , 0)
|
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, '' , 0)
|
||||||
|
|
||||||
if (!response || !Array.isArray(response) || response.length === 0) {
|
if (!response || !Array.isArray(response) || response.length === 0) {
|
||||||
cardsContainer.innerHTML = "<p>No cards found.</p>"
|
cardsContainer.innerHTML = "<p>No cards found.</p>"
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate cards and filter
|
// Validate cards and filter
|
||||||
const validatedCards = await Promise.all(
|
const validatedCards = await Promise.all(
|
||||||
response.map(async card => {
|
response.map(async card => {
|
||||||
const isValid = await validateCardStructure(card)
|
const isValid = await validateCardStructure(card)
|
||||||
return isValid ? card : null
|
return isValid ? card : null
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
const validCards = validatedCards.filter(card => card !== null)
|
const validCards = validatedCards.filter(card => card !== null)
|
||||||
|
|
||||||
if (validCards.length === 0) {
|
if (validCards.length === 0) {
|
||||||
cardsContainer.innerHTML = "<p>No valid cards found.</p>"
|
cardsContainer.innerHTML = "<p>No valid cards found.</p>"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalCards = await processMinterCards(validCards)
|
const finalCards = await processMinterCards(validCards)
|
||||||
|
|
||||||
// Display skeleton cards immediately
|
// Display skeleton cards immediately
|
||||||
@ -264,7 +308,6 @@ const loadCards = async () => {
|
|||||||
removeSkeleton(card.identifier)
|
removeSkeleton(card.identifier)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const pollPublisherPublicKey = await getPollPublisherPublicKey(cardDataResponse.poll)
|
const pollPublisherPublicKey = await getPollPublisherPublicKey(cardDataResponse.poll)
|
||||||
const cardPublisherPublicKey = await getPublicKeyByName(card.name)
|
const cardPublisherPublicKey = await getPublicKeyByName(card.name)
|
||||||
|
|
||||||
@ -273,14 +316,39 @@ const loadCards = async () => {
|
|||||||
removeSkeleton(card.identifier)
|
removeSkeleton(card.identifier)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
const pollResults = await fetchPollResults(cardDataResponse.poll)
|
||||||
const bgColor = generateDarkPastelBackgroundBy(card.name)
|
const bgColor = generateDarkPastelBackgroundBy(card.name)
|
||||||
const commentCount = await countComments(card.identifier)
|
const commentCount = await countComments(card.identifier)
|
||||||
const cardUpdatedTime = card.updated || null
|
const cardUpdatedTime = card.updated || null
|
||||||
const finalCardHTML = await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
|
||||||
|
if (isARBoard) {
|
||||||
|
const name = await getNameInfo(cardDataResponse.minterName)
|
||||||
|
const address = name.owner
|
||||||
|
if (minterAdminAddresses && minterGroupAddresses) {
|
||||||
|
if (!minterAdminAddresses.includes(address) && !minterGroupAddresses.includes(address)) {
|
||||||
|
console.warn(`Found card from ARBoard that contained a non-minter!`)
|
||||||
|
removeSkeleton(card.identifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (!minterAdminAddresses || !minterGroupAddresses){
|
||||||
|
const minterGroup = await fetchMinterGroupMembers()
|
||||||
|
const adminGroup = await fetchMinterGroupAdmins()
|
||||||
|
minterAdminAddresses = adminGroup.map(m => m.member)
|
||||||
|
minterGroupAddresses = minterGroup.map(m => m.member)
|
||||||
|
if (!minterAdminAddresses.includes(address) && !minterGroupAddresses.includes(address)) {
|
||||||
|
console.warn(`Found card from ARBoard that contained a non-minter!`)
|
||||||
|
removeSkeleton(card.identifier)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalCardHTML = isARBoard ? // If we're calling from the ARBoard, we will create HTML with a different function.
|
||||||
|
await createARCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
||||||
|
:
|
||||||
|
await createCardHTML(cardDataResponse, pollResults, card.identifier, commentCount, cardUpdatedTime, bgColor)
|
||||||
replaceSkeleton(card.identifier, finalCardHTML)
|
replaceSkeleton(card.identifier, finalCardHTML)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error processing card ${card.identifier}:`, error)
|
console.error(`Error processing card ${card.identifier}:`, error)
|
||||||
removeSkeleton(card.identifier)
|
removeSkeleton(card.identifier)
|
||||||
@ -326,7 +394,7 @@ const createSkeletonCardHTML = (cardIdentifier) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
|
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
|
||||||
const fetchExistingCard = async () => {
|
const fetchExistingCard = async (cardIdentifierPrefix) => {
|
||||||
try {
|
try {
|
||||||
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0, 0, '', true)
|
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0, 0, '', true)
|
||||||
|
|
||||||
@ -344,7 +412,6 @@ const fetchExistingCard = async () => {
|
|||||||
service: "BLOG_POST",
|
service: "BLOG_POST",
|
||||||
identifier: mostRecentCard.identifier
|
identifier: mostRecentCard.identifier
|
||||||
})
|
})
|
||||||
|
|
||||||
existingCardIdentifier = mostRecentCard.identifier
|
existingCardIdentifier = mostRecentCard.identifier
|
||||||
existingCardData = cardDataResponse
|
existingCardData = cardDataResponse
|
||||||
isExistingCard = true
|
isExistingCard = true
|
||||||
@ -418,7 +485,7 @@ const loadCardIntoForm = async (cardData) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main function to publish a new Minter Card -----------------------------------------------
|
// Main function to publish a new Minter Card -----------------------------------------------
|
||||||
const publishCard = async () => {
|
const publishCard = async (cardIdentifierPrefix) => {
|
||||||
|
|
||||||
const minterGroupData = await fetchMinterGroupMembers()
|
const minterGroupData = await fetchMinterGroupMembers()
|
||||||
const minterGroupAddresses = minterGroupData.map(m => m.member)
|
const minterGroupAddresses = minterGroupData.map(m => m.member)
|
||||||
@ -486,7 +553,7 @@ const publishCard = async () => {
|
|||||||
document.getElementById("publish-card-form").reset()
|
document.getElementById("publish-card-form").reset()
|
||||||
document.getElementById("publish-card-view").style.display = "none"
|
document.getElementById("publish-card-view").style.display = "none"
|
||||||
document.getElementById("cards-container").style.display = "flex"
|
document.getElementById("cards-container").style.display = "flex"
|
||||||
await loadCards()
|
await loadCards(minterCardIdentifierPrefix)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
@ -1084,44 +1151,75 @@ const featureTriggerCheck = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
const checkAndDisplayInviteButton = async (adminYes, creator, cardIdentifier) => {
|
||||||
|
|
||||||
|
if (!userState.isMinterAdmin){
|
||||||
|
console.warn(`User is NOT an admin, not displaying invite/approve button...`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const isBlockPassed = await featureTriggerCheck()
|
const isBlockPassed = await featureTriggerCheck()
|
||||||
let minAdminCount
|
|
||||||
const minterAdmins = await fetchMinterGroupAdmins()
|
const minterAdmins = await fetchMinterGroupAdmins()
|
||||||
|
|
||||||
if (!isBlockPassed){
|
let minAdminCount = 9
|
||||||
console.warn(`feature trigger not passed, using static number for minAdminCount`)
|
|
||||||
minAdminCount = 9
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((minterAdmins) && (minterAdmins.length === 1)){
|
|
||||||
console.warn(`simply a double-check that there is only one MINTER group admin, in which case the group hasn't been transferred to null...keeping default minAdminCount of: ${minAdminCount}`)
|
|
||||||
} else if ((minterAdmins) && (minterAdmins.length > 1) && isBlockPassed){
|
|
||||||
const totalAdmins = minterAdmins.length
|
|
||||||
const fortyPercent = totalAdmins * 0.40
|
|
||||||
minAdminCount = Math.round(fortyPercent)
|
|
||||||
console.warn(`this is another check to ensure minterAdmin group has more than 1 admin. IF so we will calculate the 40% needed for GROUP_APPROVAL, that number is: ${minAdminCount}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBlockPassed) {
|
if (isBlockPassed) {
|
||||||
const minterNameInfo = await getNameInfo(creator)
|
minAdminCount = Math.round(minterAdmins.length * 0.4)
|
||||||
const minterAddress = await minterNameInfo.owner
|
console.warn(`Using 40% => ${minAdminCount}`)
|
||||||
if (userState.isMinterAdmin){
|
}
|
||||||
let groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
|
|
||||||
if (groupApprovalHtml) {
|
if (adminYes < minAdminCount) {
|
||||||
return groupApprovalHtml
|
console.warn(`Admin votes not high enough (have=${adminYes}, need=${minAdminCount}). No button.`)
|
||||||
}
|
return null
|
||||||
}else{
|
}
|
||||||
console.log(`USER NOT ADMIN, no need for group approval buttons...`)
|
console.log(`passed initial button creation checks, pulling additional data...`)
|
||||||
|
|
||||||
|
const minterNameInfo = await getNameInfo(creator)
|
||||||
|
const minterAddress = await minterNameInfo.owner
|
||||||
|
|
||||||
|
const previousBanTx = await searchTransactions({
|
||||||
|
txTypes: ['GROUP_BAN'],
|
||||||
|
address: `${minterAddress}`,
|
||||||
|
confirmationStatus: 'CONFIRMED',
|
||||||
|
limit: 0,
|
||||||
|
reverse: true,
|
||||||
|
offset: 0,
|
||||||
|
startBlock: 1990000,
|
||||||
|
blockLimit: 0,
|
||||||
|
txGroupId: 0,
|
||||||
|
})
|
||||||
|
const previousBan = previousBanTx.filter((tx) => tx.approvalStatus !== 'PENDING')
|
||||||
|
const previousKickTx = await searchTransactions({
|
||||||
|
txTypes: ['GROUP_KICK'],
|
||||||
|
address: `${minterAddress}`,
|
||||||
|
confirmationStatus: 'CONFIRMED',
|
||||||
|
limit: 0,
|
||||||
|
reverse: true,
|
||||||
|
offset: 0,
|
||||||
|
startBlock: 1990000,
|
||||||
|
blockLimit: 0,
|
||||||
|
txGroupId: 0,
|
||||||
|
})
|
||||||
|
const previousKick = previousKickTx.filter((tx) => tx.approvalStatus !== 'PENDING')
|
||||||
|
const priorBanOrKick = (previousKick.length > 0 || previousBan.length > 0)
|
||||||
|
|
||||||
|
console.warn(`PriorBanOrKick determination:`, priorBanOrKick)
|
||||||
|
|
||||||
|
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
||||||
|
const groupApprovalHtml = await checkGroupApprovalAndCreateButton(minterAddress, cardIdentifier, "GROUP_INVITE")
|
||||||
|
|
||||||
|
if (!priorBanOrKick) {
|
||||||
|
console.log(`No prior kick/ban found, creating invite (or approve) button...` )
|
||||||
|
console.warn(`Existing Numbers - adminYes/minAdminCount: ${adminYes}/${minAdminCount}`)
|
||||||
|
if (groupApprovalHtml){
|
||||||
|
console.warn(`groupApprovalCheck found existing groupApproval, returning approval button instead of invite button...`)
|
||||||
|
return groupApprovalHtml
|
||||||
}
|
}
|
||||||
}
|
console.warn(`No pending approvals or prior kick/ban found, but votes are high enough, returning invite button...`)
|
||||||
|
|
||||||
if (adminYes >= minAdminCount && (userState.isMinterAdmin)) {
|
|
||||||
const inviteButtonHtml = createInviteButtonHtml(creator, cardIdentifier)
|
|
||||||
console.log(`admin votes over 9, creating invite button...`, adminYes)
|
|
||||||
return inviteButtonHtml
|
return inviteButtonHtml
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
} else if (priorBanOrKick){
|
||||||
|
console.warn(`Prior kick/ban found! Including BOTH buttons (due to complexities in checking, displaying both buttons is simpler than attempting to display only one)...`)
|
||||||
|
return inviteButtonHtml + groupApprovalHtml
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offset = 0) => {
|
const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offset = 0) => {
|
||||||
@ -1132,17 +1230,12 @@ const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offse
|
|||||||
if (txType) {
|
if (txType) {
|
||||||
relevantTypes = new Set([txType])
|
relevantTypes = new Set([txType])
|
||||||
} else {
|
} else {
|
||||||
relevantTypes = new Set(["GROUP_INVITE", "GROUP_BAN", "GROUP_KICK"])
|
relevantTypes = new Set(["GROUP_INVITE", "GROUP_BAN", "GROUP_KICK", "ADD_GROUP_ADMIN", "REMOVE_GROUP_ADMIN"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter pending TX for relevant types
|
// Filter pending TX for relevant types
|
||||||
const relevantTxs = pendingTxs.filter((tx) => relevantTypes.has(tx.type))
|
const relevantTxs = pendingTxs.filter((tx) => relevantTypes.has(tx.type))
|
||||||
|
|
||||||
// Further filter by whether 'address' matches the correct field
|
|
||||||
// - GROUP_INVITE => invitee
|
|
||||||
// - GROUP_BAN => offender
|
|
||||||
// - GROUP_KICK => member
|
|
||||||
// If the user passed a specific txType, only one branch might matter.
|
|
||||||
const matchedTxs = relevantTxs.filter((tx) => {
|
const matchedTxs = relevantTxs.filter((tx) => {
|
||||||
switch (tx.type) {
|
switch (tx.type) {
|
||||||
case "GROUP_INVITE":
|
case "GROUP_INVITE":
|
||||||
@ -1151,33 +1244,22 @@ const findPendingApprovalTxForAddress = async (address, txType, limit = 0, offse
|
|||||||
return tx.offender === address
|
return tx.offender === address
|
||||||
case "GROUP_KICK":
|
case "GROUP_KICK":
|
||||||
return tx.member === address
|
return tx.member === address
|
||||||
|
case "ADD_GROUP_ADMIN":
|
||||||
|
return tx.member === address
|
||||||
|
case "REMOVE_GROUP_ADMIN":
|
||||||
|
return tx.admin === address
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.warn(`matchedTxs:`,matchedTxs)
|
||||||
|
|
||||||
return matchedTxs // Array of matching pending transactions
|
return matchedTxs // Array of matching pending transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transactionType) => {
|
const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transactionType) => {
|
||||||
const txTypes = [transactionType]
|
|
||||||
|
|
||||||
const txSearchResults = await searchTransactions({
|
|
||||||
txTypes,
|
|
||||||
address: `${address}`,
|
|
||||||
confirmationStatus: 'CONFIRMED',
|
|
||||||
limit: 0,
|
|
||||||
reverse: true,
|
|
||||||
offset: 0,
|
|
||||||
startBlock: 1990000,
|
|
||||||
blockLimit: 0,
|
|
||||||
txGroupId: 694
|
|
||||||
})
|
|
||||||
|
|
||||||
const approvalTxType = ['GROUP_APPROVAL']
|
|
||||||
const approvalSearchResults = await searchTransactions({
|
const approvalSearchResults = await searchTransactions({
|
||||||
txTypes: approvalTxType,
|
txTypes: ['GROUP_APPROVAL'],
|
||||||
address: `${address}`,
|
|
||||||
confirmationStatus: 'CONFIRMED',
|
confirmationStatus: 'CONFIRMED',
|
||||||
limit: 0,
|
limit: 0,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
@ -1186,73 +1268,246 @@ const checkGroupApprovalAndCreateButton = async (address, cardIdentifier, transa
|
|||||||
blockLimit: 0,
|
blockLimit: 0,
|
||||||
txGroupId: 0
|
txGroupId: 0
|
||||||
})
|
})
|
||||||
|
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType);
|
||||||
|
|
||||||
console.warn(`transaction search results, this is for comparison to pendingApprovals search, these are not used:`,txSearchResults)
|
// If no pending transaction found, return null
|
||||||
const pendingApprovals = await findPendingApprovalTxForAddress(address, transactionType)
|
if (!pendingApprovals || pendingApprovals.length === 0) {
|
||||||
|
console.warn("no pending approval transactions found, returning null...")
|
||||||
if (pendingApprovals) {
|
return null;
|
||||||
console.warn(`this is what is used for pending results... pendingApprovals FOUND:`, pendingApprovals)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pendingApprovals.length === 0) || (!pendingApprovals)) {
|
|
||||||
console.warn(`no pending approval transactions found, returning null...`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const existingApprovalCount = approvalSearchResults.length
|
|
||||||
const txSig = pendingApprovals[0].signature
|
const txSig = pendingApprovals[0].signature
|
||||||
|
// Among the already-confirmed GROUP_APPROVAL, filter for those referencing this txSig
|
||||||
|
const relevantApprovals = approvalSearchResults.filter(
|
||||||
|
(approvalTx) => approvalTx.pendingSignature === txSig
|
||||||
|
)
|
||||||
|
const { tableHtml, uniqueApprovalCount } = await buildApprovalTableHtml(
|
||||||
|
relevantApprovals,
|
||||||
|
getNameFromAddress
|
||||||
|
)
|
||||||
|
|
||||||
if (transactionType === `GROUP_INVITE`){
|
if (transactionType === "GROUP_INVITE") {
|
||||||
|
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<h2 style="color:rgb(181, 214, 100);">Existing Invite Approvals: ${existingApprovalCount}</h2>
|
<p style="color: rgb(181, 214, 100);">
|
||||||
<button
|
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||||
style="padding: 8px; background:rgb(37, 97, 99); color:rgb(215, 215, 215) ; border: 1px solid #333; border-color: white; border-radius: 5px; cursor: pointer;"
|
</p>
|
||||||
onmouseover="this.style.backgroundColor='rgb(25, 47, 39) '"
|
${tableHtml}
|
||||||
onmouseout="this.style.backgroundColor='rgb(37, 96, 99) '"
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
onclick="handleGroupApproval('${txSig}')">
|
<button
|
||||||
Approve Invite Tx
|
style="
|
||||||
</button>
|
padding: 8px;
|
||||||
</div>
|
background: rgb(37, 97, 99);
|
||||||
|
color: rgb(215, 215, 215);
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(25, 47, 39)'"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(37, 96, 99)'"
|
||||||
|
onclick="handleGroupApproval('${txSig}')"
|
||||||
|
>
|
||||||
|
Approve Invite Tx
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === `GROUP_KICK`){
|
if (transactionType === "GROUP_KICK") {
|
||||||
|
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<h2 style="color:rgb(199, 100, 64);">Existing Kick Approvals: ${existingApprovalCount}</h2>
|
<p style="color: rgb(199, 100, 64);">
|
||||||
<button
|
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||||
style="padding: 8px; background:rgb(119, 91, 21); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(102, 69, 60); border-radius: 5px; cursor: pointer;"
|
</p>
|
||||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
${tableHtml}
|
||||||
onmouseout="this.style.backgroundColor='rgb(119, 91, 21) '"
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
onclick="handleGroupApproval('${txSig}')">
|
<button
|
||||||
Approve Kick Tx
|
style="
|
||||||
</button>
|
padding: 8px;
|
||||||
</div>
|
background: rgb(119, 91, 21);
|
||||||
|
color: rgb(201, 255, 251);
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-color: rgb(102, 69, 60);
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(119, 91, 21)'"
|
||||||
|
onclick="handleGroupApproval('${txSig}')"
|
||||||
|
>
|
||||||
|
Approve Kick Tx
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionType === `GROUP_BAN`){
|
if (transactionType === "GROUP_BAN") {
|
||||||
|
|
||||||
const approvalButtonHtml = `
|
const approvalButtonHtml = `
|
||||||
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
<h2 style="color:rgb(189, 40, 40);">Existing Ban Approvals: ${existingApprovalCount}</h2>
|
<p style="color: rgb(189, 40, 40);">
|
||||||
<button
|
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||||
style="padding: 8px; background:rgb(54, 7, 7); color:rgb(201, 255, 251) ; border: 1px solid #333; border-color:rgb(204, 94, 94); border-radius: 5px; cursor: pointer;"
|
</p>
|
||||||
onmouseover="this.style.backgroundColor='rgb(50, 52, 51) '"
|
${tableHtml}
|
||||||
onmouseout="this.style.backgroundColor='rgb(54, 7, 7) '"
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
onclick="handleGroupApproval('${txSig}')">
|
<button
|
||||||
Approve Ban Tx
|
style="
|
||||||
</button>
|
padding: 8px;
|
||||||
</div>
|
background: rgb(54, 7, 7);
|
||||||
|
color: rgb(201, 255, 251);
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-color: rgb(204, 94, 94);
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(54, 7, 7)'"
|
||||||
|
onclick="handleGroupApproval('${txSig}')"
|
||||||
|
>
|
||||||
|
Approve Ban Tx
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
return approvalButtonHtml
|
return approvalButtonHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transactionType === "ADD_GROUP_ADMIN") {
|
||||||
|
const approvalButtonHtml = `
|
||||||
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
|
<p style="color: rgb(40, 144, 189);">
|
||||||
|
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||||
|
</p>
|
||||||
|
${tableHtml}
|
||||||
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
background: rgb(8, 71, 69);
|
||||||
|
color: rgb(201, 255, 251);
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-color: rgb(198, 252, 249);
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(17, 41, 29)'"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(8, 71, 69)'"
|
||||||
|
onclick="handleGroupApproval('${txSig}')"
|
||||||
|
>
|
||||||
|
Approve Add-Admin Tx
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return approvalButtonHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionType === "REMOVE_GROUP_ADMIN") {
|
||||||
|
const approvalButtonHtml = `
|
||||||
|
<div style="display: flex; flex-direction: column; margin-top: 1em;">
|
||||||
|
<p style="color: rgb(189, 40, 40);">
|
||||||
|
Existing ${transactionType} Approvals: ${uniqueApprovalCount}
|
||||||
|
</p>
|
||||||
|
${tableHtml}
|
||||||
|
<div id="approval-button-container-${cardIdentifier}" style="margin-top: 1em;">
|
||||||
|
<button
|
||||||
|
style="
|
||||||
|
padding: 8px;
|
||||||
|
background: rgb(54, 7, 7);
|
||||||
|
color: rgb(201, 255, 251);
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-color: rgb(204, 94, 94);
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
onmouseover="this.style.backgroundColor='rgb(50, 52, 51)'"
|
||||||
|
onmouseout="this.style.backgroundColor='rgb(54, 7, 7)'"
|
||||||
|
onclick="handleGroupApproval('${txSig}')"
|
||||||
|
>
|
||||||
|
Approve Remove-Admin Tx
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return approvalButtonHtml
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildApprovalTableHtml(approvalTxs, getNameFunc) {
|
||||||
|
// Build a Map of adminAddress => one transaction (to handle multiple approvals from same admin)
|
||||||
|
const approvalMap = new Map()
|
||||||
|
for (const tx of approvalTxs) {
|
||||||
|
const adminAddr = tx.creatorAddress
|
||||||
|
if (!approvalMap.has(adminAddr)) {
|
||||||
|
approvalMap.set(adminAddr, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the map into an array for iteration
|
||||||
|
const approvalArray = Array.from(approvalMap, ([adminAddr, tx]) => ({ adminAddr, tx }))
|
||||||
|
|
||||||
|
// Build table rows asynchronously, since we need getNameFromAddress
|
||||||
|
const tableRows = await Promise.all(
|
||||||
|
approvalArray.map(async ({ adminAddr, tx }) => {
|
||||||
|
let adminName
|
||||||
|
try {
|
||||||
|
adminName = await getNameFunc(adminAddr)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Error fetching name for ${adminAddr}:`, err)
|
||||||
|
adminName = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName =
|
||||||
|
adminName && adminName !== adminAddr
|
||||||
|
? adminName
|
||||||
|
: "(No registered name)"
|
||||||
|
|
||||||
|
// Format the transaction timestamp
|
||||||
|
const dateStr = new Date(tx.timestamp).toLocaleString()
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid rgb(255, 255, 255); padding: 4px; color: #234565">${displayName}</td>
|
||||||
|
<td style="border: 1px solid rgb(255, 254, 254); padding: 4px;">${dateStr}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// The total unique approvals = number of entries in approvalMap
|
||||||
|
const uniqueApprovalCount = approvalMap.size;
|
||||||
|
|
||||||
|
// 4) Wrap the table in a container with horizontal scroll:
|
||||||
|
// 1) max-width: 100% makes it fit the parent (card) width
|
||||||
|
// 2) overflow-x: auto allows scrolling if the table is too wide
|
||||||
|
const containerHtml = `
|
||||||
|
<div style="max-width: 100%; overflow-x: auto;">
|
||||||
|
<table style="border: 1px solid #ccc; border-collapse: collapse; width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background:rgba(6, 50, 59, 0.61);">
|
||||||
|
<th style="border: 1px solid #ffffff; padding: 4px;">Admin Name</th>
|
||||||
|
<th style="border: 1px solid #ffffff; padding: 4px;">Approval Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${tableRows.join("")}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Return both the container-wrapped table and the count of unique approvals
|
||||||
|
return {
|
||||||
|
tableHtml: containerHtml,
|
||||||
|
uniqueApprovalCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleGroupApproval = async (pendingSignature) => {
|
const handleGroupApproval = async (pendingSignature) => {
|
||||||
try{
|
try{
|
||||||
if (!userState.isMinterAdmin) {
|
if (!userState.isMinterAdmin) {
|
||||||
|
@ -56,7 +56,7 @@ function loadMessagesFromLocalStorage() {
|
|||||||
console.log(`Loaded ${messageOrder.length} messages from localStorage.`)
|
console.log(`Loaded ${messageOrder.length} messages from localStorage.`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading messages from localStorage:", error);
|
console.error("Error loading messages from localStorage:", error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +98,23 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const addRemoveAdminLinks = document.querySelectorAll('a[href="ADDREMOVEADMIN"]')
|
||||||
|
addRemoveAdminLinks.forEach(link => {
|
||||||
|
link.addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
// Possibly require user to login if not logged
|
||||||
|
if (!userState.isLoggedIn) {
|
||||||
|
await login()
|
||||||
|
}
|
||||||
|
if (typeof loadMinterBoardPage === "undefined") {
|
||||||
|
console.log("loadMinterBoardPage not found, loading script dynamically...")
|
||||||
|
await loadScript("./assets/js/MinterBoard.js")
|
||||||
|
}
|
||||||
|
await loadAddRemoveAdminPage()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// --- ADMIN CHECK ---
|
// --- ADMIN CHECK ---
|
||||||
await verifyUserIsAdmin()
|
await verifyUserIsAdmin()
|
||||||
|
|
||||||
@ -116,7 +133,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userState.isAdmin) {
|
if (userState.isAdmin || userState.isForumAdmin || userState.isMinterAdmin) {
|
||||||
console.log(`User is an Admin. Admin-specific buttons will remain visible.`)
|
console.log(`User is an Admin. Admin-specific buttons will remain visible.`)
|
||||||
|
|
||||||
// DATA-BOARD Links for Admins
|
// DATA-BOARD Links for Admins
|
||||||
@ -233,7 +250,7 @@ const loadForumPage = async () => {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
document.body.appendChild(mainContent);
|
document.body.appendChild(mainContent)
|
||||||
|
|
||||||
// Add event listeners to room buttons
|
// Add event listeners to room buttons
|
||||||
document.getElementById("minters-room").addEventListener("click", () => {
|
document.getElementById("minters-room").addEventListener("click", () => {
|
||||||
|
@ -4,6 +4,7 @@ let adminGroupIDs = ["721", "1", "673"]
|
|||||||
// Settings to allow non-devmode development with 'live-server' module
|
// Settings to allow non-devmode development with 'live-server' module
|
||||||
let baseUrl = ''
|
let baseUrl = ''
|
||||||
let isOutsideOfUiDevelopment = false
|
let isOutsideOfUiDevelopment = false
|
||||||
|
let nullAddress = 'QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG'
|
||||||
|
|
||||||
if (typeof qortalRequest === 'function') {
|
if (typeof qortalRequest === 'function') {
|
||||||
console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.')
|
console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.')
|
||||||
@ -194,6 +195,13 @@ const userState = {
|
|||||||
isForumAdmin: false
|
isForumAdmin: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateQortalAddress = async (address) => {
|
||||||
|
// Regular expression to match Qortal addresses
|
||||||
|
const qortalAddressRegex = /^Q[a-zA-Z0-9]{32}$/
|
||||||
|
// Test the address against the regex
|
||||||
|
return qortalAddressRegex.test(address)
|
||||||
|
}
|
||||||
|
|
||||||
// USER-RELATED QORTAL CALLS ------------------------------------------
|
// USER-RELATED QORTAL CALLS ------------------------------------------
|
||||||
// Obtain the address of the authenticated user checking userState.accountAddress first.
|
// Obtain the address of the authenticated user checking userState.accountAddress first.
|
||||||
const getUserAddress = async () => {
|
const getUserAddress = async () => {
|
||||||
@ -1458,6 +1466,120 @@ const createGroupKickTransaction = async (recipientAddress, adminPublicKey, grou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createAddGroupAdminTransaction = async (ownerPublicKey, groupId=694, member, txGroupId, fee) => {
|
||||||
|
// If utilized to create a GROUP_APPROVAL tx, for MINTER group, then 'txCreatorPublicKey' takes the place of 'ownerPublicKey', and 'txGroupId' is required. Otherwise, txGroupId is 0 and ownerPublicKey is the tx creator, as creator = owner.
|
||||||
|
try {
|
||||||
|
|
||||||
|
let reference
|
||||||
|
|
||||||
|
if (!ownerPublicKey){
|
||||||
|
console.warn(`ownerPublicKey not passed, obtaining user public key...`)
|
||||||
|
const info = await getAddressInfo(userState.accountAddress)
|
||||||
|
reference = info.reference
|
||||||
|
ownerPublicKey = info.publicKey
|
||||||
|
}else {
|
||||||
|
// Fetch account reference correctly
|
||||||
|
const addr = await getAddressFromPublicKey(ownerPublicKey)
|
||||||
|
const accountInfo = await getAddressInfo(addr)
|
||||||
|
reference = accountInfo.reference
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inputs before making the request
|
||||||
|
if (!ownerPublicKey || !reference) {
|
||||||
|
throw new Error("Missing required parameters for group invite transaction.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reference,
|
||||||
|
fee,
|
||||||
|
txGroupId,
|
||||||
|
ownerPublicKey,
|
||||||
|
groupId,
|
||||||
|
member
|
||||||
|
}
|
||||||
|
console.log("Sending ADD_GROUP_ADMIN transaction payload:", payload)
|
||||||
|
const response = await fetch(`${baseUrl}/groups/addadmin`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Failed to create transaction: ${response.status}, ${errorText}`)
|
||||||
|
}
|
||||||
|
const rawTransaction = await response.text()
|
||||||
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
|
return rawTransaction
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating ADD_GROUP_ADMIN transaction:", error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createRemoveGroupAdminTransaction = async (ownerPublicKey, groupId=694, admin, txGroupId, fee) => {
|
||||||
|
console.log(`removeGroupAdminTxCreationInfo:`,ownerPublicKey, groupId, fee, txGroupId, admin)
|
||||||
|
|
||||||
|
try {
|
||||||
|
let reference
|
||||||
|
|
||||||
|
if (!ownerPublicKey){
|
||||||
|
console.warn(`ownerPublicKey not passed, obtaining user public key...`)
|
||||||
|
const info = getAddressInfo(userState.accountAddress)
|
||||||
|
reference = info.reference
|
||||||
|
ownerPublicKey = info.publicKey
|
||||||
|
} else {
|
||||||
|
// Fetch account reference correctly
|
||||||
|
const addr = await getAddressFromPublicKey(ownerPublicKey)
|
||||||
|
const accountInfo = await getAddressInfo(addr)
|
||||||
|
reference = accountInfo.reference
|
||||||
|
console.warn(`reference for removeTx:`, reference)
|
||||||
|
console.warn(`ownerPublicKey for removeTx`, ownerPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inputs before making the request
|
||||||
|
if (!ownerPublicKey || !reference) {
|
||||||
|
throw new Error("Missing required parameters for transaction.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
reference,
|
||||||
|
fee,
|
||||||
|
txGroupId,
|
||||||
|
ownerPublicKey,
|
||||||
|
groupId,
|
||||||
|
admin,
|
||||||
|
}
|
||||||
|
console.log("Sending REMOVE_GROUP_ADMINtransaction payload:", payload)
|
||||||
|
const response = await fetch(`${baseUrl}/groups/removeadmin`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
throw new Error(`Failed to create transaction: ${response.status}, ${errorText}`)
|
||||||
|
}
|
||||||
|
const rawTransaction = await response.text()
|
||||||
|
console.log("Raw unsigned transaction created:", rawTransaction)
|
||||||
|
return rawTransaction
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating REMOVE_GROUP_ADMIN transaction:", error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature, txGroupId=0, fee=0.01) => {
|
const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature, txGroupId=0, fee=0.01) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1468,7 +1590,7 @@ const createGroupApprovalTransaction = async (adminPublicKey, pendingSignature,
|
|||||||
|
|
||||||
// Validate inputs before making the request
|
// Validate inputs before making the request
|
||||||
if (!adminPublicKey || !accountReference ) {
|
if (!adminPublicKey || !accountReference ) {
|
||||||
throw new Error("Missing required parameters for group invite transaction.")
|
throw new Error("Missing required parameters for transaction.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@ -1771,33 +1893,3 @@ const searchPendingTransactions = async (limit = 20, offset = 0) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export {
|
|
||||||
// userState,
|
|
||||||
// adminGroups,
|
|
||||||
// searchResourcesWithMetadata,
|
|
||||||
// searchResourcesWithStatus,
|
|
||||||
// getResourceMetadata,
|
|
||||||
// renderData,
|
|
||||||
// getProductDetails,
|
|
||||||
// getUserGroups,
|
|
||||||
// getUserAddress,
|
|
||||||
// login,
|
|
||||||
// timestampToHumanReadableDate,
|
|
||||||
// base64EncodeString,
|
|
||||||
// verifyUserIsAdmin,
|
|
||||||
// fetchAllDataByIdentifier,
|
|
||||||
// fetchOwnerAddressFromName,
|
|
||||||
// verifyAddressIsAdmin,
|
|
||||||
// uid,
|
|
||||||
// fetchAllGroups,
|
|
||||||
// getNameInfo,
|
|
||||||
// publishMultipleResources,
|
|
||||||
// getPublicKeyByName,
|
|
||||||
// objectToBase64,
|
|
||||||
// fetchMinterGroupAdmins
|
|
||||||
// }
|
|
||||||
|
107
index.html
107
index.html
@ -1,37 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html >
|
<html >
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
|
||||||
<link rel="shortcut icon" href="assets/images/modded-circle-2-new-128x128.png" type="image/x-icon">
|
|
||||||
<meta name="description" content="Welcome to the Mintership Forum (alpha version)">
|
|
||||||
|
|
||||||
|
|
||||||
<title>Home</title>
|
|
||||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons2/mobirise2.css">
|
|
||||||
<link rel="stylesheet" href="assets/web/assets/mobirise-icons/mobirise-icons.css">
|
|
||||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-grid.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-reboot.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/parallax/jarallax.css">
|
|
||||||
<link rel="stylesheet" href="assets/animatecss/animate.css">
|
|
||||||
<link rel="stylesheet" href="assets/dropdown/css/style.css">
|
|
||||||
<link rel="stylesheet" href="assets/socicon/css/styles.css">
|
|
||||||
<link rel="stylesheet" href="assets/theme/css/style.css">
|
|
||||||
<link rel="preload" as="style" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ"><link rel="stylesheet" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ" type="text/css">
|
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<link rel="stylesheet" href="./assets/css/forum-styles.css">
|
|
||||||
<link rel="preload" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||||
<noscript><link rel="stylesheet" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap"></noscript>
|
<link rel="shortcut icon" href="assets/images/modded-circle-2-new-128x128.png" type="image/x-icon">
|
||||||
<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'">
|
<meta name="description" content="Welcome to the Mintership Forum (alpha version)">
|
||||||
<noscript><link rel="stylesheet" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap"></noscript>
|
|
||||||
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
|
||||||
</head>
|
<title>Home</title>
|
||||||
|
<link rel="stylesheet" href="assets/web/assets/mobirise-icons2/mobirise2.css">
|
||||||
|
<link rel="stylesheet" href="assets/web/assets/mobirise-icons/mobirise-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-grid.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap-reboot.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/parallax/jarallax.css">
|
||||||
|
<link rel="stylesheet" href="assets/animatecss/animate.css">
|
||||||
|
<link rel="stylesheet" href="assets/dropdown/css/style.css">
|
||||||
|
<link rel="stylesheet" href="assets/socicon/css/styles.css">
|
||||||
|
<link rel="stylesheet" href="assets/theme/css/style.css">
|
||||||
|
<link rel="preload" as="style" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ"><link rel="stylesheet" href="assets/mobirise/css/mbr-additional.css?v=U9lZDZ" type="text/css">
|
||||||
|
<link rel="stylesheet" href="./assets/css/forum-styles.css">
|
||||||
|
<link rel="preload" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
|
<noscript><link rel="stylesheet" href="./assets/css/css.css?family=DM+Sans:100,200,300,400,500,600,700,800,900,100i,200i,300i,400i,500i,600i,700i,800i,900i&display=swap"></noscript>
|
||||||
|
<link rel="preload" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
|
<noscript><link rel="stylesheet" href="./assets/css/space-grotesk.css?family=Space+Grotesk:300,400,500,600,700&display=swap"></noscript>
|
||||||
|
<link href="./assets/quill/quill.snow.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<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">
|
||||||
@ -45,7 +42,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-Alpha (v0.84b)
|
<a class="navbar-caption display-4" href="index.html">Q-Mintership (v1.0b)
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -64,12 +61,12 @@
|
|||||||
<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 Alpha v0.84b<br></a></span>
|
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership v1.0b<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>
|
||||||
|
|
||||||
|
|
||||||
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn admin-btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn admin-btn btn-secondary display-4" href="ADMINBOARD">ADMIN BOARD</a><a class="btn btn-danger display-4" href="MINTERS">MINTER BOARD</a></div>
|
<div class="mbr-section-btn-main" role="tablist"><a class="btn btn-danger display-4" href="MINTERSHIP-FORUM">FORUM<br></a> <a class="btn admin-btn btn-secondary display-4" href="TOOLS">ADMIN TOOLS</a><a class="btn admin-btn btn-secondary display-4" href="ADMINBOARD">ADMIN BOARD</a><a class="btn btn-danger display-4" href="MINTERS">MINTER BOARD</a><a class="btn btn-danger display-4" href="ADDREMOVEADMIN">ARA BOARD</a></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -104,6 +101,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6 col-md-6 item features-image active">
|
||||||
|
<a class="item-link" href="ADDREMOVEADMIN">
|
||||||
|
<div class="item-wrapper">
|
||||||
|
<img src="assets/images/mbr-1818x1212.jpg" alt="Admin Board" data-slide-to="1" data-bs-slide-to="1">
|
||||||
|
<div class="item-content">
|
||||||
|
<h2 class="card-title mbr-fonts-style display-2">
|
||||||
|
Promote / Demote Minter Admins</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div><div class="col-12 col-lg-6 col-md-6 item features-image">
|
||||||
|
<a class="item-link" href="MINTERSHIP-FORUM">
|
||||||
|
<div class="item-wrapper">
|
||||||
|
<img src="assets/images/mbr-1-1818x1212.jpg" alt="Mintership Forum" data-slide-to="0" data-bs-slide-to="0">
|
||||||
|
<div class="item-content">
|
||||||
|
<h2 class="card-title mbr-fonts-style display-2">Mintership Forum</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
@ -169,6 +191,23 @@
|
|||||||
|
|
||||||
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
<section data-bs-version="5.1" class="content7 boldm5 cid-uufIRKtXOO" id="content7-6">
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-7 card">
|
||||||
|
<div class="title-wrapper">
|
||||||
|
<h2 class="mbr-section-title mbr-fonts-style display-2">
|
||||||
|
v1.0beta 01-21-2025</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-5 card">
|
||||||
|
<div class="text-wrapper">
|
||||||
|
<p class="mbr-text mbr-fonts-style display-7">
|
||||||
|
<b><u>v1.0b information coming soon.</u></b>- <b>All Features related to new featureTriggers is completed and tested.</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-lg-7 card">
|
<div class="col-12 col-lg-7 card">
|
||||||
|
1434
project.mobirise
1434
project.mobirise
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user