MASSIVE re-factor on both the forum AND Admin Board... MANY changes made, too many to list. Multiple bugfixes, completely modified code structure, etc. Details will be added at a later date. Until then, see code. ;)

This commit is contained in:
crowetic 2024-12-19 21:28:36 -08:00
parent 25c0afb237
commit 759f000a00
8 changed files with 418 additions and 328 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1011,7 +1011,7 @@ body {
.comments-container {
margin-bottom: 10px;
max-height: 150px;
max-height: 300px;
overflow-y: auto;
}

View File

@ -1,9 +1,9 @@
// 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 = true;
const encryptedCardIdentifierPrefix = "test-MDC";
let isExistingEncryptedCard = false;
let existingDecryptedCardData = {};
let existingEncryptedCardIdentifier = {};
const isEncryptedTestMode = true
const encryptedCardIdentifierPrefix = "test-MDC"
let isExistingEncryptedCard = false
let existingDecryptedCardData = {}
let existingEncryptedCardIdentifier = {}
let cardMinterName = {}
let existingCardMinterNames = []
@ -93,7 +93,7 @@ const loadAdminBoardPage = async () => {
await publishEncryptedCard();
});
await createCardMinterNameList();
// await fetchAndValidateAllAdminCards();
await fetchAllEncryptedCards();
}
@ -102,55 +102,75 @@ const extractCardsMinterName = (cardIdentifier) => {
if (!cardIdentifier.startsWith(`${encryptedCardIdentifierPrefix}-`)) {
throw new Error('Invalid identifier format or prefix mismatch');
}
// Split the identifier into parts
const parts = cardIdentifier.split('-');
// Ensure the format has at least 3 parts
if (parts.length < 3) {
throw new Error('Invalid identifier format');
}
// Extract minterName (everything from the second part to the second-to-last part)
const minterName = parts.slice(2, -1).join('-');
// Return the extracted minterName
return minterName;
}
const processCards = async (validEncryptedCards) => {
const latestCardsMap = new Map()
const createCardMinterNameList = async () => {
// Step 1: Filter and keep the most recent card per identifier
validEncryptedCards.forEach(card => {
const timestamp = card.updated || card.created || 0
const existingCard = latestCardsMap.get(card.identifier)
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "MAIL_PRIVATE",
query: `${encryptedCardIdentifierPrefix}`,
mode: "ALL",
});
const validatedEncryptedCards = await Promise.all(
response.map(async card => {
const isValid = await validateEncryptedCardIdentifier(card);
return isValid ? card : null;
})
)
const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null);
if (validEncryptedCards.length === 0) {
console.log(`no matches found, not adding any names to name list.`)
return;
if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) {
latestCardsMap.set(card.identifier, card)
}
})
for (const result of validEncryptedCards) {
const minterName = await extractCardsMinterName(result.identifier)
// 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
}
//Main function to load the Minter Cards ----------------------------------------
const fetchAllEncryptedCards = async () => {
@ -184,35 +204,17 @@ const fetchAllEncryptedCards = async () => {
encryptedCardsContainer.innerHTML = "<p>No valid cards found.</p>";
return;
}
// Group by identifier and keep only the newest card for each identifier
const latestCardsMap = new Map();
validEncryptedCards.forEach(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);
}
});
// Extract unique cards and sort by timestamp descending
const uniqueValidCards = Array.from(latestCardsMap.values()).sort((a, b) => {
const timestampA = a.updated || a.created || 0;
const timestampB = b.updated || b.created || 0;
return timestampB - timestampA;
});
const finalCards = await processCards(validEncryptedCards)
// Display skeleton cards immediately
encryptedCardsContainer.innerHTML = "";
uniqueValidCards.forEach(card => {
finalCards.forEach(card => {
const skeletonHTML = createSkeletonCardHTML(card.identifier);
encryptedCardsContainer.insertAdjacentHTML("beforeend", skeletonHTML);
});
// Fetch and update each card
uniqueValidCards.forEach(async card => {
finalCards.forEach(async card => {
try {
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
@ -240,9 +242,9 @@ const fetchAllEncryptedCards = async () => {
// Fetch poll results
const pollResults = await fetchPollResults(decryptedCardData.poll);
const minterNameFromIdentifier = await extractCardsMinterName(card.identifier);
const commentCount = await getCommentCount(card.identifier);
// Generate final card HTML
const finalCardHTML = await createEncryptedCardHTML(decryptedCardData, pollResults, card.identifier);
const finalCardHTML = await createEncryptedCardHTML(decryptedCardData, pollResults, card.identifier, commentCount);
replaceEncryptedSkeleton(card.identifier, finalCardHTML);
} catch (error) {
console.error(`Error processing card ${card.identifier}:`, error);
@ -489,6 +491,22 @@ const publishEncryptedCard = async () => {
}
}
const getCommentCount = async (cardIdentifier) => {
try {
const response = await qortalRequest({
action: 'SEARCH_QDN_RESOURCES',
service: 'MAIL_PRIVATE',
query: `comment-${cardIdentifier}`,
mode: "ALL"
});
// Just return the count; no need to decrypt each comment here
return Array.isArray(response) ? response.length : 0;
} catch (error) {
console.error(`Error fetching comment count for ${cardIdentifier}:`, error);
return 0;
}
};
// Post a comment on a card. ---------------------------------
const postEncryptedComment = async (cardIdentifier) => {
const commentInput = document.getElementById(`new-comment-${cardIdentifier}`);
@ -498,7 +516,7 @@ const postEncryptedComment = async (cardIdentifier) => {
return;
}
const postTimestamp = `${Date.now()}`
const postTimestamp = Date.now()
console.log(`timestmp to be posted: ${postTimestamp}`)
const commentData = {
@ -575,7 +593,8 @@ const displayEncryptedComments = async (cardIdentifier) => {
const decryptedCommentData = await decryptAndParseObject(commentDataResponse)
const timestamp = await timestampToHumanReadableDate(decryptedCommentData.timestamp);
const timestampCheck = comment.updated || comment.created || 0
const timestamp = await timestampToHumanReadableDate(timestampCheck);
//TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section.
const commentHTML = `
@ -688,7 +707,7 @@ const processQortalLinkForRendering = async (link) => {
}
// Create the overall Minter Card HTML -----------------------------------------------
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier) => {
const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, commentCount) => {
const { minterName, header, content, links, creator, timestamp, poll } = cardData;
const formattedDate = new Date(timestamp).toLocaleString();
const minterAvatar = `/arbitrary/THUMBNAIL/${minterName}/qortal_avatar`;
@ -740,7 +759,7 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier) =>
<div class="actions">
<div class="actions-buttons">
<button class="yes" onclick="voteYesOnPoll('${poll}')">YES</button>
<button class="comment" onclick="toggleEncryptedComments('${cardIdentifier}')">COMMENTS</button>
<button class="comment" onclick="toggleEncryptedComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div>
</div>

View File

@ -554,63 +554,118 @@ const generateAttachmentID = (room, fileIndex = null) => {
return fileIndex !== null ? `${baseID}-${fileIndex}` : baseID;
};
const decryptFile = async (encryptedData) => {
const publicKey = await getPublicKeyByName(userState.accountName)
const response = await qortalRequest({
action: 'DECRYPT_DATA',
encryptedData, // has to be in base64 format
// publicKey: publicKey // requires the public key of the opposite user with whom you've created the encrypted data.
});
const decryptedObject = response
return decryptedObject
}
// const decryptFile = async (encryptedData) => {
// const publicKey = await getPublicKeyByName(userState.accountName)
// const response = await qortalRequest({
// action: 'DECRYPT_DATA',
// encryptedData, // has to be in base64 format
// // publicKey: publicKey // requires the public key of the opposite user with whom you've created the encrypted data.
// });
// const decryptedObject = response
// return decryptedObject
// }
// --- REFACTORED LOAD MESSAGES AND HELPER FUNCTIONS ---
const loadMessagesFromQDN = async (room, page, isPolling = false) => {
try {
const limit = 10;
const offset = page * limit;
console.log(`Loading messages for room: ${room}, page: ${page}, offset: ${offset}, limit: ${limit}`);
console.log(`Loading messages from QDN: room=${room}, page=${page}, offset=${offset}, limit=${limit}`);
// Get the messages container
const messagesContainer = document.querySelector("#messages-container");
if (!messagesContainer) return;
// If not polling, clear the message container and the existing identifiers for a fresh load
if (!isPolling) {
messagesContainer.innerHTML = ""; // Clear the messages container before loading new page
existingIdentifiers.clear(); // Clear the existing identifiers set for fresh page load
}
prepareMessageContainer(messagesContainer, isPolling);
// Get the set of existing identifiers from the messages container
existingIdentifiers = new Set(Array.from(messagesContainer.querySelectorAll('.message-item')).map(item => item.dataset.identifier));
const { service, query } = getServiceAndQuery(room);
const response = await fetchResourceList(service, query, limit, offset, room);
// Fetch messages for the current room and page
const service = room === "admins" ? "MAIL_PRIVATE" : "BLOG_POST"
const query = room === "admins" ? `${messageIdentifierPrefix}-${room}-e` : `${messageIdentifierPrefix}-${room}`
console.log(`Fetched ${response.length} message(s) for page ${page}.`);
const response = await searchAllWithOffset(service, query, limit, offset, room);
console.log(`Fetched messages count: ${response.length} for page: ${page}`);
if (response.length === 0) {
// If no messages are fetched and it's not polling, display "no messages" for the initial load
if (page === 0 && !isPolling) {
messagesContainer.innerHTML = `<p>No messages found. Be the first to post!</p>`;
}
if (handleNoMessagesScenario(isPolling, page, response, messagesContainer)) {
return;
}
// Define `mostRecentMessage` to track the latest message during this fetch
let mostRecentMessage = latestMessageIdentifiers[room]?.latestTimestamp ? latestMessageIdentifiers[room] : null;
let firstNewMessageIdentifier = null
// Re-establish existing identifiers after preparing container
existingIdentifiers = new Set(
Array.from(messagesContainer.querySelectorAll('.message-item'))
.map(item => item.dataset.identifier)
);
// Fetch all messages that haven't been fetched before
const fetchMessages = await Promise.all(response.map(async (resource) => {
if (existingIdentifiers.has(resource.identifier)) {
return null; // Skip messages that are already displayed
let mostRecentMessage = getCurrentMostRecentMessage(room);
const fetchMessages = await fetchAllMessages(response, service, room);
const { firstNewMessageIdentifier, updatedMostRecentMessage } = renderNewMessages(
fetchMessages,
existingIdentifiers,
messagesContainer,
room,
mostRecentMessage
);
if (firstNewMessageIdentifier && !isPolling) {
scrollToNewMessages(firstNewMessageIdentifier);
}
if (updatedMostRecentMessage) {
updateLatestMessageIdentifiers(room, updatedMostRecentMessage);
}
handleReplyLogic(fetchMessages);
await updatePaginationControls(room, limit);
} catch (error) {
console.error('Error loading messages from QDN:', error);
}
};
/** Helper Functions (Arrow Functions) **/
const prepareMessageContainer = (messagesContainer, isPolling) => {
if (!isPolling) {
messagesContainer.innerHTML = "";
existingIdentifiers.clear();
}
};
const getServiceAndQuery = (room) => {
const service = (room === "admins") ? "MAIL_PRIVATE" : "BLOG_POST";
const query = (room === "admins")
? `${messageIdentifierPrefix}-${room}-e`
: `${messageIdentifierPrefix}-${room}`;
return { service, query };
};
const fetchResourceList = async (service, query, limit, offset, room) => {
return await searchAllWithOffset(service, query, limit, offset, room);
};
const handleNoMessagesScenario = (isPolling, page, response, messagesContainer) => {
if (response.length === 0) {
if (page === 0 && !isPolling) {
messagesContainer.innerHTML = `<p>No messages found. Be the first to post!</p>`;
}
return true;
}
return false;
};
const getCurrentMostRecentMessage = (room) => {
return latestMessageIdentifiers[room]?.latestTimestamp ? latestMessageIdentifiers[room] : null;
};
const fetchAllMessages = async (response, service, room) => {
return Promise.all(response.map(resource => fetchFullMessage(resource, service, room)));
};
const fetchFullMessage = async (resource, service, room) => {
try {
// Skip if already displayed
if (existingIdentifiers.has(resource.identifier)) {
return null;
}
console.log(`Fetching message with identifier: ${resource.identifier}`);
const messageResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
@ -620,32 +675,9 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
...(room === "admins" ? { encoding: "base64" } : {}),
});
console.log("Fetched message response:", messageResponse);
const timestamp = resource.updated || resource.created;
const formattedTimestamp = await timestampToHumanReadableDate(timestamp);
let messageObject;
if (room === "admins") {
try {
const decryptedData = await decryptObject(messageResponse);
messageObject = JSON.parse(atob(decryptedData))
} catch (error) {
console.error(`Failed to decrypt message: ${error.message}`);
return {
name: resource.name,
content: "<em>Encrypted message cannot be displayed</em>",
date: formattedTimestamp,
identifier: resource.identifier,
replyTo: null,
timestamp,
attachments: [],
};
}
} else {
messageObject = messageResponse;
}
const messageObject = await processMessageObject(messageResponse, room);
return {
name: resource.name,
@ -657,7 +689,7 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
attachments: messageObject?.attachments || [],
};
} catch (error) {
console.error(`Failed to fetch message with identifier ${resource.identifier}. Error: ${error.message}`);
console.error(`Failed to fetch message ${resource.identifier}: ${error.message}`);
return {
name: resource.name,
content: "<em>Error loading message</em>",
@ -668,67 +700,60 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
attachments: [],
};
}
})
);
};
const processMessageObject = async (messageResponse, room) => {
if (room !== "admins") {
return messageResponse;
}
try {
const decryptedData = await decryptAndParseObject(messageResponse);
return decryptedData
} catch (error) {
console.error(`Failed to decrypt admin message: ${error.message}`);
return null;
}
};
const renderNewMessages = (fetchMessages, existingIdentifiers, messagesContainer, room, mostRecentMessage) => {
let firstNewMessageIdentifier = null;
let updatedMostRecentMessage = mostRecentMessage;
// Render new messages without duplication
for (const message of fetchMessages) {
if (message && !existingIdentifiers.has(message.identifier)) {
const isNewMessage = !mostRecentMessage || new Date(message.timestamp) > new Date(mostRecentMessage?.latestTimestamp);
const isNewMessage = isMessageNew(message, mostRecentMessage);
if (isNewMessage && !firstNewMessageIdentifier) {
firstNewMessageIdentifier = message.identifier;
}
let replyHtml = "";
if (message.replyTo) {
const repliedMessage = fetchMessages.find(m => m && m.identifier === message.replyTo);
if (repliedMessage) {
replyHtml = `
<div class="reply-message" style="border-left: 2px solid #ccc; margin-bottom: 0.5vh; padding-left: 1vh;">
<div class="reply-header">In reply to: <span class="reply-username">${repliedMessage.name}</span> <span class="reply-timestamp">${repliedMessage.date}</span></div>
<div class="reply-content">${repliedMessage.content}</div>
</div>
`;
}
}
let attachmentHtml = "";
if (message.attachments && message.attachments.length > 0) {
for (const attachment of message.attachments) {
if (room !== "admins" && attachment.mimeType && attachment.mimeType.startsWith('image/')) {
try {
// Construct the image URL
const imageUrl = `/arbitrary/${attachment.service}/${attachment.name}/${attachment.identifier}`;
const messageHTML = buildMessageHTML(message, fetchMessages, room, isNewMessage);
messagesContainer.insertAdjacentHTML('beforeend', messageHTML);
// Add the image HTML with the direct URL
attachmentHtml += `<div class="attachment">
<img src="${imageUrl}" alt="${attachment.filename}" class="inline-image"/>
</div>`;
// Set up the modal download button
const downloadButton = document.getElementById("download-button");
downloadButton.onclick = () => {
fetchAndSaveAttachment(
attachment.service,
attachment.name,
attachment.identifier,
attachment.filename,
attachment.mimeType
);
if (!updatedMostRecentMessage || new Date(message.timestamp) > new Date(updatedMostRecentMessage?.latestTimestamp || 0)) {
updatedMostRecentMessage = {
latestIdentifier: message.identifier,
latestTimestamp: message.timestamp,
};
} catch (error) {
console.error(`Failed to fetch attachment ${attachment.filename}:`, error);
}
} else {
// Display a button to download non-image attachments
attachmentHtml += `<div class="attachment">
<button onclick="fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')">Download ${attachment.filename}</button>
</div>`;
}
existingIdentifiers.add(message.identifier);
}
}
return { firstNewMessageIdentifier, updatedMostRecentMessage };
};
const isMessageNew = (message, mostRecentMessage) => {
return !mostRecentMessage || new Date(message.timestamp) > new Date(mostRecentMessage?.latestTimestamp);
};
const buildMessageHTML = (message, fetchMessages, room, isNewMessage) => {
const replyHtml = buildReplyHtml(message, fetchMessages);
const attachmentHtml = buildAttachmentHtml(message, room);
const avatarUrl = `/arbitrary/THUMBNAIL/${message.name}/qortal_avatar`;
const messageHTML = `
return `
<div class="message-item" data-identifier="${message.identifier}">
<div class="message-header" style="display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center;">
@ -746,46 +771,76 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
<button class="reply-button" data-message-identifier="${message.identifier}">Reply</button>
</div>
`;
};
// Append new message to the end of the container
messagesContainer.insertAdjacentHTML('beforeend', messageHTML);
const buildReplyHtml = (message, fetchMessages) => {
if (!message.replyTo) return "";
// Update mostRecentMessage if this message is newer
if (!mostRecentMessage || new Date(message.timestamp) > new Date(mostRecentMessage?.latestTimestamp || 0)) {
mostRecentMessage = {
latestIdentifier: message.identifier,
latestTimestamp: message.timestamp
};
const repliedMessage = fetchMessages.find(m => m && m.identifier === message.replyTo);
if (!repliedMessage) return "";
return `
<div class="reply-message" style="border-left: 2px solid #ccc; margin-bottom: 0.5vh; padding-left: 1vh;">
<div class="reply-header">In reply to: <span class="reply-username">${repliedMessage.name}</span> <span class="reply-timestamp">${repliedMessage.date}</span></div>
<div class="reply-content">${repliedMessage.content}</div>
</div>
`;
};
const buildAttachmentHtml = (message, room) => {
if (!message.attachments || message.attachments.length === 0) return "";
return message.attachments.map(attachment => buildSingleAttachmentHtml(attachment, room)).join("");
};
const buildSingleAttachmentHtml = (attachment, room) => {
if (room !== "admins" && attachment.mimeType && attachment.mimeType.startsWith('image/')) {
const imageUrl = `/arbitrary/${attachment.service}/${attachment.name}/${attachment.identifier}`;
return `
<div class="attachment">
<img src="${imageUrl}" alt="${attachment.filename}" class="inline-image"/>
</div>
`;
} else {
// Non-image attachment
return `
<div class="attachment">
<button onclick="fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')">
Download ${attachment.filename}
</button>
</div>
`;
}
};
// Add the identifier to the existingIdentifiers set
existingIdentifiers.add(message.identifier);
}
}
if (firstNewMessageIdentifier && !isPolling) {
// Scroll to the first new message
const scrollToNewMessages = (firstNewMessageIdentifier) => {
const newMessageElement = document.querySelector(`.message-item[data-identifier="${firstNewMessageIdentifier}"]`);
if (newMessageElement) {
newMessageElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
};
// Update latestMessageIdentifiers for the room
if (mostRecentMessage) {
const updateLatestMessageIdentifiers = (room, mostRecentMessage) => {
latestMessageIdentifiers[room] = mostRecentMessage;
localStorage.setItem("latestMessageIdentifiers", JSON.stringify(latestMessageIdentifiers));
}
};
// Add event listeners to the reply buttons
const handleReplyLogic = (fetchMessages) => {
const replyButtons = document.querySelectorAll(".reply-button");
replyButtons.forEach(button => {
button.addEventListener("click", () => {
replyToMessageIdentifier = button.dataset.messageIdentifier;
// Find the message being replied to
const replyToMessageIdentifier = button.dataset.messageIdentifier;
const repliedMessage = fetchMessages.find(m => m && m.identifier === replyToMessageIdentifier);
if (repliedMessage) {
showReplyPreview(repliedMessage);
}
});
});
};
const showReplyPreview = (repliedMessage) => {
replyToMessageIdentifier = repliedMessage.identifier;
const replyContainer = document.createElement("div");
replyContainer.className = "reply-container";
replyContainer.innerHTML = `
@ -797,17 +852,15 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
if (!document.querySelector(".reply-container")) {
const messageInputSection = document.querySelector(".message-input-section");
if (messageInputSection) {
messageInputSection.insertBefore(replyContainer, messageInputSection.firstChild);
// Add a listener for the cancel reply button
document.getElementById("cancel-reply").addEventListener("click", () => {
replyToMessageIdentifier = null;
replyContainer.remove();
});
}
}
const messageInputSection = document.querySelector(".message-input-section");
const editor = document.querySelector(".ql-editor");
@ -818,17 +871,13 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
if (editor) {
editor.focus();
}
}
});
});
};
// Render pagination controls
const totalMessages = await searchAllCountOnly(`${messageIdentifierPrefix}-${room}`);
const updatePaginationControls = async (room, limit) => {
const totalMessages = room === "admins" ? await searchAllCountOnly(`${messageIdentifierPrefix}-${room}`, room) : await searchAllCountOnly(`${messageIdentifierPrefix}-${room}-e`, room)
renderPaginationControls(room, totalMessages, limit);
} catch (error) {
console.error('Error loading messages from QDN:', error);
}
}
};
// Polling function to check for new messages without clearing existing ones

View File

@ -645,13 +645,34 @@ const searchAllWithOffset = async (service, query, limit, offset, room) => {
};
const searchAllCountOnly = async (query) => {
const searchAllCountOnly = async (query, room) => {
try {
let offset = 0;
const limit = 100; // Chunk size for fetching
let totalCount = 0;
let hasMore = true;
if (room === "admins") {
while (hasMore) {
const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
service: "MAIL_PRIVATE",
query: query,
limit: limit,
offset: offset,
mode: "ALL",
reverse: false
});
if (response && response.length > 0) {
totalCount += response.length;
offset = totalCount;
console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`);
} else {
hasMore = false;
}
}
}else {
// Fetch in chunks to accumulate the count
while (hasMore) {
const response = await qortalRequest({
@ -666,12 +687,13 @@ const searchAllCountOnly = async (query) => {
if (response && response.length > 0) {
totalCount += response.length;
offset += limit;
offset = totalCount;
console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`);
} else {
hasMore = false;
}
}
}
return totalCount;
} catch (error) {

View File

@ -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.54b<br></a></span>
<span class="navbar-caption-wrap"><a class="navbar-caption text-primary display-4" href="index.html">Q-Mintership Alpha v0.55b<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>
@ -263,7 +263,7 @@
</div>
<a class="link-wrap" href="#">
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.54beta</p>
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.55beta</p>
</a>
</div>
<div class="col-12 col-lg-6">