Massive re-factor to forum code (at least the first half)

This commit is contained in:
crowetic 2024-12-12 21:49:14 -08:00
parent 24e517b299
commit f500846975
3 changed files with 278 additions and 291 deletions

View File

@ -384,6 +384,16 @@
background-color: rgba(0, 0, 0, 0.8); /* Black w/ opacity */
}
.remove-image-button {
background-color: #0f4c41;
color: #ffffff;
border: dotted;
border-radius: 1vh;
padding: 0.3vh 0.6vh;
margin-top: 1px;
cursor: pointer;
}
.modal-content {
margin: auto;
display: block;

View File

@ -494,7 +494,7 @@ const toggleComments = async (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;">
<div style="position: relative; margin: 10% auto; width: 95%; height: 80%; 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>
@ -522,9 +522,16 @@ const closeModal = async () => {
const processLink = async (link) => {
if (link.startsWith('qortal://')) {
return link.replace('qortal://', '/render/')
const match = link.match(/^qortal:\/\/([^/]+)(\/.*)?$/);
if (match) {
const firstParam = match[1].toUpperCase(); // Convert to uppercase
const remainingPath = match[2] || ""; // Rest of the URL
// Perform any asynchronous operation if necessary
await new Promise(resolve => setTimeout(resolve, 10)); // Simulating async operation
return `http://localhost:12391/render/${firstParam}${remainingPath}`;
}
return link // Return the link unchanged if it doesn't start with `qortal://`
}
return link; // Return unchanged if not a Qortal link
}

View File

@ -137,7 +137,13 @@ const renderPaginationControls = async(room, totalMessages, limit) => {
// 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) {
if (!forumContent) {
console.error("Forum content container not found!");
return;
}
// Set initial content
forumContent.innerHTML = `
<div class="room-content">
<h3 class="room-title" style="color: lightblue;">${room.charAt(0).toUpperCase() + room.slice(1)} Room</h3>
@ -159,86 +165,90 @@ const loadRoomContent = async (room) => {
</div>
`;
const imageModalHTML = `
// Add modal for image preview
forumContent.insertAdjacentHTML(
'beforeend',
`
<div id="image-modal" class="image-modal">
<span id="close-modal" class="close">&times;</span>
<img id="modal-image" class="modal-content">
<div id="caption" class="caption"></div>
<button id="download-button" class="download-button">Download</button>
</div>
`;
forumContent.insertAdjacentHTML('beforeend', imageModalHTML);
`);
// Initialize Quill editor for rich text input
const quill = new Quill('#editor', {
initializeQuillEditor();
setupModalHandlers();
setupFileInputs(room);
await loadMessagesFromQDN(room, currentPage);
};
// Initialize Quill editor
const initializeQuillEditor = () => {
new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: [
[{ 'font': [] }], // Add font family options
[{ 'size': ['small', false, 'large', 'huge'] }], // Add font size options
[{ 'font': [] }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, false] }],
['bold', 'italic', 'underline'], // Text formatting options
['bold', 'italic', 'underline'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link', 'blockquote', 'code-block'],
[{ 'color': [] }, { 'background': [] }], // Text color and background color options
[{ 'align': [] }], // Text alignment
['clean'] // Remove formatting button
[{ 'color': [] }, { 'background': [] }],
[{ 'align': [] }],
['clean']
]
}
});
};
// Load messages from QDN for the selected room
await loadMessagesFromQDN(room, currentPage);
document.addEventListener("click", async (event) => {
// Set up modal behavior
const setupModalHandlers = () => {
document.addEventListener("click", (event) => {
if (event.target.classList.contains("inline-image")) {
const modal = document.getElementById("image-modal");
const modalImage = document.getElementById("modal-image");
const caption = document.getElementById("caption");
// const downloadButton = document.getElementById("download-button");
// Set the modal content
modalImage.src = event.target.src;
caption.textContent = event.target.alt;
// Show the modal
modal.style.display = "block";
}
});
// Close the modal
document.getElementById("close-modal").addEventListener("click", async () => {
document.getElementById("close-modal").addEventListener("click", () => {
document.getElementById("image-modal").style.display = "none";
});
// Hide the modal when clicking outside of the image or close button
window.addEventListener("click", async (event) => {
window.addEventListener("click", (event) => {
const modal = document.getElementById("image-modal");
if (!event.target == modal) {
if (event.target === modal) {
modal.style.display = "none";
}
});
};
let selectedImages = [];
let selectedFiles = [];
let multiResource = [];
let attachmentIdentifiers = [];
let selectedFiles = [];
let selectedImages = [];
let attachmentIdentifiers = [];
let multiResource = []
// Set up file input handling
const setupFileInputs = (room) => {
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}`;
const addToPublishButton = document.getElementById('add-images-to-publish-button');
const fileInput = document.getElementById('file-input');
const sendButton = document.getElementById('send-button');
imageFileInput.addEventListener('change', async (event) => {
// Clear previous previews to prepare for preview generation
const attachmentID = generateAttachmentID(room);
imageFileInput.addEventListener('change', (event) => {
previewContainer.innerHTML = '';
selectedImages = Array.from(event.target.files);
selectedImages = [...event.target.files];
if (selectedImages.length > 0) {
addToPublishButton.disabled = false;
}
addToPublishButton.disabled = selectedImages.length === 0;
selectedImages.forEach((file, index) => {
const reader = new FileReader();
@ -246,161 +256,107 @@ const loadRoomContent = async (room) => {
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';
img.style = "width: 100px; height: 100px; object-fit: cover; border: 1px solid #ccc; border-radius: 5px;";
// Add remove button
const removeButton = document.createElement('button');
removeButton.innerText = 'Remove';
removeButton.style.marginTop = '5px';
removeButton.classList.add('remove-image-button');
removeButton.onclick = () => {
selectedImages.splice(index, 1);
img.remove();
removeButton.remove();
if (selectedImages.length === 0) {
addToPublishButton.disabled = true;
}
addToPublishButton.disabled = selectedImages.length === 0;
};
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);
container.style = "display: flex; flex-direction: column; align-items: center; margin: 5px;";
container.append(img, removeButton);
previewContainer.append(container);
};
reader.readAsDataURL(file);
});
});
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,
addToPublishButton.addEventListener('click', () => {
processSelectedImages(selectedImages, multiResource, room);
selectedImages = [];
addToPublishButton.disabled = true;
});
attachmentIdentifiers.push({
name: userState.accountName,
service: "FILE",
identifier: attachmentID,
filename: file.name,
mimeType: file.type
fileInput.addEventListener('change', (event) => {
selectedFiles = [...event.target.files];
});
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 () => {
sendButton.addEventListener('click', async () => {
const quill = new Quill('#editor');
const messageHtml = quill.root.innerHTML.trim();
if (messageHtml !== "" || selectedFiles.length > 0 || selectedImages.length > 0) {
const messageIdentifier = `${messageIdentifierPrefix}-${room}-${randomID}`;
if (selectedImages.length > 0) {
await addImagesToMultiPublish()
if (messageHtml || selectedFiles.length > 0 || selectedImages.length > 0) {
await handleSendMessage(room, messageHtml, selectedFiles, selectedImages, multiResource);
}
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
})
// Process selected images
const processSelectedImages = async (selectedImages, multiResource, room) => {
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];
for (const file of selectedImages) {
let attachmentID = generateAttachmentID(room, selectedImages.indexOf(file))
try {
multiResource.push({
name: userState.accountName,
service: "FILE",
identifier: attachmentID,
file: 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);
console.error(`Error processing image ${file.name}:`, error);
}
}
};
// Handle send message
const handleSendMessage = async (room, messageHtml, selectedFiles, selectedImages, multiResource) => {
const messageIdentifier = `${messageIdentifierPrefix}-${room}-${Date.now()}`;
try {
if (selectedImages.length > 0) {
await processSelectedImages(selectedImages, multiResource, room);
}
for (const file of selectedFiles) {
let attachmentID = generateAttachmentID(room, selectedFiles.indexOf(file))
multiResource.push({
name: userState.accountName,
service: "FILE",
identifier: attachmentID,
file
});
attachmentIdentifiers.push({
name: userState.accountName,
service: "FILE",
identifier: attachmentID,
filename: file.name,
mimeType: file.type
})
}
// Create message object with unique identifier, HTML content, and attachments
const messageObject = {
messageHtml: messageHtml,
hasAttachment: attachmentIdentifiers.length > 0,
messageHtml,
hasAttachment: multiResource.length > 0,
attachments: attachmentIdentifiers,
replyTo: replyToMessageIdentifier
replyTo: replyToMessageIdentifier || null // Add replyTo
};
try {
// Convert message object to base64
let base64Message = await objectToBase64(messageObject);
if (!base64Message) {
console.log(`initial object creation with object failed, using btoa...`);
base64Message = btoa(JSON.stringify(messageObject));
}
const base64Message = btoa(JSON.stringify(messageObject));
// Put the message into the multiResource for batch-publishing.
multiResource.push({
name: userState.accountName,
service: "BLOG_POST",
@ -408,25 +364,38 @@ const loadRoomContent = async (room) => {
data64: base64Message
});
console.log("Message added to multi-publish resource successfully, attempting multi-publish... ");
await publishMultipleResources(multiResource);
await publishMultipleResources(multiResource)
clearInputs();
showSuccessNotification();
} catch (error) {
console.error("Error sending message:", error);
}
};
// Clear the editor after sending the message, including any potential attached files and replies.
// Modify clearInputs to reset replyTo
const clearInputs = () => {
const quill = new Quill('#editor');
quill.root.innerHTML = "";
document.getElementById('file-input').value = "";
selectedFiles = [];
selectedImages = [];
multiResource = [];
document.getElementById('image-input').value = "";
document.getElementById('preview-container').innerHTML = "";
replyToMessageIdentifier = null;
multiResource = [];
attachmentIdentifiers = [];
selectedImages = []
selectedFiles = []
const replyContainer = document.querySelector(".reply-container");
if (replyContainer) {
replyContainer.remove()
replyContainer.remove();
}
};
// Show success notification
// Show success notification
const showSuccessNotification = () => {
const notification = document.createElement('div');
notification.innerText = "Message published successfully! Message will take a confirmation to show, please be patient...";
notification.innerText = "Message published successfully! Please wait for confirmation.";
notification.style.color = "green";
notification.style.marginTop = "1em";
document.querySelector(".message-input-section").appendChild(notification);
@ -434,14 +403,16 @@ const loadRoomContent = async (room) => {
setTimeout(() => {
notification.remove();
}, 10000);
};
// Generate unique attachment ID
const generateAttachmentID = (room, fileIndex = null) => {
const baseID = `${messageAttachmentIdentifierPrefix}-${room}-${Date.now()}`;
return fileIndex !== null ? `${baseID}-${fileIndex}` : baseID;
};
} catch (error) {
console.error("Error publishing message:", error);
}
}
})
}
}
const loadMessagesFromQDN = async (room, page, isPolling = false) => {
try {
@ -534,17 +505,17 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
let attachmentHtml = "";
if (message.attachments && message.attachments.length > 0) {
for (const attachment of message.attachments) {
if (attachment.mimeType.startsWith('image/')) {
if (attachment.mimeType && attachment.mimeType.startsWith('image/')) {
try {
// OTHER METHOD NOT BEING USED HERE. WE CAN LOAD THE IMAGE DIRECTLY SINCE IT WILL BE PUBLISHED UNENCRYPTED/UNENCODED.
// const imageHtml = await loadImageHtml(attachment.service, attachment.name, attachment.identifier, attachment.filename, attachment.mimeType);
// Construct the image URL
const imageUrl = `/arbitrary/${attachment.service}/${attachment.name}/${attachment.identifier}`;
// Add the image HTML with the direct URL
attachmentHtml += `<div class="attachment">
<img src="${imageUrl}" alt="${attachment.filename}" class="inline-image"/>
</div>`;
// Add the modal download button details as well, in order to pass correct information to the modal
// Set up the modal download button
const downloadButton = document.getElementById("download-button");
downloadButton.onclick = () => {
fetchAndSaveAttachment(
@ -555,13 +526,11 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
attachment.mimeType
);
};
// FOR OTHER METHOD NO LONGER USED
// attachmentHtml += imageHtml;
} catch (error) {
console.error(`Failed to fetch attachment ${attachment.filename}:`, error);
}
} else {
// Display a button to download other attachments
// Display a button to download non-image attachments
attachmentHtml += `<div class="attachment">
<button onclick="fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')">Download ${attachment.filename}</button>
</div>`;
@ -569,6 +538,7 @@ const loadMessagesFromQDN = async (room, page, isPolling = false) => {
}
}
const avatarUrl = `/arbitrary/THUMBNAIL/${message.name}/qortal_avatar`;
const messageHTML = `
<div class="message-item" data-identifier="${message.identifier}">