Massive performance improvments my more accurately leveraging async, and searchSimple. Resolved issue of cards on AdminBoard not displaying properly. version 0.63beta.
This commit is contained in:
parent
cd033bc756
commit
f1d27a70f9
@ -173,9 +173,10 @@ const extractCardsMinterName = (cardIdentifier) => {
|
||||
if (parts.length < 3) {
|
||||
throw new Error('Invalid identifier format');
|
||||
}
|
||||
|
||||
if (parts.slice(2, -1).join('-') === 'TOPIC') {
|
||||
console.log(`TOPIC found in identifier: ${cardIdentifier} - not including in duplicatesList`)
|
||||
return
|
||||
return 'topic'
|
||||
}
|
||||
// Extract minterName (everything from the second part to the second-to-last part)
|
||||
const minterName = parts.slice(2, -1).join('-');
|
||||
@ -186,73 +187,39 @@ const extractCardsMinterName = (cardIdentifier) => {
|
||||
const processCards = async (validEncryptedCards) => {
|
||||
const latestCardsMap = new Map()
|
||||
|
||||
// Step 1: Filter and keep the most recent card per identifier
|
||||
validEncryptedCards.forEach(card => {
|
||||
// Step 1: Process all cards in parallel
|
||||
await Promise.all(validEncryptedCards.map(async card => {
|
||||
const timestamp = card.updated || card.created || 0
|
||||
const existingCard = latestCardsMap.get(card.identifier)
|
||||
|
||||
if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) {
|
||||
latestCardsMap.set(card.identifier, card)
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
console.log(`latestCardsMap, by timestamp`, latestCardsMap)
|
||||
|
||||
// Step 2: Extract unique cards
|
||||
const uniqueValidCards = Array.from(latestCardsMap.values())
|
||||
|
||||
// Step 3: Group by minterName and select the most recent card per minterName
|
||||
const minterNameMap = new Map()
|
||||
|
||||
for (const card of validEncryptedCards) {
|
||||
const minterName = await extractCardsMinterName(card.identifier)
|
||||
const existingCard = minterNameMap.get(minterName)
|
||||
const cardTimestamp = card.updated || card.created || 0
|
||||
const existingTimestamp = existingCard?.updated || existingCard?.created || 0
|
||||
|
||||
if (!existingCardMinterNames.includes(minterName)) {
|
||||
existingCardMinterNames.push(minterName)
|
||||
console.log(`cardsMinterName: ${minterName} - added to list`)
|
||||
}
|
||||
|
||||
// Keep only the most recent card for each minterName
|
||||
if (!existingCard || cardTimestamp > existingTimestamp) {
|
||||
minterNameMap.set(minterName, card)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Filter cards to ensure each minterName is included only once
|
||||
const finalCards = []
|
||||
const seenMinterNames = new Set()
|
||||
|
||||
for (const [minterName, card] of minterNameMap.entries()) {
|
||||
if (!seenMinterNames.has(minterName)) {
|
||||
finalCards.push(card)
|
||||
seenMinterNames.add(minterName) // Mark the minterName as seen
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Sort by the most recent timestamp
|
||||
finalCards.sort((a, b) => {
|
||||
const timestampA = a.updated || a.created || 0
|
||||
const timestampB = b.updated || b.created || 0
|
||||
return timestampB - timestampA
|
||||
})
|
||||
|
||||
return finalCards
|
||||
return uniqueValidCards
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Main function to load the Minter Cards ----------------------------------------
|
||||
const fetchAllEncryptedCards = async () => {
|
||||
const encryptedCardsContainer = document.getElementById("encrypted-cards-container");
|
||||
encryptedCardsContainer.innerHTML = "<p>Loading cards...</p>";
|
||||
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
service: "MAIL_PRIVATE",
|
||||
query: encryptedCardIdentifierPrefix,
|
||||
mode: "ALL"
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: "SEARCH_QDN_RESOURCES",
|
||||
// service: "MAIL_PRIVATE",
|
||||
// query: encryptedCardIdentifierPrefix,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0)
|
||||
|
||||
if (!response || !Array.isArray(response) || response.length === 0) {
|
||||
encryptedCardsContainer.innerHTML = "<p>No cards found.</p>";
|
||||
@ -262,19 +229,21 @@ const fetchAllEncryptedCards = async () => {
|
||||
// Validate cards and filter
|
||||
const validatedEncryptedCards = await Promise.all(
|
||||
response.map(async card => {
|
||||
const isValid = await validateEncryptedCardIdentifier(card);
|
||||
return isValid ? card : null;
|
||||
const isValid = await validateEncryptedCardIdentifier(card)
|
||||
return isValid ? card : null
|
||||
})
|
||||
);
|
||||
)
|
||||
console.log(`validatedEncryptedCards:`, validatedEncryptedCards, `... running next filter...`)
|
||||
|
||||
const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null);
|
||||
console.log(`validEncryptedcards:`, validEncryptedCards)
|
||||
|
||||
if (validEncryptedCards.length === 0) {
|
||||
encryptedCardsContainer.innerHTML = "<p>No valid cards found.</p>";
|
||||
return;
|
||||
}
|
||||
const finalCards = await processCards(validEncryptedCards)
|
||||
|
||||
console.log(`finalCards:`,finalCards)
|
||||
// Display skeleton cards immediately
|
||||
encryptedCardsContainer.innerHTML = "";
|
||||
finalCards.forEach(card => {
|
||||
@ -371,14 +340,16 @@ const createEncryptedSkeletonCardHTML = (cardIdentifier) => {
|
||||
// Function to check and fech an existing Minter Card if attempting to publish twice ----------------------------------------
|
||||
const fetchExistingEncryptedCard = async (minterName) => {
|
||||
try {
|
||||
// Step 1: Perform the search
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
service: "MAIL_PRIVATE",
|
||||
identifier: encryptedCardIdentifierPrefix,
|
||||
query: minterName,
|
||||
mode: "ALL",
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: "SEARCH_QDN_RESOURCES",
|
||||
// service: "MAIL_PRIVATE",
|
||||
// identifier: encryptedCardIdentifierPrefix,
|
||||
// query: minterName,
|
||||
// mode: "ALL",
|
||||
// })
|
||||
|
||||
//CHANGED to searchSimple to speed up search results.
|
||||
const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0)
|
||||
|
||||
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
|
||||
|
||||
@ -598,17 +569,18 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
|
||||
|
||||
const getEncryptedCommentCount = async (cardIdentifier) => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: 'SEARCH_QDN_RESOURCES',
|
||||
service: 'MAIL_PRIVATE',
|
||||
query: `comment-${cardIdentifier}`,
|
||||
mode: "ALL"
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: 'SEARCH_QDN_RESOURCES',
|
||||
// service: 'MAIL_PRIVATE',
|
||||
// query: `comment-${cardIdentifier}`,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
||||
// Just return the count; no need to decrypt each comment here
|
||||
return Array.isArray(response) ? response.length : 0;
|
||||
return Array.isArray(response) ? response.length : 0
|
||||
} catch (error) {
|
||||
console.error(`Error fetching comment count for ${cardIdentifier}:`, error);
|
||||
return 0;
|
||||
console.error(`Error fetching comment count for ${cardIdentifier}:`, error)
|
||||
return 0
|
||||
}
|
||||
};
|
||||
|
||||
@ -666,14 +638,17 @@ const postEncryptedComment = async (cardIdentifier) => {
|
||||
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||
const fetchEncryptedComments = async (cardIdentifier) => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: 'SEARCH_QDN_RESOURCES',
|
||||
service: 'MAIL_PRIVATE',
|
||||
query: `comment-${cardIdentifier}`,
|
||||
mode: "ALL"
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: 'SEARCH_QDN_RESOURCES',
|
||||
// service: 'MAIL_PRIVATE',
|
||||
// query: `comment-${cardIdentifier}`,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0)
|
||||
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching comments for ${cardIdentifier}:`, error);
|
||||
return [];
|
||||
@ -683,7 +658,9 @@ const fetchEncryptedComments = async (cardIdentifier) => {
|
||||
// display the comments on the card, with passed cardIdentifier to identify the card --------------
|
||||
const displayEncryptedComments = async (cardIdentifier) => {
|
||||
try {
|
||||
|
||||
const comments = await fetchEncryptedComments(cardIdentifier);
|
||||
|
||||
const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`);
|
||||
|
||||
// Fetch and display each comment
|
||||
@ -712,8 +689,8 @@ const displayEncryptedComments = async (cardIdentifier) => {
|
||||
commentsContainer.insertAdjacentHTML('beforeend', commentHTML);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error displaying comments for ${cardIdentifier}:`, error);
|
||||
alert("Failed to load comments. Please try again.");
|
||||
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -757,7 +734,7 @@ const calculateAdminBoardPollResults = async (pollData, minterGroupMembers, mint
|
||||
// 4) Process votes
|
||||
for (const vote of pollData.votes) {
|
||||
const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey);
|
||||
console.log(`voter address: ${voterAddress}, optionIndex: ${vote.optionIndex}`);
|
||||
// console.log(`voter address: ${voterAddress}, optionIndex: ${vote.optionIndex}`);
|
||||
|
||||
if (vote.optionIndex === 0) {
|
||||
if (adminAddresses.includes(voterAddress)) {
|
||||
|
@ -120,17 +120,17 @@ const loadMinterBoardPage = async () => {
|
||||
const extractMinterCardsMinterName = async (cardIdentifier) => {
|
||||
// Ensure the identifier starts with the prefix
|
||||
if (!cardIdentifier.startsWith(`${cardIdentifierPrefix}-`)) {
|
||||
throw new Error('Invalid identifier format or prefix mismatch');
|
||||
throw new Error('Invalid identifier format or prefix mismatch')
|
||||
}
|
||||
// Split the identifier into parts
|
||||
const parts = cardIdentifier.split('-');
|
||||
const parts = cardIdentifier.split('-')
|
||||
// Ensure the format has at least 3 parts
|
||||
if (parts.length < 3) {
|
||||
throw new Error('Invalid identifier format');
|
||||
throw new Error('Invalid identifier format')
|
||||
}
|
||||
try {
|
||||
const nameFromIdentifier = await searchSimple('BLOG_POST', cardIdentifier, "", 1)
|
||||
const minterName = await nameFromIdentifier.name
|
||||
const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1)
|
||||
const minterName = await searchSimpleResults.name
|
||||
return minterName
|
||||
} catch (error) {
|
||||
throw error
|
||||
@ -195,12 +195,14 @@ const loadCards = async () => {
|
||||
cardsContainer.innerHTML = "<p>Loading cards...</p>";
|
||||
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
service: "BLOG_POST",
|
||||
query: cardIdentifierPrefix,
|
||||
mode: "ALL"
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: "SEARCH_QDN_RESOURCES",
|
||||
// service: "BLOG_POST",
|
||||
// query: cardIdentifierPrefix,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
|
||||
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, '' , 0)
|
||||
|
||||
if (!response || !Array.isArray(response) || response.length === 0) {
|
||||
cardsContainer.innerHTML = "<p>No cards found.</p>";
|
||||
@ -320,14 +322,16 @@ const createSkeletonCardHTML = (cardIdentifier) => {
|
||||
const fetchExistingCard = async () => {
|
||||
try {
|
||||
// Step 1: Perform the search
|
||||
const response = await qortalRequest({
|
||||
action: "SEARCH_QDN_RESOURCES",
|
||||
service: "BLOG_POST",
|
||||
identifier: cardIdentifierPrefix,
|
||||
name: userState.accountName,
|
||||
mode: "ALL",
|
||||
exactMatchNames: true // Search for the exact userName only when finding existing cards
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: "SEARCH_QDN_RESOURCES",
|
||||
// service: "BLOG_POST",
|
||||
// identifier: cardIdentifierPrefix,
|
||||
// name: userState.accountName,
|
||||
// mode: "ALL",
|
||||
// exactMatchNames: true // Search for the exact userName only when finding existing cards
|
||||
// })
|
||||
// Changed to searchSimple to improve load times.
|
||||
const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0)
|
||||
|
||||
console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`);
|
||||
|
||||
@ -335,9 +339,11 @@ const fetchExistingCard = async () => {
|
||||
if (!response || !Array.isArray(response) || response.length === 0) {
|
||||
console.log("No cards found for the current user.");
|
||||
return null;
|
||||
} else if (response.length === 1) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it.
|
||||
return response[0]
|
||||
}
|
||||
|
||||
// Step 3: Validate cards asynchronously
|
||||
// Validate cards asynchronously, check that they are not comments, etc.
|
||||
const validatedCards = await Promise.all(
|
||||
response.map(async card => {
|
||||
const isValid = await validateCardStructure(card);
|
||||
@ -345,14 +351,14 @@ const fetchExistingCard = async () => {
|
||||
})
|
||||
);
|
||||
|
||||
// Step 4: Filter out invalid cards
|
||||
// Filter out invalid cards
|
||||
const validCards = validatedCards.filter(card => card !== null);
|
||||
|
||||
if (validCards.length > 0) {
|
||||
// Step 5: Sort by most recent timestamp
|
||||
// Sort by most recent timestamp
|
||||
const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0];
|
||||
|
||||
// Step 6: Fetch full card data
|
||||
// Fetch full card data
|
||||
const cardDataResponse = await qortalRequest({
|
||||
action: "FETCH_QDN_RESOURCE",
|
||||
name: userState.accountName, // User's account name
|
||||
@ -490,7 +496,7 @@ const calculatePollResults = async (pollData, minterGroupMembers, minterAdmins)
|
||||
|
||||
for (const vote of pollData.votes) {
|
||||
const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey)
|
||||
console.log(`voter address: ${voterAddress}`)
|
||||
// console.log(`voter address: ${voterAddress}`)
|
||||
|
||||
if (vote.optionIndex === 0) {
|
||||
adminAddresses.includes(voterAddress) ? adminYes++ : memberAddresses.includes(voterAddress) ? minterYes++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`)
|
||||
@ -555,18 +561,19 @@ const postComment = async (cardIdentifier) => {
|
||||
//Fetch the comments for a card with passed card identifier ----------------------------
|
||||
const fetchCommentsForCard = async (cardIdentifier) => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: 'SEARCH_QDN_RESOURCES',
|
||||
service: 'BLOG_POST',
|
||||
query: `comment-${cardIdentifier}`,
|
||||
mode: "ALL"
|
||||
});
|
||||
return response;
|
||||
// const response = await qortalRequest({
|
||||
// action: 'SEARCH_QDN_RESOURCES',
|
||||
// service: 'BLOG_POST',
|
||||
// query: `comment-${cardIdentifier}`,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
const response = await searchSimple('BLOG_POST',`comment-${cardIdentifier}`, '', 0)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error(`Error fetching comments for ${cardIdentifier}:`, error);
|
||||
return [];
|
||||
console.error(`Error fetching comments for ${cardIdentifier}:`, error)
|
||||
return []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// display the comments on the card, with passed cardIdentifier to identify the card --------------
|
||||
const displayComments = async (cardIdentifier) => {
|
||||
@ -594,8 +601,7 @@ const displayComments = async (cardIdentifier) => {
|
||||
commentsContainer.insertAdjacentHTML('beforeend', commentHTML);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error displaying comments for ${cardIdentifier}:`, error);
|
||||
alert("Failed to load comments. Please try again.");
|
||||
console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -624,12 +630,14 @@ const toggleComments = async (cardIdentifier) => {
|
||||
|
||||
const countComments = async (cardIdentifier) => {
|
||||
try {
|
||||
const response = await qortalRequest({
|
||||
action: 'SEARCH_QDN_RESOURCES',
|
||||
service: 'BLOG_POST',
|
||||
query: `comment-${cardIdentifier}`,
|
||||
mode: "ALL"
|
||||
});
|
||||
// const response = await qortalRequest({
|
||||
// action: 'SEARCH_QDN_RESOURCES',
|
||||
// service: 'BLOG_POST',
|
||||
// query: `comment-${cardIdentifier}`,
|
||||
// mode: "ALL"
|
||||
// })
|
||||
// Changed to searchSimple to hopefully improve load times...
|
||||
const response = await searchSimple('BLOG_POST', `comment-${cardIdentifier}`, '', 0)
|
||||
// Just return the count; no need to decrypt each comment here
|
||||
return Array.isArray(response) ? response.length : 0;
|
||||
} catch (error) {
|
||||
|
@ -649,8 +649,8 @@ const generateAttachmentID = (room, fileIndex = null) => {
|
||||
|
||||
const findMessagePage = async (room, identifier, limit) => {
|
||||
const { service, query } = getServiceAndQuery(room)
|
||||
|
||||
const allMessages = await searchAllWithOffset(service, query, 0, 0, room)
|
||||
//TODO check that searchSimple change worked.
|
||||
const allMessages = await searchSimple(service, query, '', 0, 0, room, 'false')
|
||||
|
||||
const idx = allMessages.findIndex(msg => msg.identifier === identifier);
|
||||
if (idx === -1) {
|
||||
@ -746,7 +746,8 @@ const getServiceAndQuery = (room) => {
|
||||
};
|
||||
|
||||
const fetchResourceList = async (service, query, limit, offset, room) => {
|
||||
return await searchAllWithOffset(service, query, limit, offset, room);
|
||||
//TODO check
|
||||
return await searchSimple(service, query, '', limit, offset, room, 'false');
|
||||
};
|
||||
|
||||
const handleNoMessagesScenario = (isPolling, page, response, messagesContainer) => {
|
||||
|
File diff suppressed because it is too large
Load Diff
38
index.html
38
index.html
@ -68,7 +68,7 @@
|
||||
<img src="assets/images/again-edited-qortal-minting-icon-156x156.png" alt="">
|
||||
</a>
|
||||
</span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.61b<br></a></span>
|
||||
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.63b<br></a></span>
|
||||
</div>
|
||||
<ul class="navbar-nav nav-dropdown" data-app-modern-menu="true"><li class="nav-item"><a class="nav-link link text-primary display-7" href="MINTERSHIP-FORUM"></a></li></ul>
|
||||
|
||||
@ -197,6 +197,40 @@
|
||||
|
||||
<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">
|
||||
Issues from QDN 12-27-2024</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 card">
|
||||
<div class="text-wrapper">
|
||||
<p class="mbr-text mbr-fonts-style display-7">
|
||||
MASSIVE performance improvements today. Increased performance of both the Forum and Minter Board by a HUGE margin today by leveraging async better, and utilizing searchSimple for all search calls. This makes the forum and boards load 100x faster. Also, may have determined the cause of the QDN bug... we will research further, but as of now, I am definitely getting better results withOUT following names. Will update as time goes on. Also... PLEASE NOTE - POLLS ARE NOT TO BE UTILIZED THROUGH QOMBO OR ANY OTHER APP. Polls in the Minter and Admin Boards are TIED to the cards that published them, and the results are FILTERED to display only the results of MINTERS and ADMINS. Therefore, utilizing outside tools to read (or create) polls is not only an exercise in futility, but also will provide no useful information whatsoever. Please realize that the poll data is utilized to show direct support of the cards by minters and admins, and pols are NOT MEANT TO BE CREATED OR VIEWED OUTIDE OF Q-MINTERSHIP. Thank you. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Issues from QDN 12-27-2024</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-5 card">
|
||||
<div class="text-wrapper">
|
||||
<p class="mbr-text mbr-fonts-style display-7">
|
||||
it seems there were some issues caused by what can only be described as the QDN bug. I will be checking into this in more detail in the near future, but it seems like the previous publish did not get the update as it was supposed to out to many people, and as such many of the changes were not there in the code that was on each node. Due to this, many issues were happening. Including the fact that the identifier change didn't take place, or at least that is what seems to have happened. Will verify this shortly. Until then will publish another update now that should resolve some lingering issues. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7 card">
|
||||
@ -315,7 +349,7 @@
|
||||
</div>
|
||||
|
||||
<a class="link-wrap" href="#">
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.61beta</p>
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.63beta</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
|
Loading…
Reference in New Issue
Block a user