diff --git a/assets/css/forum-styles.css b/assets/css/forum-styles.css index 94ad1a2..3f22c67 100644 --- a/assets/css/forum-styles.css +++ b/assets/css/forum-styles.css @@ -169,6 +169,45 @@ padding: 1vh; } +.file-input { + display: none; +} + +.image-input { + display: none; +} + +/* Custom button styling */ +.custom-file-input-button { + display: inline-block; + background-color: #07375a; + color: white; + padding: 0.5vh 0.2vh; + cursor: pointer; + border: solid white; + border-radius: 5px; + text-align: center; +} + +.custom-file-input-button:hover { + background-color: #073783; +} + +.custom-image-input-button { + display: inline-block; + background-color: #350550; + color: white; + padding: 0.5vh 0.2vh; + cursor: pointer; + border: solid white; + border-radius: 5px; + text-align: center; +} + +.custom-image-input-button:hover { + background-color: #4d0541; +} + /* .ql-editor { flex-grow: 1; font-size: 1.25rem; @@ -338,9 +377,9 @@ position: fixed; /* Stay in place */ z-index: 1000; /* Sit on top */ left: 10%; - top: 10%; + top: 15%; width: 80%; - height: 80%; + height: 70%; overflow: none; /* Enable scroll if needed */ background-color: rgba(0, 0, 0, 0.8); /* Black w/ opacity */ } @@ -349,7 +388,7 @@ margin: auto; display: block; border: 2px dashed #638b93; - width: 60%; + width: 69%; height: auto; max-width: 1200vh; } @@ -436,10 +475,12 @@ body { /* Publish Card View */ .publish-card-view { display: flex; - flex-direction: column; - justify-content: center; /* Centers vertically */ - align-items: center; /* Centers horizontally */ - min-height: 80vh; /* Ensures the view takes full height of the viewport */ + text-align: left; + padding: 0em; + flex-wrap: wrap; + align-content: flex-start; + justify-content: center; + align-items: center; } diff --git a/assets/js/MinterBoard.js b/assets/js/MinterBoard.js index 8463405..352ac3b 100644 --- a/assets/js/MinterBoard.js +++ b/assets/js/MinterBoard.js @@ -18,7 +18,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); }); -async function loadMinterBoardPage() { +const loadMinterBoardPage = async () => { // Clear existing content on the page const bodyChildren = document.body.children; for (let i = bodyChildren.length - 1; i >= 0; i--) { @@ -67,7 +67,7 @@ async function loadMinterBoardPage() { if (updateCard) { // Load existing card into the form for editing - loadCardIntoForm(existingCardData); + await loadCardIntoForm(existingCardData); alert("Edit your existing card and publish."); } else { // Allow creating a new card for testing purposes @@ -91,14 +91,14 @@ async function loadMinterBoardPage() { } }); - document.getElementById("cancel-publish-button").addEventListener("click", () => { + document.getElementById("cancel-publish-button").addEventListener("click", async () => { const cardsContainer = document.getElementById("cards-container"); cardsContainer.style.display = "flex"; // Restore visibility const publishCardView = document.getElementById("publish-card-view"); publishCardView.style.display = "none"; // Hide the publish form }); - document.getElementById("add-link-button").addEventListener("click", () => { + document.getElementById("add-link-button").addEventListener("click", async () => { const linksContainer = document.getElementById("links-container"); const newLinkInput = document.createElement("input"); newLinkInput.type = "text"; @@ -115,7 +115,75 @@ async function loadMinterBoardPage() { await loadCards(); } -async function fetchExistingCard() { +//Main function to load the Minter Cards ---------------------------------------- +const loadCards = async () => { + const cardsContainer = document.getElementById("cards-container"); + cardsContainer.innerHTML = "

Loading cards...

"; + + try { + const response = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service: "BLOG_POST", + query: cardIdentifierPrefix, + mode: "ALL" + }); + + if (!response || !Array.isArray(response) || response.length === 0) { + cardsContainer.innerHTML = "

No cards found.

"; + return; + } + + const validatedCards = await Promise.all( + response.map(async card => { + console.log("Validating card:", card); + const isValid = await validateCardStructure(card); + return isValid ? card : null; + }) + ); + + const validCards = validatedCards.filter(card => card !== null); + cardsContainer.innerHTML = ""; + + if (validCards.length === 0) { + cardsContainer.innerHTML = "

No valid cards found.

"; + return; + } + + await Promise.all( + validCards.map(async card => { + try { + const cardDataResponse = await qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: card.name, + service: "BLOG_POST", + identifier: card.identifier, + }); + + const cardData = cardDataResponse; + if (!cardData || !cardData.poll) { + console.warn(`Skipping card with missing poll data: ${JSON.stringify(cardData)}`); + return; + } + + const pollResults = await fetchPollResults(cardData.poll); + console.log(`Poll Results Fetched - totalVotes: ${pollResults.totalVotes}`); + + const cardHTML = await createCardHTML(cardData, pollResults, card.identifier); + cardsContainer.insertAdjacentHTML("beforeend", cardHTML); + } catch (error) { + console.error(`Error processing card ${card.identifier}:`, error); + } + }) + ); + } catch (error) { + console.error("Error loading cards:", error); + cardsContainer.innerHTML = "

Failed to load cards.

"; + } +}; + + +// Function to check and fech an existing Minter Card if attempting to publish twice ---------------------------------------- +const fetchExistingCard = async () => { try { // Step 1: Perform the search const response = await qortalRequest({ @@ -123,7 +191,8 @@ async function fetchExistingCard() { service: "BLOG_POST", identifier: cardIdentifierPrefix, name: userState.accountName, - exactMatchNames: true //we want to search for the EXACT userName only when finding existing cards. + mode: "ALL", + exactMatchNames: true // Search for the exact userName only when finding existing cards }); console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`); @@ -134,37 +203,48 @@ async function fetchExistingCard() { return null; } - const validCards = response.filter(card => validateCardStructure(card)); + // Step 3: Validate cards asynchronously + const validatedCards = await Promise.all( + response.map(async card => { + const isValid = await validateCardStructure(card); + return isValid ? card : null; + }) + ); + + // Step 4: Filter out invalid cards + const validCards = validatedCards.filter(card => card !== null); if (validCards.length > 0) { - // Sort by most recent timestamp + // Step 5: Sort by most recent timestamp const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0]; - const cardDataResponse = await qortalRequest({ - action: "FETCH_QDN_RESOURCE", - name: userState.accountName, // User's account name - service: "BLOG_POST", - identifier: mostRecentCard.identifier, - }); - - existingCardIdentifier = mostRecentCard.identifier - existingCardData = cardDataResponse + // Step 6: Fetch full card data + const cardDataResponse = await qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: userState.accountName, // User's account name + service: "BLOG_POST", + identifier: mostRecentCard.identifier + }); - console.log("Full card data fetched successfully:", cardDataResponse); + existingCardIdentifier = mostRecentCard.identifier; + existingCardData = cardDataResponse; - return cardDataResponse; // Return full card data - } + console.log("Full card data fetched successfully:", cardDataResponse); - console.log("No valid cards found."); + return cardDataResponse; + } + + console.log("No valid cards found."); return null; - } catch (error) { console.error("Error fetching existing card:", error); return null; } -} +}; -const validateCardStructure = (card) => { + +// Validate that a card is indeed a card and not a comment. ------------------------------------- +const validateCardStructure = async (card) => { return ( typeof card === "object" && card.name && @@ -174,7 +254,9 @@ const validateCardStructure = (card) => { ); } -function loadCardIntoForm(cardData) { +// Load existing card data passed, into the form for editing ------------------------------------- +const loadCardIntoForm = async (cardData) => { + console.log("Loading existing card data:", cardData); document.getElementById("card-header").value = cardData.header; document.getElementById("card-content").value = cardData.content; @@ -189,7 +271,8 @@ function loadCardIntoForm(cardData) { }); } -async function publishCard() { +// Main function to publish a new Minter Card ----------------------------------------------- +const publishCard = async () => { const header = document.getElementById("card-header").value.trim(); const content = document.getElementById("card-content").value.trim(); const links = Array.from(document.querySelectorAll(".card-link")) @@ -213,7 +296,7 @@ async function publishCard() { timestamp: Date.now(), poll: pollName, }; - // new Date().toISOString() + try { let base64CardData = await objectToBase64(cardData); @@ -221,7 +304,6 @@ async function publishCard() { console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`); base64CardData = btoa(JSON.stringify(cardData)); } - // const base64CardData = btoa(JSON.stringify(cardData)); await qortalRequest({ action: "PUBLISH_QDN_RESOURCE", @@ -254,83 +336,45 @@ async function publishCard() { } } -async function loadCards() { - const cardsContainer = document.getElementById("cards-container"); - cardsContainer.innerHTML = "

Loading cards...

"; +//Calculate the poll results passed from other functions with minterGroupMembers and minterAdmins --------------------------- +const calculatePollResults = async (pollData, minterGroupMembers, minterAdmins) => { + const memberAddresses = minterGroupMembers.map(member => member.member) + const adminAddresses = minterAdmins.map(member => member.member) - try { - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "BLOG_POST", - query: cardIdentifierPrefix, - }); + let adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, yesWeight = 0 , noWeight = 0 - if (!response || response.length === 0) { - cardsContainer.innerHTML = "

No cards found.

"; - return; + pollData.voteWeights.forEach(weightData => { + if (weightData.optionName === 'Yes') { + yesWeight = weightData.voteWeight + } else if (weightData.optionName === 'No') { + noWeight = weightData.voteWeight } + }) - cardsContainer.innerHTML = "" - const pollResultsCache = {}; - - const validCards = response.filter(card => validateCardStructure(card)); - - for (const card of validCards) { - const cardDataResponse = await qortalRequest({ - action: "FETCH_QDN_RESOURCE", - name: card.name, - service: "BLOG_POST", - identifier: card.identifier, - }); - - const cardData = cardDataResponse; - - if (!cardData || !cardData.poll) { - console.warn(`Skipping card with missing poll data: ${JSON.stringify(cardData)}`); - continue; // Skip to the next card - } - - // Cache poll results - if (!pollResultsCache[cardData.poll]) { - try { - pollResultsCache[cardData.poll] = await fetchPollResults(cardData.poll); - } catch (error) { - console.warn(`Failed to fetch poll results for poll: ${cardData.poll}`, error); - pollResultsCache[cardData.poll] = null; // Store as null to avoid repeated attempts - } - } - - const pollResults = pollResultsCache[cardData.poll]; - const cardHTML = await createCardHTML(cardData, pollResults, card.identifier); - cardsContainer.insertAdjacentHTML("beforeend", cardHTML); - } - } catch (error) { - console.error("Error loading cards:", error); - cardsContainer.innerHTML = "

Failed to load cards.

"; - } -} - -const calculatePollResults = (pollData, minterGroupMembers) => { - const memberAddresses = minterGroupMembers.map(member => member.member); - let adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0; - - pollData.votes.forEach(vote => { - const voterAddress = getAddressFromPublicKey(vote.voterPublicKey); - const isAdmin = minterGroupMembers.some(member => member.member === voterAddress && member.isAdmin); + for (const vote of pollData.votes) { + const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey) + console.log(`voter address: ${voterAddress}`) if (vote.optionIndex === 0) { - isAdmin ? adminYes++ : memberAddresses.includes(voterAddress) ? minterYes++ : null; + adminAddresses.includes(voterAddress) ? adminYes++ : memberAddresses.includes(voterAddress) ? minterYes++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`) } else if (vote.optionIndex === 1) { - isAdmin ? adminNo++ : memberAddresses.includes(voterAddress) ? minterNo++ : null; + adminAddresses.includes(voterAddress) ? adminNo++ : memberAddresses.includes(voterAddress) ? minterNo++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`) } - }); + } - const totalYes = adminYes + minterYes; - const totalNo = adminNo + minterNo; + // TODO - create a new function to calculate the weights of each voting MINTER only. + // This will give ALL weight whether voter is in minter group or not... + // until that is changed on the core we must calculate manually. + const totalYesWeight = yesWeight + const totalNoWeight = noWeight - return { adminYes, adminNo, minterYes, minterNo, totalYes, totalNo }; -}; + const totalYes = adminYes + minterYes + const totalNo = adminNo + minterNo + return { adminYes, adminNo, minterYes, minterNo, totalYes, totalNo, totalYesWeight, totalNoWeight } +} + +// Post a comment on a card. --------------------------------- const postComment = async (cardIdentifier) => { const commentInput = document.getElementById(`new-comment-${cardIdentifier}`); const commentText = commentInput.value.trim(); @@ -353,7 +397,7 @@ const postComment = async (cardIdentifier) => { console.log(`initial base64 object creation with objectToBase64 failed, using btoa...`); base64CommentData = btoa(JSON.stringify(commentData)); } - // const base64CommentData = btoa(JSON.stringify(commentData)); + await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', name: userState.accountName, @@ -371,12 +415,14 @@ 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; } catch (error) { @@ -385,6 +431,7 @@ const fetchCommentsForCard = async (cardIdentifier) => { } }; +// display the comments on the card, with passed cardIdentifier to identify the card -------------- const displayComments = async (cardIdentifier) => { try { const comments = await fetchCommentsForCard(cardIdentifier); @@ -399,6 +446,7 @@ const displayComments = async (cardIdentifier) => { identifier: comment.identifier, }); const timestamp = await timestampToHumanReadableDate(commentDataResponse.timestamp); + //TODO - add fetching of poll results and checking to see if the commenter has voted and display it as 'supports minter' section. const commentHTML = `

${commentDataResponse.creator}:

@@ -414,6 +462,7 @@ const displayComments = async (cardIdentifier) => { } }; +// Vote YES on a poll ------------------------------ const voteYesOnPoll = async (poll) => { await qortalRequest({ action: "VOTE_ON_POLL", @@ -422,6 +471,7 @@ const voteYesOnPoll = async (poll) => { }); } +// Vote NO on a poll ----------------------------- const voteNoOnPoll = async (poll) => { await qortalRequest({ action: "VOTE_ON_POLL", @@ -430,6 +480,7 @@ const voteNoOnPoll = async (poll) => { }); } +// 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) { @@ -440,20 +491,58 @@ const toggleComments = async (cardIdentifier) => { } }; -async function createCardHTML(cardData, pollResults, cardIdentifier) { +const createModal = async () => { + const modalHTML = ` + + `; + document.body.insertAdjacentHTML('beforeend', modalHTML); +} + +// Function to open the modal +const openModal = async (link) => { + const processedLink = await processLink(link) // Process the link to replace `qortal://` for rendering in modal + const modal = document.getElementById('modal'); + const modalContent = document.getElementById('modalContent'); + modalContent.src = processedLink; // Set the iframe source to the link + modal.style.display = 'block'; // Show the modal +} + +// Function to close the modal +const closeModal = async () => { + const modal = document.getElementById('modal'); + const modalContent = document.getElementById('modalContent'); + modal.style.display = 'none'; // Hide the modal + modalContent.src = ''; // Clear the iframe source +} + +const processLink = async (link) => { + if (link.startsWith('qortal://')) { + return link.replace('qortal://', '/render/') + } + return link // Return the link unchanged if it doesn't start with `qortal://` +} + + +// Create the overall Minter Card HTML ----------------------------------------------- +const createCardHTML = async (cardData, pollResults, cardIdentifier) => { const { header, content, links, creator, timestamp, poll } = cardData; const formattedDate = new Date(timestamp).toLocaleString(); const avatarUrl = `/arbitrary/THUMBNAIL/${creator}/qortal_avatar`; const linksHTML = links.map((link, index) => ` - `).join(""); const minterGroupMembers = await fetchMinterGroupMembers(); - const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0 } = - calculatePollResults(pollResults, minterGroupMembers) || {}; - + const minterAdmins = await fetchMinterGroupAdmins(); + const { adminYes = 0, adminNo = 0, minterYes = 0, minterNo = 0, totalYes = 0, totalNo = 0, totalYesWeight = 0, totalNoWeight = 0 } = await calculatePollResults(pollResults, minterGroupMembers, minterAdmins) + await createModal() return `
diff --git a/assets/js/Q-Mintership.js b/assets/js/Q-Mintership.js index 86f987e..a319a5f 100644 --- a/assets/js/Q-Mintership.js +++ b/assets/js/Q-Mintership.js @@ -14,7 +14,7 @@ if (localStorage.getItem("latestMessageIdentifiers")) { } document.addEventListener("DOMContentLoaded", async () => { - // Identify the link for 'Mintership Forum' + // Identify the links for 'Mintership Forum' and apply functionality const mintershipForumLinks = document.querySelectorAll('a[href="MINTERSHIP-FORUM"]'); mintershipForumLinks.forEach(link => { @@ -31,14 +31,9 @@ document.addEventListener("DOMContentLoaded", async () => { }); }); -async function loadForumPage() { - // // Remove all sections except the menu - // const allSections = document.querySelectorAll('body > section'); - // allSections.forEach(section => { - // if (!section.classList.contains('menu')) { - // section.remove(); - // } - // }); +// Main load function to clear existing HTML and load the forum page ----------------------------------------------------- +const loadForumPage = async () => { + // remove everything that isn't the menu from the body to use js to generate page content. const bodyChildren = document.body.children; for (let i = bodyChildren.length - 1; i >= 0; i--) { const child = bodyChildren[i]; @@ -89,7 +84,8 @@ async function loadForumPage() { }); } -async function renderPaginationControls(room, totalMessages, limit) { +// Function to add the pagination buttons and related control mechanisms ------------------------ +const renderPaginationControls = async(room, totalMessages, limit) => { const paginationContainer = document.getElementById("pagination-container"); if (!paginationContainer) return; @@ -138,9 +134,8 @@ async function renderPaginationControls(room, totalMessages, limit) { } } - - -async function loadRoomContent(room) { +// Main function to load the full content of the room, along with all main functionality ----------------------------------- +const loadRoomContent = async (room) => { const forumContent = document.getElementById("forum-content"); if (forumContent) { forumContent.innerHTML = ` @@ -153,9 +148,13 @@ async function loadRoomContent(room) {
- + + + + +
- +
`; @@ -191,7 +190,7 @@ async function loadRoomContent(room) { // Load messages from QDN for the selected room await loadMessagesFromQDN(room, currentPage); - document.addEventListener("click", (event) => { + document.addEventListener("click", async (event) => { if (event.target.classList.contains("inline-image")) { const modal = document.getElementById("image-modal"); const modalImage = document.getElementById("modal-image"); @@ -202,75 +201,189 @@ async function loadRoomContent(room) { modalImage.src = event.target.src; caption.textContent = event.target.alt; - // Set download button link - This has been moved to the Message Rendering Section of the code. - // downloadButton.onclick = () => { - // fetchAndSaveAttachment( - // "FILE", - // event.target.dataset.name, - // event.target.dataset.identifier, - // event.target.dataset.filename, - // event.target.dataset.mimeType - // ); - // }; - // Show the modal modal.style.display = "block"; } }); // Close the modal - document.getElementById("close-modal").addEventListener("click", () => { + document.getElementById("close-modal").addEventListener("click", async () => { document.getElementById("image-modal").style.display = "none"; }); // Hide the modal when clicking outside of the image or close button - window.addEventListener("click", (event) => { + window.addEventListener("click", async (event) => { const modal = document.getElementById("image-modal"); - if (event.target == modal) { + if (!event.target == modal) { modal.style.display = "none"; } }); let selectedFiles = []; - - // Add event listener to handle file selection - document.getElementById('file-input').addEventListener('change', (event) => { - selectedFiles = Array.from(event.target.files); + let selectedImages = []; + let attachmentIdentifiers = []; + let multiResource = [] + + const imageFileInput = document.getElementById('image-input'); + const previewContainer = document.getElementById('preview-container'); + const addToPublishButton = document.getElementById('add-images-to-publish-button') + const randomID = await uid(); + const attachmentID = `${messageAttachmentIdentifierPrefix}-${room}-${randomID}`; + + imageFileInput.addEventListener('change', async (event) => { + // Clear previous previews to prepare for preview generation + previewContainer.innerHTML = ''; + selectedImages = Array.from(event.target.files); + + if (selectedImages.length > 0) { + addToPublishButton.disabled = false; + } + + selectedImages.forEach((file, index) => { + const reader = new FileReader(); + reader.onload = () => { + const img = document.createElement('img'); + img.src = reader.result; + img.alt = file.name; + img.style.width = '100px'; + img.style.height = '100px'; + img.style.objectFit = 'cover'; + img.style.border = '1px solid #ccc'; + img.style.borderRadius = '5px'; + + // Add remove button + const removeButton = document.createElement('button'); + removeButton.innerText = 'Remove'; + removeButton.style.marginTop = '5px'; + removeButton.onclick = () => { + selectedImages.splice(index, 1); + img.remove(); + removeButton.remove(); + if (selectedImages.length === 0) { + addToPublishButton.disabled = true; + } + }; + + const container = document.createElement('div'); + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.alignItems = 'center'; + container.style.margin = '5px'; + container.appendChild(img); + container.appendChild(removeButton); + previewContainer.appendChild(container); + }; + reader.readAsDataURL(file); + }); }); - // Add event listener for the send button + addToPublishButton.addEventListener('click', async () => { + await addImagesToMultiPublish() + }) + + // Function to add images in the preview to the multi-publish object -------------------------- + const addImagesToMultiPublish = async () => { + console.log('Adding Images to multi-publish:', selectedImages); + for (let i = 0; i < selectedImages.length; i++) { + const file = selectedImages[i]; + try { + multiResource.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + file: file, + }); + + attachmentIdentifiers.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + filename: file.name, + mimeType: file.type + }); + + console.log(`Attachment ${file.name} placed into multiResource with attachmentID: ${attachmentID}`); + + // Remove the processed file + selectedImages.splice(i, 1); + i--; // Adjust the index since we removed an item + + } catch (error) { + console.error(`Error processing attachment ${file.name}:`, error); + } + } + selectedImages = [] + addToPublishButton.disabled = true + } + + // Add event listener to handle file selection + document.getElementById('file-input').addEventListener('change', async (event) => { + selectedFiles = Array.from(event.target.files); + }); + // Add event listener for the PUBLISH button document.getElementById("send-button").addEventListener("click", async () => { const messageHtml = quill.root.innerHTML.trim(); - if (messageHtml !== "" || selectedFiles.length > 0) { - const randomID = await uid(); + if (messageHtml !== "" || selectedFiles.length > 0 || selectedImages.length > 0) { const messageIdentifier = `${messageIdentifierPrefix}-${room}-${randomID}`; - let attachmentIdentifiers = []; - - // Handle attachments - for (const file of selectedFiles) { - const attachmentID = `${messageAttachmentIdentifierPrefix}-${room}-${randomID}`; - try { - await qortalRequest({ - action: "PUBLISH_QDN_RESOURCE", - name: userState.accountName, - service: "FILE", - identifier: attachmentID, - file: file, - }); - attachmentIdentifiers.push({ - name: userState.accountName, - service: "FILE", - identifier: attachmentID, - filename: file.name, - mimeType: file.type - }); - console.log(`Attachment ${file.name} published successfully with ID: ${attachmentID}`); - } catch (error) { - console.error(`Error publishing attachment ${file.name}:`, error); + + if (selectedImages.length > 0) { + await addImagesToMultiPublish() + } + if (selectedFiles.length === 1) { + console.log(`single file has been detected, attaching single file...`) + const singleAttachment = selectedFiles[0] + + multiResource.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + file: singleAttachment + }) + + attachmentIdentifiers.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + filename: singleAttachment.name, + filetype: singleAttachement.type + }) + // Clear selectedFiles as we do not need them anymore. + document.getElementById('file-input').value = ""; + selectedFiles = []; + + }else if (selectedFiles.length >= 2) { + console.log(`selected files found: ${selectedFiles.length}, adding multiple files to multi-publish resource...`) + // Handle Multiple attachements utilizing multi-publish + for (let i = 0; i < selectedFiles.length; i++) { + const file = selectedFiles[i]; + try { + multiResource.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + file: file, + }); + + attachmentIdentifiers.push({ + name: userState.accountName, + service: "FILE", + identifier: attachmentID, + filename: file.name, + mimeType: file.type + }); + + console.log(`Attachment ${file.name} placed into multiResource with attachmentID: ${attachmentID}`); + + // Remove the processed file + selectedFiles.splice(i, 1); + i--; // Adjust the index since we removed an item + } catch (error) { + console.error(`Error processing attachment ${file.name}:`, error); + } } } - + // Create message object with unique identifier, HTML content, and attachments const messageObject = { messageHtml: messageHtml, @@ -278,7 +391,7 @@ async function loadRoomContent(room) { attachments: attachmentIdentifiers, replyTo: replyToMessageIdentifier }; - + try { // Convert message object to base64 let base64Message = await objectToBase64(messageObject); @@ -287,21 +400,24 @@ async function loadRoomContent(room) { base64Message = btoa(JSON.stringify(messageObject)); } - // Publish message to QDN - await qortalRequest({ - action: "PUBLISH_QDN_RESOURCE", + // Put the message into the multiResource for batch-publishing. + multiResource.push({ name: userState.accountName, service: "BLOG_POST", identifier: messageIdentifier, data64: base64Message }); - console.log("Message published successfully"); + console.log("Message added to multi-publish resource successfully, attempting multi-publish... "); + + await publishMultipleResources(multiResource) // Clear the editor after sending the message, including any potential attached files and replies. quill.root.innerHTML = ""; document.getElementById('file-input').value = ""; selectedFiles = []; + selectedImages = []; + multiResource = []; replyToMessageIdentifier = null; const replyContainer = document.querySelector(".reply-container"); if (replyContainer) { @@ -310,33 +426,24 @@ async function loadRoomContent(room) { // Show success notification const notification = document.createElement('div'); - notification.innerText = "Message published successfully! Message will take a confirmation to show."; + notification.innerText = "Message published successfully! Message will take a confirmation to show, please be patient..."; notification.style.color = "green"; - notification.style.marginTop = "10px"; + notification.style.marginTop = "1em"; document.querySelector(".message-input-section").appendChild(notification); setTimeout(() => { notification.remove(); - }, 3000); + }, 10000); } catch (error) { console.error("Error publishing message:", error); } } - }); - // Add event listener for the load more button - const loadMoreContainer = document.getElementById("load-more-container"); - if (loadMoreContainer) { - loadMoreContainer.innerHTML = ''; - document.getElementById("load-more-button").addEventListener("click", () => { - currentPage++; - loadMessagesFromQDN(room, currentPage); - }); - } + }) } } -async function loadMessagesFromQDN(room, page, isPolling = false) { +const loadMessagesFromQDN = async (room, page, isPolling = false) => { try { const limit = 10; const offset = page * limit; diff --git a/assets/js/QortalApi.js b/assets/js/QortalApi.js index 1c8f6f3..17ba714 100644 --- a/assets/js/QortalApi.js +++ b/assets/js/QortalApi.js @@ -130,17 +130,31 @@ const verifyUserIsAdmin = async () => { try { const accountAddress = userState.accountAddress || await getUserAddress(); userState.accountAddress = accountAddress; + const userGroups = await getUserGroups(accountAddress); + console.log('userGroups:', userGroups); + const minterGroupAdmins = await fetchMinterGroupAdmins(); - const isAdmin = await userGroups.some(group => adminGroups.includes(group.groupName)) - const isMinterAdmin = minterGroupAdmins.members.some(admin => admin.member === userState.accountAddress && admin.isAdmin) + console.log('minterGroupAdmins.members:', minterGroupAdmins); + + if (!Array.isArray(userGroups)) { + throw new Error('userGroups is not an array or is undefined'); + } + + if (!Array.isArray(minterGroupAdmins)) { + throw new Error('minterGroupAdmins.members is not an array or is undefined'); + } + + const isAdmin = userGroups.some(group => adminGroups.includes(group.groupName)); + const isMinterAdmin = minterGroupAdmins.some(admin => admin.member === userState.accountAddress && admin.isAdmin); + if (isMinterAdmin) { - userState.isMinterAdmin = true + userState.isMinterAdmin = true; } if (isAdmin) { userState.isAdmin = true; - userState.isForumAdmin = true - } + userState.isForumAdmin = true; + } return userState.isAdmin; } catch (error) { console.error('Error verifying user admin status:', error); @@ -148,6 +162,7 @@ const verifyUserIsAdmin = async () => { } }; + const verifyAddressIsAdmin = async (address) => { console.log('verifyAddressIsAdmin called'); console.log('address:', address); @@ -234,8 +249,7 @@ const getAddressFromPublicKey = async (publicKey) => { method: 'GET', headers: { 'Accept': 'text/plain' } }); - const data = await response.text(); - const address = data; + const address = await response.text(); console.log('Converted Address:', address); return address; } catch (error) { @@ -290,7 +304,7 @@ try { }; -// QORTAL GROUP-RELATED CALLS ------------------------------------------ +// QORTAL GROUP-RELATED CALLS ------------------------------------------------------------------------------------ const getUserGroups = async (userAddress) => { console.log('getUserGroups called'); console.log('userAddress:', userAddress); @@ -319,8 +333,15 @@ const fetchMinterGroupAdmins = async () => { headers: { 'Accept': 'application/json' } }); const admins = await response.json(); + + if (!Array.isArray(admins.members)) { + throw new Error("Expected 'members' to be an array but got a different structure"); + } + + const adminMembers = admins.members console.log('Fetched minter admins', admins); - return admins; + return adminMembers; + //use what is returned .member to obtain each member... {"member": "memberAddress", "isAdmin": "true"} } const fetchMinterGroupMembers = async () => { @@ -340,16 +361,17 @@ const fetchMinterGroupMembers = async () => { if (!Array.isArray(data.members)) { throw new Error("Expected 'members' to be an array but got a different structure"); } - - return data.members; // Assuming 'members' is the key in the response JSON + + console.log(`MinterGroupMembers have been fetched.`) + return data.members; + + //use what is returned .member to obtain each member... {"member": "memberAddress", "joined": "{timestamp}"} } catch (error) { console.error("Error fetching minter group members:", error); return []; // Return an empty array to prevent further errors } }; - - const fetchAllGroups = async (limit) => { console.log('fetchAllGroups called'); diff --git a/index.html b/index.html index 85933e3..729e581 100644 --- a/index.html +++ b/index.html @@ -229,7 +229,7 @@
- +