2024-12-11 22:40:32 +00:00
const messageIdentifierPrefix = ` mintership-forum-message ` ;
const messageAttachmentIdentifierPrefix = ` mintership-forum-attachment ` ;
2024-12-15 03:40:31 +00:00
let adminPublicKeys = [ ]
2024-12-11 22:40:32 +00:00
// NOTE - SET adminGroups in QortalApi.js to enable admin access to forum for specific groups. Minter Admins will be fetched automatically.
let replyToMessageIdentifier = null ;
let latestMessageIdentifiers = { } ; // To keep track of the latest message in each room
let currentPage = 0 ; // Track current pagination page
let existingIdentifiers = new Set ( ) ; // Keep track of existing identifiers to not pull them more than once.
// If there is a previous latest message identifiers, use them. Otherwise, use an empty.
if ( localStorage . getItem ( "latestMessageIdentifiers" ) ) {
latestMessageIdentifiers = JSON . parse ( localStorage . getItem ( "latestMessageIdentifiers" ) ) ;
}
document . addEventListener ( "DOMContentLoaded" , async ( ) => {
2024-12-18 06:24:40 +00:00
console . log ( "DOMContentLoaded fired!" ) ;
2024-12-11 22:40:32 +00:00
2024-12-18 06:24:40 +00:00
// --- GENERAL LINKS (MINTERSHIP-FORUM and MINTER-BOARD) ---
const mintershipForumLinks = document . querySelectorAll ( 'a[href="MINTERSHIP-FORUM"]' ) ;
2024-12-11 22:40:32 +00:00
mintershipForumLinks . forEach ( link => {
link . addEventListener ( 'click' , async ( event ) => {
event . preventDefault ( ) ;
if ( ! userState . isLoggedIn ) {
await login ( ) ;
}
await loadForumPage ( ) ;
2024-12-18 06:24:40 +00:00
loadRoomContent ( "general" ) ;
startPollingForNewMessages ( ) ;
} ) ;
} ) ;
const minterBoardLinks = document . querySelectorAll ( 'a[href="MINTER-BOARD"], a[href="MINTERS"]' ) ;
minterBoardLinks . forEach ( link => {
link . addEventListener ( "click" , async ( event ) => {
event . preventDefault ( ) ;
if ( ! userState . isLoggedIn ) {
await login ( ) ;
}
if ( typeof loadMinterBoardPage === "undefined" ) {
console . log ( "loadMinterBoardPage not found, loading script dynamically..." ) ;
await loadScript ( "./assets/js/MinterBoard.js" ) ;
}
await loadMinterBoardPage ( ) ;
2024-12-11 22:40:32 +00:00
} ) ;
} ) ;
2024-12-18 06:24:40 +00:00
// --- ADMIN CHECK ---
await verifyUserIsAdmin ( ) ;
if ( userState . isAdmin ) {
console . log ( ` User is an Admin. Admin-specific buttons will remain visible. ` ) ;
// DATA-BOARD Links for Admins
const minterDataBoardLinks = document . querySelectorAll ( 'a[href="ADMINBOARD"]' ) ;
minterDataBoardLinks . forEach ( link => {
link . addEventListener ( "click" , async ( event ) => {
event . preventDefault ( ) ;
if ( ! userState . isLoggedIn ) {
await login ( ) ;
}
if ( typeof loadAdminBoardPage === "undefined" ) {
console . log ( "loadAdminBoardPage function not found, loading script dynamically..." ) ;
await loadScript ( "./assets/js/AdminBoard.js" ) ;
}
await loadAdminBoardPage ( ) ;
} ) ;
} ) ;
// TOOLS Links for Admins
const toolsLinks = document . querySelectorAll ( 'a[href="TOOLS"]' ) ;
toolsLinks . forEach ( link => {
link . addEventListener ( 'click' , async ( event ) => {
event . preventDefault ( ) ;
if ( ! userState . isLoggedIn ) {
await login ( ) ;
}
if ( typeof loadMinterAdminToolsPage === "undefined" ) {
console . log ( "loadMinterAdminToolsPage function not found, loading script dynamically..." ) ;
await loadScript ( "./assets/js/AdminTools.js" ) ;
}
await loadMinterAdminToolsPage ( ) ;
} ) ;
} ) ;
} else {
console . log ( "User is NOT an Admin. Removing admin-specific links." ) ;
// Remove all admin-specific links and their parents
2024-12-19 01:33:09 +00:00
const toolsLinks = document . querySelectorAll ( 'a[href="TOOLS"], a[href="ADMINBOARD"]' ) ;
2024-12-18 06:24:40 +00:00
toolsLinks . forEach ( link => {
const buttonParent = link . closest ( 'button' ) ;
if ( buttonParent ) buttonParent . remove ( ) ;
const cardParent = link . closest ( '.item.features-image' ) ;
if ( cardParent ) cardParent . remove ( ) ;
link . remove ( ) ;
} ) ;
// Center the remaining card if it exists
const remainingCard = document . querySelector ( '.features7 .row .item.features-image' ) ;
if ( remainingCard ) {
remainingCard . classList . remove ( 'col-lg-6' , 'col-md-6' ) ;
remainingCard . classList . add ( 'col-12' , 'text-center' ) ;
}
}
console . log ( "All DOMContentLoaded tasks completed." ) ;
2024-12-11 22:40:32 +00:00
} ) ;
2024-12-18 06:24:40 +00:00
async function loadScript ( src ) {
return new Promise ( ( resolve , reject ) => {
const script = document . createElement ( "script" ) ;
script . src = src ;
script . onload = resolve ;
script . onerror = reject ;
document . head . appendChild ( script ) ;
} ) ;
}
2024-12-13 01:23:34 +00:00
// 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.
2024-12-11 22:40:32 +00:00
const bodyChildren = document . body . children ;
for ( let i = bodyChildren . length - 1 ; i >= 0 ; i -- ) {
const child = bodyChildren [ i ] ;
if ( ! child . classList . contains ( 'menu' ) ) {
child . remove ( ) ;
}
}
2024-12-15 03:40:31 +00:00
if ( typeof userState . isAdmin === 'undefined' ) {
try {
// Fetch and verify the admin status asynchronously
userState . isAdmin = await verifyUserIsAdmin ( ) ;
} catch ( error ) {
console . error ( 'Error verifying admin status:' , error ) ;
userState . isAdmin = false ; // Default to non-admin if there's an issue
}
}
2024-12-11 22:40:32 +00:00
const avatarUrl = ` /arbitrary/THUMBNAIL/ ${ userState . accountName } /qortal_avatar ` ;
2024-12-15 03:40:31 +00:00
const isAdmin = userState . isAdmin ;
2024-12-11 22:40:32 +00:00
2024-12-15 03:40:31 +00:00
// Create the forum layout, including a header, sub-menu, and keeping the original background image: style="background-image: url('/assets/images/background.jpg');">
2024-12-11 22:40:32 +00:00
const mainContent = document . createElement ( 'div' ) ;
mainContent . innerHTML = `
< div class = "forum-main mbr-parallax-background cid-ttRnlSkg2R" >
< div class = "forum-header" style = "color: lightblue; display: flex; justify-content: center; align-items: center; padding: 10px;" >
2024-12-15 03:40:31 +00:00
< div class = "user-info" style = "border: 1px solid lightblue; padding: 5px; color: white; display: flex; align-items: center; justify-content: center;" >
2024-12-11 22:40:32 +00:00
< img src = "${avatarUrl}" alt = "User Avatar" class = "user-avatar" style = "width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;" >
< span > $ { userState . accountName || 'Guest' } < / s p a n >
< / d i v >
< / d i v >
< div class = "forum-submenu" >
< div class = "forum-rooms" >
< button class = "room-button" id = "minters-room" > Minters Room < / b u t t o n >
2024-12-15 03:40:31 +00:00
$ { isAdmin ? '<button class="room-button" id="admins-room">Admins Room</button>' : '' }
2024-12-11 22:40:32 +00:00
< button class = "room-button" id = "general-room" > General Room < / b u t t o n >
< / d i v >
< / d i v >
< div id = "forum-content" class = "forum-content" > < / d i v >
< / d i v >
` ;
document . body . appendChild ( mainContent ) ;
// Add event listeners to room buttons
document . getElementById ( "minters-room" ) . addEventListener ( "click" , ( ) => {
currentPage = 0 ;
loadRoomContent ( "minters" ) ;
} ) ;
if ( userState . isAdmin ) {
document . getElementById ( "admins-room" ) . addEventListener ( "click" , ( ) => {
currentPage = 0 ;
loadRoomContent ( "admins" ) ;
} ) ;
}
document . getElementById ( "general-room" ) . addEventListener ( "click" , ( ) => {
currentPage = 0 ;
loadRoomContent ( "general" ) ;
} ) ;
}
2024-12-13 01:23:34 +00:00
// Function to add the pagination buttons and related control mechanisms ------------------------
2024-12-15 03:40:31 +00:00
const renderPaginationControls = ( room , totalMessages , limit ) => {
2024-12-11 22:40:32 +00:00
const paginationContainer = document . getElementById ( "pagination-container" ) ;
if ( ! paginationContainer ) return ;
paginationContainer . innerHTML = "" ; // Clear existing buttons
const totalPages = Math . ceil ( totalMessages / limit ) ;
// Add "Previous" button
if ( currentPage > 0 ) {
const prevButton = document . createElement ( "button" ) ;
prevButton . innerText = "Previous" ;
prevButton . addEventListener ( "click" , ( ) => {
if ( currentPage > 0 ) {
currentPage -- ;
loadMessagesFromQDN ( room , currentPage , false ) ;
}
} ) ;
paginationContainer . appendChild ( prevButton ) ;
}
// Add numbered page buttons
for ( let i = 0 ; i < totalPages ; i ++ ) {
const pageButton = document . createElement ( "button" ) ;
pageButton . innerText = i + 1 ;
pageButton . className = i === currentPage ? "active-page" : "" ;
pageButton . addEventListener ( "click" , ( ) => {
if ( i !== currentPage ) {
currentPage = i ;
loadMessagesFromQDN ( room , currentPage , false ) ;
}
} ) ;
paginationContainer . appendChild ( pageButton ) ;
}
// Add "Next" button
if ( currentPage < totalPages - 1 ) {
const nextButton = document . createElement ( "button" ) ;
nextButton . innerText = "Next" ;
nextButton . addEventListener ( "click" , ( ) => {
if ( currentPage < totalPages - 1 ) {
currentPage ++ ;
loadMessagesFromQDN ( room , currentPage , false ) ;
}
} ) ;
paginationContainer . appendChild ( nextButton ) ;
}
}
2024-12-13 01:23:34 +00:00
// Main function to load the full content of the room, along with all main functionality -----------------------------------
const loadRoomContent = async ( room ) => {
2024-12-11 22:40:32 +00:00
const forumContent = document . getElementById ( "forum-content" ) ;
2024-12-13 05:49:14 +00:00
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 < / h 3 >
< div id = "messages-container" class = "messages-container" > < / d i v >
< div id = "pagination-container" class = "pagination-container" style = "margin-top: 20px; text-align: center;" > < / d i v >
< div class = "message-input-section" >
< div id = "toolbar" class = "message-toolbar" > < / d i v >
< div id = "editor" class = "message-input" > < / d i v >
< div class = "attachment-section" >
< input type = "file" id = "file-input" class = "file-input" multiple >
< label for = "file-input" class = "custom-file-input-button" > Select Files < / l a b e l >
< 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 < / l a b e l >
< button id = "add-images-to-publish-button" disabled > Add Images to Multi - Publish < / b u t t o n >
< div id = "preview-container" style = "display: flex; flex-wrap: wrap; gap: 10px;" > < / d i v >
2024-12-11 22:40:32 +00:00
< / d i v >
2024-12-13 05:49:14 +00:00
< button id = "send-button" class = "send-button" > Publish < / b u t t o n >
2024-12-11 22:40:32 +00:00
< / d i v >
2024-12-13 05:49:14 +00:00
< / d i v >
` ;
2024-12-11 22:40:32 +00:00
2024-12-13 05:49:14 +00:00
// Add modal for image preview
forumContent . insertAdjacentHTML (
'beforeend' ,
`
< div id = "image-modal" class = "image-modal" >
< span id = "close-modal" class = "close" > & times ; < / s p a n >
< img id = "modal-image" class = "modal-content" >
< div id = "caption" class = "caption" > < / d i v >
< button id = "download-button" class = "download-button" > Download < / b u t t o n >
< / d i v >
` );
initializeQuillEditor ( ) ;
setupModalHandlers ( ) ;
setupFileInputs ( room ) ;
await loadMessagesFromQDN ( room , currentPage ) ;
} ;
// Initialize Quill editor
const initializeQuillEditor = ( ) => {
new Quill ( '#editor' , {
theme : 'snow' ,
modules : {
toolbar : [
[ { 'font' : [ ] } ] ,
[ { 'size' : [ 'small' , false , 'large' , 'huge' ] } ] ,
[ { 'header' : [ 1 , 2 , false ] } ] ,
[ 'bold' , 'italic' , 'underline' ] ,
[ { 'list' : 'ordered' } , { 'list' : 'bullet' } ] ,
[ 'link' , 'blockquote' , 'code-block' ] ,
[ { 'color' : [ ] } , { 'background' : [ ] } ] ,
[ { 'align' : [ ] } ] ,
[ 'clean' ]
]
}
} ) ;
} ;
// Set up modal behavior
const setupModalHandlers = ( ) => {
document . addEventListener ( "click" , ( event ) => {
if ( event . target . classList . contains ( "inline-image" ) ) {
2024-12-11 22:40:32 +00:00
const modal = document . getElementById ( "image-modal" ) ;
2024-12-13 05:49:14 +00:00
const modalImage = document . getElementById ( "modal-image" ) ;
const caption = document . getElementById ( "caption" ) ;
modalImage . src = event . target . src ;
caption . textContent = event . target . alt ;
modal . style . display = "block" ;
}
} ) ;
document . getElementById ( "close-modal" ) . addEventListener ( "click" , ( ) => {
document . getElementById ( "image-modal" ) . style . display = "none" ;
} ) ;
window . addEventListener ( "click" , ( event ) => {
const modal = document . getElementById ( "image-modal" ) ;
if ( event . target === modal ) {
modal . style . display = "none" ;
}
} ) ;
} ;
let selectedImages = [ ] ;
let selectedFiles = [ ] ;
let multiResource = [ ] ;
let attachmentIdentifiers = [ ] ;
// 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 fileInput = document . getElementById ( 'file-input' ) ;
const sendButton = document . getElementById ( 'send-button' ) ;
const attachmentID = generateAttachmentID ( room ) ;
imageFileInput . addEventListener ( 'change' , ( event ) => {
previewContainer . innerHTML = '' ;
selectedImages = [ ... event . target . files ] ;
addToPublishButton . disabled = selectedImages . length === 0 ;
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; height: 100px; object-fit: cover; border: 1px solid #ccc; border-radius: 5px;" ;
const removeButton = document . createElement ( 'button' ) ;
removeButton . innerText = 'Remove' ;
removeButton . classList . add ( 'remove-image-button' ) ;
removeButton . onclick = ( ) => {
selectedImages . splice ( index , 1 ) ;
img . remove ( ) ;
removeButton . remove ( ) ;
addToPublishButton . disabled = selectedImages . length === 0 ;
} ;
const container = document . createElement ( 'div' ) ;
container . style = "display: flex; flex-direction: column; align-items: center; margin: 5px;" ;
container . append ( img , removeButton ) ;
previewContainer . append ( container ) ;
} ;
reader . readAsDataURL ( file ) ;
2024-12-11 22:40:32 +00:00
} ) ;
2024-12-13 05:49:14 +00:00
} ) ;
2024-12-11 22:40:32 +00:00
2024-12-13 05:49:14 +00:00
addToPublishButton . addEventListener ( 'click' , ( ) => {
processSelectedImages ( selectedImages , multiResource , room ) ;
selectedImages = [ ] ;
addToPublishButton . disabled = true ;
} ) ;
2024-12-13 01:23:34 +00:00
2024-12-13 05:49:14 +00:00
fileInput . addEventListener ( 'change' , ( event ) => {
selectedFiles = [ ... event . target . files ] ;
} ) ;
2024-12-13 01:23:34 +00:00
2024-12-13 05:49:14 +00:00
sendButton . addEventListener ( 'click' , async ( ) => {
const quill = new Quill ( '#editor' ) ;
const messageHtml = quill . root . innerHTML . trim ( ) ;
2024-12-13 01:23:34 +00:00
2024-12-13 05:49:14 +00:00
if ( messageHtml || selectedFiles . length > 0 || selectedImages . length > 0 ) {
await handleSendMessage ( room , messageHtml , selectedFiles , selectedImages , multiResource ) ;
}
} ) ;
} ;
2024-12-13 01:23:34 +00:00
2024-12-13 05:49:14 +00:00
// Process selected images
const processSelectedImages = async ( selectedImages , multiResource , room ) => {
for ( const file of selectedImages ) {
2024-12-15 03:40:31 +00:00
const attachmentID = generateAttachmentID ( room , selectedImages . indexOf ( file ) ) ;
multiResource . push ( {
name : userState . accountName ,
service : room === "admins" ? "FILE_PRIVATE" : "FILE" ,
identifier : attachmentID ,
file : file , // Use encrypted file for admins
} ) ;
attachmentIdentifiers . push ( {
name : userState . accountName ,
service : room === "admins" ? "FILE_PRIVATE" : "FILE" ,
identifier : attachmentID ,
filename : file . name ,
mimeType : file . type ,
} ) ;
2024-12-13 05:49:14 +00:00
}
} ;
2024-12-13 01:23:34 +00:00
2024-12-13 05:49:14 +00:00
// Handle send message
const handleSendMessage = async ( room , messageHtml , selectedFiles , selectedImages , multiResource ) => {
2024-12-15 03:40:31 +00:00
const messageIdentifier = room === "admins"
? ` ${ messageIdentifierPrefix } - ${ room } -e- ${ Date . now ( ) } `
: ` ${ messageIdentifierPrefix } - ${ room } - ${ Date . now ( ) } ` ;
const adminPublicKeys = room === "admins" && userState . isAdmin
? await fetchAdminGroupsMembersPublicKeys ( )
: [ ] ;
2024-12-13 05:49:14 +00:00
try {
2024-12-15 03:40:31 +00:00
// Process selected images
2024-12-13 05:49:14 +00:00
if ( selectedImages . length > 0 ) {
await processSelectedImages ( selectedImages , multiResource , room ) ;
2024-12-13 01:23:34 +00:00
}
2024-12-15 03:40:31 +00:00
// Process selected files
if ( selectedFiles && selectedFiles . length > 0 ) {
for ( const file of selectedFiles ) {
const attachmentID = generateAttachmentID ( room , selectedFiles . indexOf ( file ) ) ;
multiResource . push ( {
name : userState . accountName ,
service : room === "admins" ? "FILE_PRIVATE" : "FILE" ,
identifier : attachmentID ,
file : file , // Use encrypted file for admins
} ) ;
attachmentIdentifiers . push ( {
name : userState . accountName ,
service : room === "admins" ? "FILE_PRIVATE" : "FILE" ,
identifier : attachmentID ,
filename : file . name ,
mimeType : file . type ,
} ) ;
}
2024-12-13 05:49:14 +00:00
}
2024-12-11 22:40:32 +00:00
2024-12-15 03:40:31 +00:00
// Build the message object
2024-12-13 05:49:14 +00:00
const messageObject = {
messageHtml ,
hasAttachment : multiResource . length > 0 ,
attachments : attachmentIdentifiers ,
2024-12-15 03:40:31 +00:00
replyTo : replyToMessageIdentifier || null , // Include replyTo if applicable
2024-12-13 05:49:14 +00:00
} ;
2024-12-11 22:40:32 +00:00
2024-12-15 03:40:31 +00:00
// Encode the message object
let base64Message = await objectToBase64 ( messageObject ) ;
if ( ! base64Message ) {
base64Message = btoa ( JSON . stringify ( messageObject ) ) ;
}
2024-12-11 22:40:32 +00:00
2024-12-15 03:40:31 +00:00
if ( room === "admins" && userState . isAdmin ) {
console . log ( "Encrypting message for admins..." ) ;
multiResource . push ( {
name : userState . accountName ,
service : "MAIL_PRIVATE" ,
identifier : messageIdentifier ,
data64 : base64Message ,
} ) ;
} else {
multiResource . push ( {
name : userState . accountName ,
service : "BLOG_POST" ,
identifier : messageIdentifier ,
data64 : base64Message ,
} ) ;
}
2024-12-13 05:49:14 +00:00
2024-12-15 03:40:31 +00:00
// Publish resources
if ( room === "admins" ) {
if ( ! userState . isAdmin || adminPublicKeys . length === 0 ) {
console . error ( "User is not an admin or no admin public keys found. Aborting publish." ) ;
window . alert ( "You are not authorized to post in the Admin room." ) ;
return ;
}
console . log ( "Publishing encrypted resources for Admin room..." ) ;
await publishMultipleResources ( multiResource , adminPublicKeys , true ) ;
} else {
console . log ( "Publishing resources for non-admin room..." ) ;
await publishMultipleResources ( multiResource ) ;
}
2024-12-13 05:49:14 +00:00
2024-12-15 03:40:31 +00:00
// Clear inputs and show success notification
2024-12-13 05:49:14 +00:00
clearInputs ( ) ;
showSuccessNotification ( ) ;
} catch ( error ) {
console . error ( "Error sending message:" , error ) ;
2024-12-11 22:40:32 +00:00
}
2024-12-13 05:49:14 +00:00
} ;
2024-12-15 03:40:31 +00:00
2024-12-13 05:49:14 +00:00
// Modify clearInputs to reset replyTo
const clearInputs = ( ) => {
const quill = new Quill ( '#editor' ) ;
quill . root . innerHTML = "" ;
document . getElementById ( 'file-input' ) . value = "" ;
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 ( ) ;
}
} ;
// Show success notification
const showSuccessNotification = ( ) => {
const notification = document . createElement ( 'div' ) ;
notification . innerText = "Message published successfully! Please wait for confirmation." ;
notification . style . color = "green" ;
notification . style . marginTop = "1em" ;
document . querySelector ( ".message-input-section" ) . appendChild ( notification ) ;
2024-12-18 06:24:40 +00:00
alert ( ` Successfully Published! Please note that messages will not display until after they are CONFIRMED, be patient! ` )
2024-12-13 05:49:14 +00:00
setTimeout ( ( ) => {
notification . remove ( ) ;
} , 10000 ) ;
} ;
// Generate unique attachment ID
const generateAttachmentID = ( room , fileIndex = null ) => {
2024-12-21 06:07:18 +00:00
const baseID = room === "admins" ? ` ${ messageAttachmentIdentifierPrefix } - ${ room } -e- ${ randomID ( ) } ` : ` ${ messageAttachmentIdentifierPrefix } - ${ room } - ${ randomID ( ) } ` ;
2024-12-13 05:49:14 +00:00
return fileIndex !== null ? ` ${ baseID } - ${ fileIndex } ` : baseID ;
} ;
2024-12-20 05:28:36 +00:00
// const decryptFile = async (encryptedData) => {
// const publicKey = await getPublicKeyByName(userState.accountName)
// const response = await qortalRequest({
// action: 'DECRYPT_DATA',
// encryptedData, // has to be in base64 format
// // publicKey: publicKey // requires the public key of the opposite user with whom you've created the encrypted data.
// });
// const decryptedObject = response
// return decryptedObject
// }
// --- REFACTORED LOAD MESSAGES AND HELPER FUNCTIONS ---
2024-12-11 22:40:32 +00:00
2024-12-13 01:23:34 +00:00
const loadMessagesFromQDN = async ( room , page , isPolling = false ) => {
2024-12-11 22:40:32 +00:00
try {
const limit = 10 ;
const offset = page * limit ;
2024-12-20 05:28:36 +00:00
console . log ( ` Loading messages from QDN: room= ${ room } , page= ${ page } , offset= ${ offset } , limit= ${ limit } ` ) ;
2024-12-11 22:40:32 +00:00
const messagesContainer = document . querySelector ( "#messages-container" ) ;
if ( ! messagesContainer ) return ;
2024-12-20 05:28:36 +00:00
prepareMessageContainer ( messagesContainer , isPolling ) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
const { service , query } = getServiceAndQuery ( room ) ;
const response = await fetchResourceList ( service , query , limit , offset , room ) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
console . log ( ` Fetched ${ response . length } message(s) for page ${ page } . ` ) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
if ( handleNoMessagesScenario ( isPolling , page , response , messagesContainer ) ) {
2024-12-11 22:40:32 +00:00
return ;
}
2024-12-20 05:28:36 +00:00
// Re-establish existing identifiers after preparing container
existingIdentifiers = new Set (
Array . from ( messagesContainer . querySelectorAll ( '.message-item' ) )
. map ( item => item . dataset . identifier )
2024-12-15 03:40:31 +00:00
) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
let mostRecentMessage = getCurrentMostRecentMessage ( room ) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
const fetchMessages = await fetchAllMessages ( response , service , room ) ;
const { firstNewMessageIdentifier , updatedMostRecentMessage } = renderNewMessages (
fetchMessages ,
existingIdentifiers ,
messagesContainer ,
room ,
mostRecentMessage
) ;
2024-12-11 22:40:32 +00:00
2024-12-20 05:28:36 +00:00
if ( firstNewMessageIdentifier && ! isPolling ) {
scrollToNewMessages ( firstNewMessageIdentifier ) ;
2024-12-11 22:40:32 +00:00
}
2024-12-20 05:28:36 +00:00
if ( updatedMostRecentMessage ) {
updateLatestMessageIdentifiers ( room , updatedMostRecentMessage ) ;
}
handleReplyLogic ( fetchMessages ) ;
await updatePaginationControls ( room , limit ) ;
} catch ( error ) {
console . error ( 'Error loading messages from QDN:' , error ) ;
}
} ;
/** Helper Functions (Arrow Functions) **/
const prepareMessageContainer = ( messagesContainer , isPolling ) => {
if ( ! isPolling ) {
messagesContainer . innerHTML = "" ;
existingIdentifiers . clear ( ) ;
}
} ;
const getServiceAndQuery = ( room ) => {
const service = ( room === "admins" ) ? "MAIL_PRIVATE" : "BLOG_POST" ;
const query = ( room === "admins" )
? ` ${ messageIdentifierPrefix } - ${ room } -e `
: ` ${ messageIdentifierPrefix } - ${ room } ` ;
return { service , query } ;
} ;
const fetchResourceList = async ( service , query , limit , offset , room ) => {
return await searchAllWithOffset ( service , query , limit , offset , room ) ;
} ;
const handleNoMessagesScenario = ( isPolling , page , response , messagesContainer ) => {
if ( response . length === 0 ) {
if ( page === 0 && ! isPolling ) {
messagesContainer . innerHTML = ` <p>No messages found. Be the first to post!</p> ` ;
2024-12-15 03:40:31 +00:00
}
2024-12-20 05:28:36 +00:00
return true ;
}
return false ;
} ;
const getCurrentMostRecentMessage = ( room ) => {
return latestMessageIdentifiers [ room ] ? . latestTimestamp ? latestMessageIdentifiers [ room ] : null ;
} ;
const fetchAllMessages = async ( response , service , room ) => {
return Promise . all ( response . map ( resource => fetchFullMessage ( resource , service , room ) ) ) ;
} ;
2024-12-15 03:40:31 +00:00
2024-12-20 05:28:36 +00:00
const fetchFullMessage = async ( resource , service , room ) => {
try {
// Skip if already displayed
if ( existingIdentifiers . has ( resource . identifier ) ) {
return null ;
2024-12-11 22:40:32 +00:00
}
2024-12-20 05:28:36 +00:00
console . log ( ` Fetching message with identifier: ${ resource . identifier } ` ) ;
const messageResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : resource . name ,
service ,
identifier : resource . identifier ,
... ( room === "admins" ? { encoding : "base64" } : { } ) ,
2024-12-11 22:40:32 +00:00
} ) ;
2024-12-20 05:28:36 +00:00
const timestamp = resource . updated || resource . created ;
const formattedTimestamp = await timestampToHumanReadableDate ( timestamp ) ;
const messageObject = await processMessageObject ( messageResponse , room ) ;
return {
name : resource . name ,
content : messageObject ? . messageHtml || "<em>Message content missing</em>" ,
date : formattedTimestamp ,
identifier : resource . identifier ,
replyTo : messageObject ? . replyTo || null ,
timestamp ,
attachments : messageObject ? . attachments || [ ] ,
} ;
2024-12-11 22:40:32 +00:00
} catch ( error ) {
2024-12-20 05:28:36 +00:00
console . error ( ` Failed to fetch message ${ resource . identifier } : ${ error . message } ` ) ;
return {
name : resource . name ,
content : "<em>Error loading message</em>" ,
date : "Unknown" ,
identifier : resource . identifier ,
replyTo : null ,
timestamp : resource . updated || resource . created ,
attachments : [ ] ,
} ;
2024-12-11 22:40:32 +00:00
}
2024-12-20 05:28:36 +00:00
} ;
const processMessageObject = async ( messageResponse , room ) => {
if ( room !== "admins" ) {
return messageResponse ;
}
try {
const decryptedData = await decryptAndParseObject ( messageResponse ) ;
return decryptedData
} catch ( error ) {
console . error ( ` Failed to decrypt admin message: ${ error . message } ` ) ;
return null ;
}
} ;
const renderNewMessages = ( fetchMessages , existingIdentifiers , messagesContainer , room , mostRecentMessage ) => {
let firstNewMessageIdentifier = null ;
let updatedMostRecentMessage = mostRecentMessage ;
for ( const message of fetchMessages ) {
if ( message && ! existingIdentifiers . has ( message . identifier ) ) {
const isNewMessage = isMessageNew ( message , mostRecentMessage ) ;
if ( isNewMessage && ! firstNewMessageIdentifier ) {
firstNewMessageIdentifier = message . identifier ;
}
const messageHTML = buildMessageHTML ( message , fetchMessages , room , isNewMessage ) ;
messagesContainer . insertAdjacentHTML ( 'beforeend' , messageHTML ) ;
if ( ! updatedMostRecentMessage || new Date ( message . timestamp ) > new Date ( updatedMostRecentMessage ? . latestTimestamp || 0 ) ) {
updatedMostRecentMessage = {
latestIdentifier : message . identifier ,
latestTimestamp : message . timestamp ,
} ;
}
existingIdentifiers . add ( message . identifier ) ;
}
}
return { firstNewMessageIdentifier , updatedMostRecentMessage } ;
} ;
const isMessageNew = ( message , mostRecentMessage ) => {
return ! mostRecentMessage || new Date ( message . timestamp ) > new Date ( mostRecentMessage ? . latestTimestamp ) ;
} ;
const buildMessageHTML = ( message , fetchMessages , room , isNewMessage ) => {
const replyHtml = buildReplyHtml ( message , fetchMessages ) ;
2024-12-21 06:07:18 +00:00
const attachmentHtml = buildAttachmentHtml ( message , room ) ;
2024-12-20 05:28:36 +00:00
const avatarUrl = ` /arbitrary/THUMBNAIL/ ${ message . name } /qortal_avatar ` ;
return `
< div class = "message-item" data - identifier = "${message.identifier}" >
< div class = "message-header" style = "display: flex; align-items: center; justify-content: space-between;" >
< div style = "display: flex; align-items: center;" >
< img src = "${avatarUrl}" alt = "Avatar" class = "user-avatar" style = "width: 30px; height: 30px; border-radius: 50%; margin-right: 10px;" >
< span class = "username" > $ { message . name } < / s p a n >
$ { isNewMessage ? ` <span class="new-indicator" style="margin-left: 10px; color: red; font-weight: bold;">NEW</span> ` : '' }
< / d i v >
< span class = "timestamp" > $ { message . date } < / s p a n >
< / d i v >
$ { replyHtml }
< div class = "message-text" > $ { message . content } < / d i v >
< div class = "attachments-gallery" >
$ { attachmentHtml }
< / d i v >
< button class = "reply-button" data - message - identifier = "${message.identifier}" > Reply < / b u t t o n >
< / d i v >
2024-12-21 06:07:18 +00:00
`
}
2024-12-20 05:28:36 +00:00
const buildReplyHtml = ( message , fetchMessages ) => {
2024-12-21 06:07:18 +00:00
if ( ! message . replyTo ) return ""
2024-12-20 05:28:36 +00:00
2024-12-21 06:07:18 +00:00
const repliedMessage = fetchMessages . find ( m => m && m . identifier === message . replyTo )
if ( ! repliedMessage ) return ""
2024-12-20 05:28:36 +00:00
return `
< div class = "reply-message" style = "border-left: 2px solid #ccc; margin-bottom: 0.5vh; padding-left: 1vh;" >
< div class = "reply-header" > In reply to : < span class = "reply-username" > $ { repliedMessage . name } < / s p a n > < s p a n c l a s s = " r e p l y - t i m e s t a m p " > $ { r e p l i e d M e s s a g e . d a t e } < / s p a n > < / d i v >
< div class = "reply-content" > $ { repliedMessage . content } < / d i v >
< / d i v >
2024-12-21 06:07:18 +00:00
`
}
2024-12-20 05:28:36 +00:00
const buildAttachmentHtml = ( message , room ) => {
2024-12-21 06:07:18 +00:00
if ( ! message . attachments || message . attachments . length === 0 ) return ""
2024-12-20 05:28:36 +00:00
2024-12-21 06:07:18 +00:00
return message . attachments . map ( attachment => buildSingleAttachmentHtml ( attachment , room ) ) . join ( "" )
}
2024-12-20 05:28:36 +00:00
const buildSingleAttachmentHtml = ( attachment , room ) => {
if ( room !== "admins" && attachment . mimeType && attachment . mimeType . startsWith ( 'image/' ) ) {
2024-12-21 06:07:18 +00:00
const imageUrl = ` /arbitrary/ ${ attachment . service } / ${ attachment . name } / ${ attachment . identifier } `
2024-12-20 05:28:36 +00:00
return `
< div class = "attachment" >
< img src = "${imageUrl}" alt = "${attachment.filename}" class = "inline-image" / >
< / d i v >
2024-12-21 06:07:18 +00:00
`
} else if
( room === "admins" && attachment . mimeType && attachment . mimeType . startsWith ( 'image/' ) ) {
return fetchEncryptedImageHtml ( attachment )
2024-12-20 05:28:36 +00:00
} else {
return `
< div class = "attachment" >
< button onclick = "fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')" >
Download $ { attachment . filename }
< / b u t t o n >
< / d i v >
2024-12-21 06:07:18 +00:00
`
2024-12-20 05:28:36 +00:00
}
2024-12-21 06:07:18 +00:00
}
2024-12-20 05:28:36 +00:00
const scrollToNewMessages = ( firstNewMessageIdentifier ) => {
2024-12-21 06:07:18 +00:00
const newMessageElement = document . querySelector ( ` .message-item[data-identifier=" ${ firstNewMessageIdentifier } "] ` )
2024-12-20 05:28:36 +00:00
if ( newMessageElement ) {
2024-12-21 06:07:18 +00:00
newMessageElement . scrollIntoView ( { behavior : 'smooth' , block : 'center' } )
2024-12-20 05:28:36 +00:00
}
2024-12-21 06:07:18 +00:00
}
2024-12-20 05:28:36 +00:00
const updateLatestMessageIdentifiers = ( room , mostRecentMessage ) => {
2024-12-21 06:07:18 +00:00
latestMessageIdentifiers [ room ] = mostRecentMessage
localStorage . setItem ( "latestMessageIdentifiers" , JSON . stringify ( latestMessageIdentifiers ) )
}
2024-12-20 05:28:36 +00:00
const handleReplyLogic = ( fetchMessages ) => {
2024-12-21 06:07:18 +00:00
const replyButtons = document . querySelectorAll ( ".reply-button" )
2024-12-20 05:28:36 +00:00
replyButtons . forEach ( button => {
button . addEventListener ( "click" , ( ) => {
2024-12-21 06:07:18 +00:00
const replyToMessageIdentifier = button . dataset . messageIdentifier
const repliedMessage = fetchMessages . find ( m => m && m . identifier === replyToMessageIdentifier )
2024-12-20 05:28:36 +00:00
if ( repliedMessage ) {
2024-12-21 06:07:18 +00:00
showReplyPreview ( repliedMessage )
2024-12-20 05:28:36 +00:00
}
2024-12-21 06:07:18 +00:00
} )
} )
}
2024-12-20 05:28:36 +00:00
const showReplyPreview = ( repliedMessage ) => {
2024-12-21 06:07:18 +00:00
replyToMessageIdentifier = repliedMessage . identifier
2024-12-20 05:28:36 +00:00
2024-12-21 06:07:18 +00:00
const replyContainer = document . createElement ( "div" )
replyContainer . className = "reply-container"
2024-12-20 05:28:36 +00:00
replyContainer . innerHTML = `
< div class = "reply-preview" style = "border: 1px solid #ccc; padding: 1vh; margin-bottom: 1vh; background-color: black; color: white;" >
< strong > Replying to : < / s t r o n g > $ { r e p l i e d M e s s a g e . c o n t e n t }
< button id = "cancel-reply" style = "float: right; color: red; background-color: black; font-weight: bold;" > Cancel < / b u t t o n >
< / d i v >
2024-12-21 06:07:18 +00:00
`
2024-12-20 05:28:36 +00:00
if ( ! document . querySelector ( ".reply-container" ) ) {
2024-12-21 06:07:18 +00:00
const messageInputSection = document . querySelector ( ".message-input-section" )
2024-12-20 05:28:36 +00:00
if ( messageInputSection ) {
2024-12-21 06:07:18 +00:00
messageInputSection . insertBefore ( replyContainer , messageInputSection . firstChild )
2024-12-20 05:28:36 +00:00
document . getElementById ( "cancel-reply" ) . addEventListener ( "click" , ( ) => {
2024-12-21 06:07:18 +00:00
replyToMessageIdentifier = null
replyContainer . remove ( )
} )
2024-12-20 05:28:36 +00:00
}
}
2024-12-21 06:07:18 +00:00
const messageInputSection = document . querySelector ( ".message-input-section" )
const editor = document . querySelector ( ".ql-editor" )
2024-12-20 05:28:36 +00:00
if ( messageInputSection ) {
2024-12-21 06:07:18 +00:00
messageInputSection . scrollIntoView ( { behavior : 'smooth' , block : 'center' } )
2024-12-20 05:28:36 +00:00
}
if ( editor ) {
2024-12-21 06:07:18 +00:00
editor . focus ( )
2024-12-20 05:28:36 +00:00
}
2024-12-21 06:07:18 +00:00
}
2024-12-20 05:28:36 +00:00
const updatePaginationControls = async ( room , limit ) => {
2024-12-21 06:07:18 +00:00
const totalMessages = room === "admins" ? await searchAllCountOnly ( ` ${ messageIdentifierPrefix } - ${ room } -e ` , room ) : await searchAllCountOnly ( ` ${ messageIdentifierPrefix } - ${ room } ` , room )
renderPaginationControls ( room , totalMessages , limit )
}
2024-12-20 05:28:36 +00:00
2024-12-11 22:40:32 +00:00
// Polling function to check for new messages without clearing existing ones
function startPollingForNewMessages ( ) {
setInterval ( async ( ) => {
2024-12-21 06:07:18 +00:00
const activeRoom = document . querySelector ( '.room-title' ) ? . innerText . toLowerCase ( ) . split ( " " ) [ 0 ]
2024-12-11 22:40:32 +00:00
if ( activeRoom ) {
2024-12-21 06:07:18 +00:00
await loadMessagesFromQDN ( activeRoom , currentPage , true )
2024-12-11 22:40:32 +00:00
}
2024-12-21 06:07:18 +00:00
} , 40000 )
2024-12-11 22:40:32 +00:00
}