This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.

This commit is contained in:
crowetic 2024-12-26 20:06:51 -08:00
parent 0fc471a1b8
commit 2192f7c855
11 changed files with 424 additions and 45 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
// 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 isEncryptedTestMode = false
const encryptedCardIdentifierPrefix = "card-MAC"
let isExistingEncryptedCard = false
let existingDecryptedCardData = {}
@ -9,6 +9,7 @@ let existingCardMinterNames = []
let isTopic = false
let attemptLoadAdminDataCount = 0
let adminMemberCount = 0
let adminPublicKeys = []
console.log("Attempting to load AdminBoard.js");
@ -112,7 +113,7 @@ const loadAdminBoardPage = async () => {
const updateOrSaveAdminGroupsDataLocally = async () => {
try {
// Fetch the array of admin public keys
const verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys();
const verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
// Build an object containing the count and the array
const adminData = {
@ -120,12 +121,14 @@ const updateOrSaveAdminGroupsDataLocally = async () => {
publicKeys: verifiedAdminPublicKeys
};
// Stringify and save to localStorage
localStorage.setItem('savedAdminData', JSON.stringify(adminData));
adminPublicKeys = verifiedAdminPublicKeys
console.log('Admin public keys saved locally:', adminData);
// Stringify and save to localStorage
localStorage.setItem('savedAdminData', JSON.stringify(adminData))
console.log('Admin public keys saved locally:', adminData)
} catch (error) {
console.error('Error fetching/storing admin public keys:', error);
console.error('Error fetching/storing admin public keys:', error)
attemptLoadAdminDataCount++
}
};
@ -146,7 +149,10 @@ const loadOrFetchAdminGroupsData = async () => {
adminMemberCount = parsedData.keysCount
adminPublicKeys = parsedData.publicKeys
console.log(`Loaded admins 'keysCount'=${adminMemberCount}, adminKeys=`, adminPublicKeys)
console.log(typeof adminPublicKeys); // Should be "object"
console.log(Array.isArray(adminPublicKeys))
console.log(`Loaded admins 'keysCount'=${adminMemberCount}, publicKeys=`, adminPublicKeys)
attemptLoadAdminDataCount = 0
return parsedData; // and return { adminMemberCount, adminKeys } to the caller
@ -526,7 +532,22 @@ const publishEncryptedCard = async (isTopicModePassed = false) => {
base64CardData = btoa(JSON.stringify(cardData));
}
const verifiedAdminPublicKeys = (adminPublicKeys) ? adminPublicKeys : loadOrFetchAdminGroupsData().publicKeys
let verifiedAdminPublicKeys = adminPublicKeys
if ((!verifiedAdminPublicKeys) || verifiedAdminPublicKeys.length <= 5 || !Array.isArray(verifiedAdminPublicKeys)) {
console.log(`adminPublicKeys variable failed check, attempting to load from localStorage`,adminPublicKeys)
const savedAdminData = localStorage.getItem('savedAdminData')
const parsedAdminData = JSON.parse(savedAdminData)
const loadedAdminKeys = parsedAdminData.publicKeys
if ((!loadedAdminKeys) || (!Array.isArray(loadedAdminKeys)) || (loadedAdminKeys.length === 0)){
console.log('loaded admin keys from localStorage failed, falling back to API call...')
verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
}
verifiedAdminPublicKeys = loadedAdminKeys
}
await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
@ -605,8 +626,9 @@ const postEncryptedComment = async (cardIdentifier) => {
const commentIdentifier = `comment-${cardIdentifier}-${await uid()}`;
if (!Array.isArray(adminPublicKeys) || (adminPublicKeys.length === 0)) {
const verifiedAdminPublicKeys = await loadOrFetchAdminGroupsData().publicKeys
if (!Array.isArray(adminPublicKeys) || (adminPublicKeys.length === 0) || (!adminPublicKeys)) {
console.log('adminPpublicKeys variable failed checks, calling for admin public keys from API (comment)',adminPublicKeys)
const verifiedAdminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
adminPublicKeys = verifiedAdminPublicKeys
}
@ -731,12 +753,24 @@ const calculateAdminBoardPollResults = async (pollData, minterGroupMembers, mint
}
const toggleEncryptedComments = async (cardIdentifier) => {
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`);
if (commentsSection.style.display === 'none' || !commentsSection.style.display) {
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`)
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
if (!commentsSection || !commentButton) return;
const count = commentButton.dataset.commentCount;
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display);
if (isHidden) {
// Show comments
commentButton.textContent = "LOADING...";
await displayEncryptedComments(cardIdentifier);
commentsSection.style.display = 'block';
// Change the button text to 'HIDE COMMENTS'
commentButton.textContent = 'HIDE COMMENTS';
} else {
// Hide comments
commentsSection.style.display = 'none';
commentButton.textContent = `COMMENTS (${count})`;
}
};
@ -847,7 +881,7 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
showTopic = false
}
}
const cardColorCode = showTopic ? '#0e1b15' : '#151f28'
const minterOrTopicHtml = ((showTopic) || (isUndefinedUser)) ? `
@ -895,7 +929,7 @@ const createEncryptedCardHTML = async (cardData, pollResults, cardIdentifier, co
<div class="actions">
<div class="actions-buttons">
<button class="yes" onclick="voteYesOnPoll('${poll}')">YES</button>
<button class="comment" onclick="toggleEncryptedComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button id="comment-button-${cardIdentifier}" data-comment-count="${commentCount}" class="comment" onclick="toggleEncryptedComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div>
</div>

View File

@ -117,6 +117,78 @@ const loadMinterBoardPage = async () => {
await loadCards();
}
const extractMinterCardsMinterName = async (cardIdentifier) => {
// Ensure the identifier starts with the prefix
if (!cardIdentifier.startsWith(`${cardIdentifierPrefix}-`)) {
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');
}
try {
const nameFromIdentifier = await searchSimple('BLOG_POST', cardIdentifier, "", 1)
const minterName = await nameFromIdentifier.name
return minterName
} catch (error) {
throw error
}
}
const processMinterCards = async (validMinterCards) => {
const latestCardsMap = new Map()
// Step 1: Filter and keep the most recent card per identifier
validMinterCards.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)
}
})
// 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 validMinterCards) {
const minterName = await extractMinterCardsMinterName(card.identifier)
const existingCard = minterNameMap.get(minterName)
const cardTimestamp = card.updated || card.created || 0
const existingTimestamp = existingCard?.updated || existingCard?.created || 0
// 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 loadCards = async () => {
const cardsContainer = document.getElementById("cards-container");
@ -150,22 +222,24 @@ const loadCards = async () => {
return;
}
const finalCards = await processMinterCards(validCards)
// Sort cards by timestamp descending (newest first)
validCards.sort((a, b) => {
const timestampA = a.updated || a.created || 0;
const timestampB = b.updated || b.created || 0;
return timestampB - timestampA;
});
// validCards.sort((a, b) => {
// const timestampA = a.updated || a.created || 0;
// const timestampB = b.updated || b.created || 0;
// return timestampB - timestampA;
// });
// Display skeleton cards immediately
cardsContainer.innerHTML = "";
validCards.forEach(card => {
finalCards.forEach(card => {
const skeletonHTML = createSkeletonCardHTML(card.identifier);
cardsContainer.insertAdjacentHTML("beforeend", skeletonHTML);
});
// Fetch and update each card
validCards.forEach(async card => {
finalCards.forEach(async card => {
try {
const cardDataResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
@ -528,11 +602,23 @@ const displayComments = async (cardIdentifier) => {
// Toggle comments from being shown or not, with passed cardIdentifier for comments being toggled --------------------
const toggleComments = async (cardIdentifier) => {
const commentsSection = document.getElementById(`comments-section-${cardIdentifier}`);
if (commentsSection.style.display === 'none' || !commentsSection.style.display) {
const commentButton = document.getElementById(`comment-button-${cardIdentifier}`)
if (!commentsSection || !commentButton) return;
const count = commentButton.dataset.commentCount;
const isHidden = (commentsSection.style.display === 'none' || !commentsSection.style.display);
if (isHidden) {
// Show comments
commentButton.textContent = "LOADING...";
await displayComments(cardIdentifier);
commentsSection.style.display = 'block';
// Change the button text to 'HIDE COMMENTS'
commentButton.textContent = 'HIDE COMMENTS';
} else {
// Hide comments
commentsSection.style.display = 'none';
commentButton.textContent = `COMMENTS (${count})`;
}
};
@ -610,14 +696,13 @@ const generateDarkPastelBackgroundBy = (name) => {
const safeHash = Math.abs(hash);
// 2) Restrict hue to a 'blue-ish' range (150..270 = 120 degrees total)
// We'll define a certain number of hue steps in that range.
const hueSteps = 69.69; // e.g., 12 steps
const hueSteps = 69.69;
const hueIndex = safeHash % hueSteps;
// Each step is 120 / (hueSteps - 1) or so:
// but a simpler approach is 120 / hueSteps. It's okay if we don't use the extreme ends exactly.
const hueRange = 240;
const hue = 22.69 + (hueIndex * (hueRange / hueSteps));
// This yields values like 150, 160, 170, ... up to near 270
const hueRange = 288;
const hue = 140 + (hueIndex * (hueRange / hueSteps));
// 3) Satura­tion:
const satSteps = 13.69;
@ -690,7 +775,7 @@ const createCardHTML = async (cardData, pollResults, cardIdentifier, commentCoun
<div class="actions">
<div class="actions-buttons">
<button class="yes" onclick="voteYesOnPoll('${poll}')">YES</button>
<button class="comment" onclick="toggleComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button class="comment" id="comment-button-${cardIdentifier}" data-comment-count="${commentCount}" onclick="toggleComments('${cardIdentifier}')">COMMENTS (${commentCount})</button>
<button class="no" onclick="voteNoOnPoll('${poll}')">NO</button>
</div>
</div>

View File

@ -1,6 +1,5 @@
const messageIdentifierPrefix = `mintership-forum-message`;
const messageAttachmentIdentifierPrefix = `mintership-forum-attachment`;
let adminPublicKeys = []
// NOTE - SET adminGroups in QortalApi.js to enable admin access to forum for specific groups. Minter Admins will be fetched automatically.
@ -9,7 +8,58 @@ let latestMessageIdentifiers = {}; // To keep track of the latest message in eac
let currentPage = 0; // Track current pagination page
let existingIdentifiers = new Set(); // Keep track of existing identifiers to not pull them more than once.
let messagesById = {}
let messageOrder =[]
const MAX_MESSAGES = 2000
// Key = message.identifier
// Value = { ...the message object with timestamp, name, content, etc. }
// If there is a previous latest message identifiers, use them. Otherwise, use an empty.
const storeMessageInMap = (msg) => {
if (!msg?.identifier || !msg || !msg?.timestamp) return
messagesById[msg.identifier] = msg
// We will keep an array 'messageOrder' to store the messages and limit the size they take
messageOrder.push({ identifier: msg.identifier, timestamp: msg.timestamp })
messageOrder.sort((a, b) => a.timestamp - b.timestamp);
while (messageOrder.length > MAX_MESSAGES) {
// Remove oldest from the front
const oldest = messageOrder.shift();
// Delete from the map as well
delete messagesById[oldest.identifier];
}
}
function saveMessagesToLocalStorage() {
try {
const data = { messagesById, messageOrder };
localStorage.setItem("forumMessages", JSON.stringify(data));
console.log("Saved messages to localStorage. Count:", messageOrder.length);
} catch (error) {
console.error("Error saving to localStorage:", error);
}
}
function loadMessagesFromLocalStorage() {
try {
const stored = localStorage.getItem("forumMessages");
if (!stored) {
console.log("No saved messages in localStorage.");
return;
}
const parsed = JSON.parse(stored);
if (parsed.messagesById && parsed.messageOrder) {
messagesById = parsed.messagesById;
messageOrder = parsed.messageOrder;
console.log(`Loaded ${messageOrder.length} messages from localStorage.`);
}
} catch (error) {
console.error("Error loading messages from localStorage:", error);
}
}
if (localStorage.getItem("latestMessageIdentifiers")) {
latestMessageIdentifiers = JSON.parse(localStorage.getItem("latestMessageIdentifiers"));
}
@ -49,6 +99,21 @@ document.addEventListener("DOMContentLoaded", async () => {
// --- ADMIN CHECK ---
await verifyUserIsAdmin();
if (userState.isAdmin && (localStorage.getItem('savedAdminData'))) {
console.log('saved admin data found (Q-Mintership.js), loading...')
const adminData = localStorage.getItem('savedAdminData')
const parsedAdminData = JSON.parse(adminData)
if (!adminPublicKeys || adminPublicKeys.length === 0 || !Array.isArray(adminPublicKeys)) {
console.log('no adminPublicKey variable data found and/or data did not pass checks, using fetched localStorage data...',adminPublicKeys)
if (parsedAdminData.publicKeys.length === 0 || !parsedAdminData.publicKeys || !Array.isArray(parsedAdminData.publicKeys)) {
console.log('loaded data from localStorage also did not pass checks... fetching from API...',parsedAdminData.publicKeys)
adminPublicKeys = await fetchAdminGroupsMembersPublicKeys()
} else {
adminPublicKeys = parsedAdminData.publicKeys
}
}
}
if (userState.isAdmin) {
console.log(`User is an Admin. Admin-specific buttons will remain visible.`);
@ -285,7 +350,17 @@ const loadRoomContent = async (room) => {
initializeQuillEditor();
setupModalHandlers();
setupFileInputs(room);
await loadMessagesFromQDN(room, currentPage);
//TODO - maybe turn this into its own function and put it as a button? But for now it's fine to just load the latest message's position by default I think.
const latestId = latestMessageIdentifiers[room]?.latestIdentifier;
if (latestId) {
const page = await findMessagePage(room, latestId, 10)
currentPage = page;
await loadMessagesFromQDN(room, currentPage)
scrollToMessage(latestId.latestIdentifier)
} else{
await loadMessagesFromQDN(room, currentPage)
}
;
};
// Initialize Quill editor
@ -572,6 +647,21 @@ const generateAttachmentID = (room, fileIndex = null) => {
// --- REFACTORED LOAD MESSAGES AND HELPER FUNCTIONS ---
const findMessagePage = async (room, identifier, limit) => {
const { service, query } = getServiceAndQuery(room)
const allMessages = await searchAllWithOffset(service, query, 0, 0, room)
const idx = allMessages.findIndex(msg => msg.identifier === identifier);
if (idx === -1) {
// Not found, default to last page or page=0
return 0;
}
return Math.floor(idx / limit)
}
const loadMessagesFromQDN = async (room, page, isPolling = false) => {
try {
const limit = 10;
@ -601,6 +691,12 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
let mostRecentMessage = getCurrentMostRecentMessage(room);
const fetchMessages = await fetchAllMessages(response, service, room);
for (const msg of fetchMessages) {
if (!msg) continue;
storeMessageInMap(msg);
}
const { firstNewMessageIdentifier, updatedMostRecentMessage } = await renderNewMessages(
fetchMessages,
existingIdentifiers,
@ -625,6 +721,13 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
}
};
function scrollToMessage(identifier) {
const targetElement = document.querySelector(`.message-item[data-identifier="${identifier}"]`);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
/** Helper Functions (Arrow Functions) **/
const prepareMessageContainer = (messagesContainer, isPolling) => {
@ -684,6 +787,13 @@ const fetchAllMessages = async (response, service, room) => {
// 2) fetchFullMessage is already async. We keep it async/await-based
const fetchFullMessage = async (resource, service, room) => {
// 1) Skip if we already have it in memory
if (messagesById[resource.identifier]) {
// Possibly also check if the local data is "up to date," //TODO when adding 'edit' ability to messages, will also need to verify timestamp in saved data.
// but if you trust your local data, skip the fetch entirely.
console.log(`Skipping fetch. Found in local store: ${resource.identifier}`);
return messagesById[resource.identifier];
}
try {
// Skip if already displayed
if (existingIdentifiers.has(resource.identifier)) {
@ -703,7 +813,7 @@ const fetchFullMessage = async (resource, service, room) => {
const formattedTimestamp = await timestampToHumanReadableDate(timestamp);
const messageObject = await processMessageObject(messageResponse, room);
return {
const builtMsg = {
name: resource.name,
content: messageObject?.messageHtml || "<em>Message content missing</em>",
date: formattedTimestamp,
@ -712,6 +822,11 @@ const fetchFullMessage = async (resource, service, room) => {
timestamp,
attachments: messageObject?.attachments || [],
};
// 3) Store it in the map so we skip future fetches
storeMessageInMap(builtMsg);
return builtMsg;
} catch (error) {
console.error(`Failed to fetch message ${resource.identifier}: ${error.message}`);
return {
@ -726,6 +841,46 @@ const fetchFullMessage = async (resource, service, room) => {
}
};
const fetchReplyData = async (service, name, identifier, room, replyTimestamp) => {
try {
console.log(`Fetching message with identifier: ${identifier}`);
const messageResponse = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name,
service,
identifier,
...(room === "admins" ? { encoding: "base64" } : {}),
})
console.log('reply response',messageResponse)
const messageObject = await processMessageObject(messageResponse, room)
console.log('reply message object',messageObject)
const formattedTimestamp = await timestampToHumanReadableDate(replyTimestamp)
return {
name,
content: messageObject?.messageHtml || "<em>Message content missing</em>",
date: formattedTimestamp,
identifier,
replyTo: messageObject?.replyTo || null,
timestamp: replyTimestamp,
attachments: messageObject?.attachments || [],
};
} catch (error) {
console.error(`Failed to fetch message ${identifier}: ${error.message}`)
return {
name,
content: "<em>Error loading message</em>",
date: "Unknown",
identifier,
replyTo: null,
timestamp: null,
attachments: [],
}
}
}
const processMessageObject = async (messageResponse, room) => {
if (room !== "admins") {
@ -774,7 +929,7 @@ const isMessageNew = (message, mostRecentMessage) => {
};
const buildMessageHTML = async (message, fetchMessages, room, isNewMessage) => {
const replyHtml = await buildReplyHtml(message, fetchMessages);
const replyHtml = await buildReplyHtml(message, room);
const attachmentHtml = await buildAttachmentHtml(message, room);
const avatarUrl = `/arbitrary/THUMBNAIL/${message.name}/qortal_avatar`;
@ -798,18 +953,57 @@ const buildMessageHTML = async (message, fetchMessages, room, isNewMessage) => {
`
}
const buildReplyHtml = async (message, fetchMessages) => {
const buildReplyHtml = async (message, room) => {
if (!message.replyTo) return ""
const replyService = (room === "admins") ? "MAIL_PRIVATE" : "BLOG_POST";
const replyIdentifier = message.replyTo
const repliedMessage = fetchMessages.find(m => m && m.identifier === message.replyTo)
if (!repliedMessage) return ""
const savedRepliedToMessage = messagesById[message.replyTo];
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>
`
if (savedRepliedToMessage) {
const processedMessage = await processMessageObject(savedRepliedToMessage, room)
console.log('message is saved in saved message data, returning from that',savedRepliedToMessage)
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">${processedMessage.name}</span>
<span class="reply-timestamp">${processedMessage.date}</span>
</div>
<div class="reply-content">${processedMessage.content}</div>
</div>
`;
}
try {
const replyData = await searchSimple(replyService, replyIdentifier, "", 1)
if ((!replyData) || (!replyData.name)){
// No result found. You can either return an empty string or handle differently
console.log("No data found via searchSimple. Skipping reply rendering.");
return "";
}
const replyName = await replyData.name
const replyTimestamp = await replyData.updated || await replyData.created
console.log('message not found in saved message data, using searchSimple', replyData)
// const repliedMessage = fetchMessages.find(m => m && m.identifier === message.replyTo)
// const repliedMessageIdentifier = message.replyTo
const repliedMessage = await fetchReplyData(replyService, replyName, replyIdentifier, room, replyTimestamp)
storeMessageInMap(repliedMessage)
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>
`
} catch (error) {
throw (error)
}
}
const buildAttachmentHtml = async (message, room) => {

View File

@ -651,9 +651,58 @@ const searchAllWithOffset = async (service, query, limit, offset, room) => {
console.error("Error during SEARCH_QDN_RESOURCES:", error);
return []; // Return empty array on error
}
};
// NOTE - This function does a search and will return EITHER AN ARRAY OR A SINGLE OBJECT. if you want to guarantee a single object, pass 1 as limit. i.e. await searchSimple(service, identifier, "", 1) will return a single object.
const searchSimple = async (service, identifier, name, limit = 20) => {
try {
let urlSuffix = `service=${service}&identifier=${identifier}&name=${name}&limit=${limit}`;
if (name && !identifier) {
console.log('name only searchSimple', name);
urlSuffix = `service=${service}&name=${name}&limit=${limit}`;
} else if (!name && identifier) {
console.log('identifier only searchSimple', identifier);
urlSuffix = `service=${service}&identifier=${identifier}&limit=${limit}`;
} else if (!name && !identifier) {
console.error(`name: ${name} AND identifier: ${identifier} not passed. Must include at least one...`);
return null;
} else {
console.log(`name: ${name} AND identifier: ${identifier} passed, searching by both...`);
}
const response = await fetch(`${baseUrl}/arbitrary/resources/searchsimple?${urlSuffix}`, {
method: 'GET',
headers: { 'accept': 'application/json' }
});
const data = await response.json();
if (!Array.isArray(data)) {
console.log("searchSimple: data is not an array?", data);
return null;
}
if (data.length === 0) {
console.log("searchSimple: no results found");
return null; // Return null when no items
}
if (data.length === 1 || limit === 1) {
console.log("searchSimple: single result returned", data[0]);
return data[0]; // Return just the single object
}
console.log("searchSimple: multiple results returned", data);
return data;
} catch (error) {
console.error("error during searchSimple", error);
throw error;
}
};
const searchAllCountOnly = async (query, room) => {
try {
let offset = 0;

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.6b<br></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>
</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,23 @@
<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">
Yet More Fixes + Updates 12-26-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">
Another update has been accomplished, this time to version 0.61beta. This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well. </p>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-12 col-lg-7 card">
@ -298,7 +315,7 @@
</div>
<a class="link-wrap" href="#">
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.6beta</p>
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.61beta</p>
</a>
</div>
<div class="col-12 col-lg-6">