massive modifications to both MinterBoard and forum. There are still many changes to come, but these needed to be pushed to resolve important bugs on MinterBoard for testing
This commit is contained in:
parent
f0a2a3ea58
commit
a5bf12aed2
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 = "<p>Loading cards...</p>";
|
||||
|
||||
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 = "<p>No cards found.</p>";
|
||||
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 = "<p>No valid cards found.</p>";
|
||||
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 = "<p>Failed to load cards.</p>";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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 = "<p>Loading cards...</p>";
|
||||
//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 = "<p>No cards found.</p>";
|
||||
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 = "<p>Failed to load cards.</p>";
|
||||
}
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div class="comment" style="border: 1px solid gray; margin: 1vh 0; padding: 1vh; background: #1c1c1c;">
|
||||
<p><strong><u>${commentDataResponse.creator}</strong>:</p></u>
|
||||
@ -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 = `
|
||||
<div id="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 1000;">
|
||||
<div style="position: relative; margin: 5% auto; width: 90%; height: 90%; background: white; border-radius: 10px; overflow: hidden;">
|
||||
<iframe id="modalContent" src="" style="width: 100%; height: 100%; border: none;"></iframe>
|
||||
<button onclick="closeModal()" style="position: absolute; top: 10px; right: 10px; background: red; color: white; border: none; padding: 5px 10px; border-radius: 5px;">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
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) => `
|
||||
<button onclick="window.open('${link}', '_blank')">
|
||||
<button onclick="openModal('${link}')">
|
||||
${`Link ${index + 1} - ${link}`}
|
||||
</button>
|
||||
`).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 `
|
||||
<div class="minter-card">
|
||||
<div class="minter-card-header">
|
||||
|
@ -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) {
|
||||
<div id="editor" class="message-input"></div>
|
||||
<div class="attachment-section">
|
||||
<input type="file" id="file-input" class="file-input" multiple>
|
||||
<button id="attach-button" class="attach-button">Attach Files</button>
|
||||
<label for="file-input" class="custom-file-input-button">Select Files</label>
|
||||
<input type="file" id="image-input" class="image-input" multiple accept="image/*">
|
||||
<label for="image-input" class="custom-image-input-button">Select IMAGES w/Preview</label>
|
||||
<button id="add-images-to-publish-button" disabled>Add Images to Multi-Publish</button>
|
||||
<div id="preview-container" style="display: flex; flex-wrap: wrap; gap: 10px;"></div>
|
||||
</div>
|
||||
<button id="send-button" class="send-button">Send</button>
|
||||
<button id="send-button" class="send-button">Publish</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -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 = '<button id="load-more-button" class="load-more-button" style="margin-top: 10px;">Load More</button>';
|
||||
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;
|
||||
|
@ -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');
|
||||
|
@ -229,7 +229,7 @@
|
||||
</div>
|
||||
|
||||
<a class="link-wrap" href="#">
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.24beta</p>
|
||||
<p class="mbr-link mbr-fonts-style display-4">Q-Mintership v0.25beta</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
|
Loading…
Reference in New Issue
Block a user