2024-12-11 22:40:32 +00:00
const messageIdentifierPrefix = ` mintership-forum-message ` ;
const messageAttachmentIdentifierPrefix = ` mintership-forum-attachment ` ;
// 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-13 01:23:34 +00:00
// Identify the links for 'Mintership Forum' and apply functionality
2024-12-11 22:40:32 +00:00
const mintershipForumLinks = document . querySelectorAll ( 'a[href="MINTERSHIP-FORUM"]' ) ;
mintershipForumLinks . forEach ( link => {
link . addEventListener ( 'click' , async ( event ) => {
event . preventDefault ( ) ;
//login if not already logged in.
if ( ! userState . isLoggedIn ) {
await login ( ) ;
}
await loadForumPage ( ) ;
loadRoomContent ( "general" ) ; // Automatically load General Room on forum load
startPollingForNewMessages ( ) ; // Start polling for new messages after loading the forum page
} ) ;
} ) ;
} ) ;
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 ( ) ;
}
}
const avatarUrl = ` /arbitrary/THUMBNAIL/ ${ userState . accountName } /qortal_avatar ` ;
// Create the forum layout, including a header, sub-menu, and keeping the original background imagestyle="background-image: url('/assets/images/background.jpg');">
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;" >
< div class = "user-info" style = "border: 1px solid lightblue; padding: 5px; color: lightblue; display: flex; align-items: center; justify-content: center;" >
< 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 >
$ { userState . isAdmin ? '<button class="room-button" id="admins-room">Admins Room</button>' : '' }
< 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 ------------------------
const renderPaginationControls = async ( 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 ) {
let attachmentID = generateAttachmentID ( room , selectedImages . indexOf ( file ) )
try {
multiResource . push ( {
name : userState . accountName ,
service : "FILE" ,
identifier : attachmentID ,
file
2024-12-13 01:23:34 +00:00
} ) ;
2024-12-13 05:49:14 +00:00
attachmentIdentifiers . push ( {
name : userState . accountName ,
service : "FILE" ,
identifier : attachmentID ,
filename : file . name ,
mimeType : file . type
} )
} catch ( error ) {
console . error ( ` Error processing image ${ file . name } : ` , error ) ;
}
}
} ;
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 ) => {
const messageIdentifier = ` ${ messageIdentifierPrefix } - ${ room } - ${ Date . now ( ) } ` ;
try {
if ( selectedImages . length > 0 ) {
await processSelectedImages ( selectedImages , multiResource , room ) ;
2024-12-13 01:23:34 +00:00
}
2024-12-13 05:49:14 +00:00
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
} )
}
2024-12-11 22:40:32 +00:00
2024-12-13 05:49:14 +00:00
const messageObject = {
messageHtml ,
hasAttachment : multiResource . length > 0 ,
attachments : attachmentIdentifiers ,
replyTo : replyToMessageIdentifier || null // Add replyTo
} ;
2024-12-11 22:40:32 +00:00
2024-12-13 05:49:14 +00:00
const base64Message = btoa ( JSON . stringify ( messageObject ) ) ;
2024-12-11 22:40:32 +00:00
2024-12-13 05:49:14 +00:00
multiResource . push ( {
name : userState . accountName ,
service : "BLOG_POST" ,
identifier : messageIdentifier ,
data64 : base64Message
} ) ;
await publishMultipleResources ( multiResource ) ;
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
} ;
// 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 ) ;
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 ;
} ;
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 ;
console . log ( ` Loading messages for room: ${ room } , page: ${ page } , offset: ${ offset } , limit: ${ limit } ` ) ;
// Get the messages container
const messagesContainer = document . querySelector ( "#messages-container" ) ;
if ( ! messagesContainer ) return ;
// If not polling, clear the message container and the existing identifiers for a fresh load
if ( ! isPolling ) {
messagesContainer . innerHTML = "" ; // Clear the messages container before loading new page
existingIdentifiers . clear ( ) ; // Clear the existing identifiers set for fresh page load
}
// Get the set of existing identifiers from the messages container
existingIdentifiers = new Set ( Array . from ( messagesContainer . querySelectorAll ( '.message-item' ) ) . map ( item => item . dataset . identifier ) ) ;
// Fetch messages for the current room and page
const response = await searchAllWithOffset ( ` ${ messageIdentifierPrefix } - ${ room } ` , limit , offset ) ;
console . log ( ` Fetched messages count: ${ response . length } for page: ${ page } ` ) ;
if ( response . length === 0 ) {
// If no messages are fetched and it's not polling, display "no messages" for the initial load
if ( page === 0 && ! isPolling ) {
messagesContainer . innerHTML = ` <p>No messages found. Be the first to post!</p> ` ;
}
return ;
}
// Define `mostRecentMessage` to track the latest message during this fetch
let mostRecentMessage = latestMessageIdentifiers [ room ] ? . latestTimestamp ? latestMessageIdentifiers [ room ] : null ;
// Fetch all messages that haven't been fetched before
const fetchMessages = await Promise . all ( response . map ( async ( resource ) => {
if ( existingIdentifiers . has ( resource . identifier ) ) {
return null ; // Skip messages that are already displayed
}
try {
console . log ( ` Fetching message with identifier: ${ resource . identifier } ` ) ;
const messageResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name : resource . name ,
service : "BLOG_POST" ,
identifier : resource . identifier ,
} ) ;
console . log ( "Fetched message response:" , messageResponse ) ;
// No need to decode, as qortalRequest returns the decoded data if no 'encoding: base64' is set.
const messageObject = messageResponse ;
const timestamp = resource . updated || resource . created ;
const formattedTimestamp = await timestampToHumanReadableDate ( timestamp ) ;
return {
name : resource . name ,
content : messageObject . messageHtml ,
date : formattedTimestamp ,
identifier : resource . identifier ,
replyTo : messageObject . replyTo ,
timestamp ,
attachments : messageObject . attachments || [ ] // Include attachments if they exist
} ;
} catch ( error ) {
console . error ( ` Failed to fetch message with identifier ${ resource . identifier } . Error: ${ error . message } ` ) ;
return null ;
}
} ) ) ;
// Render new messages without duplication
for ( const message of fetchMessages ) {
if ( message && ! existingIdentifiers . has ( message . identifier ) ) {
let replyHtml = "" ;
if ( message . replyTo ) {
const repliedMessage = fetchMessages . find ( m => m && m . identifier === message . replyTo ) ;
if ( repliedMessage ) {
replyHtml = `
< 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 >
` ;
}
}
const isNewMessage = ! mostRecentMessage || new Date ( message . timestamp ) > new Date ( mostRecentMessage ? . latestTimestamp ) ;
let attachmentHtml = "" ;
if ( message . attachments && message . attachments . length > 0 ) {
for ( const attachment of message . attachments ) {
2024-12-13 05:49:14 +00:00
if ( attachment . mimeType && attachment . mimeType . startsWith ( 'image/' ) ) {
2024-12-11 22:40:32 +00:00
try {
2024-12-13 05:49:14 +00:00
// Construct the image URL
2024-12-11 22:40:32 +00:00
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" / >
< / d i v > ` ;
2024-12-13 05:49:14 +00:00
// Set up the modal download button
2024-12-11 22:40:32 +00:00
const downloadButton = document . getElementById ( "download-button" ) ;
downloadButton . onclick = ( ) => {
fetchAndSaveAttachment (
attachment . service ,
attachment . name ,
attachment . identifier ,
attachment . filename ,
attachment . mimeType
) ;
} ;
} catch ( error ) {
console . error ( ` Failed to fetch attachment ${ attachment . filename } : ` , error ) ;
}
} else {
2024-12-13 05:49:14 +00:00
// Display a button to download non-image attachments
2024-12-11 22:40:32 +00:00
attachmentHtml += ` <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-13 05:49:14 +00:00
2024-12-11 22:40:32 +00:00
const avatarUrl = ` /arbitrary/THUMBNAIL/ ${ message . name } /qortal_avatar ` ;
const messageHTML = `
< 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 >
< / 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 >
` ;
// Append new message to the end of the container
messagesContainer . insertAdjacentHTML ( 'beforeend' , messageHTML ) ;
// Update mostRecentMessage if this message is newer
if ( ! mostRecentMessage || new Date ( message . timestamp ) > new Date ( mostRecentMessage ? . latestTimestamp || 0 ) ) {
mostRecentMessage = {
latestIdentifier : message . identifier ,
latestTimestamp : message . timestamp
} ;
}
// Add the identifier to the existingIdentifiers set
existingIdentifiers . add ( message . identifier ) ;
}
}
// Update latestMessageIdentifiers for the room
if ( mostRecentMessage ) {
latestMessageIdentifiers [ room ] = mostRecentMessage ;
localStorage . setItem ( "latestMessageIdentifiers" , JSON . stringify ( latestMessageIdentifiers ) ) ;
}
// Add event listeners to the reply buttons
const replyButtons = document . querySelectorAll ( ".reply-button" ) ;
replyButtons . forEach ( button => {
button . addEventListener ( "click" , ( ) => {
replyToMessageIdentifier = button . dataset . messageIdentifier ;
// Find the message being replied to
const repliedMessage = fetchMessages . find ( m => m && m . identifier === replyToMessageIdentifier ) ;
if ( repliedMessage ) {
const replyContainer = document . createElement ( "div" ) ;
replyContainer . className = "reply-container" ;
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 >
` ;
if ( ! document . querySelector ( ".reply-container" ) ) {
const messageInputSection = document . querySelector ( ".message-input-section" ) ;
if ( messageInputSection ) {
messageInputSection . insertBefore ( replyContainer , messageInputSection . firstChild ) ;
// Add a listener for the cancel reply button
document . getElementById ( "cancel-reply" ) . addEventListener ( "click" , ( ) => {
replyToMessageIdentifier = null ;
replyContainer . remove ( ) ;
} ) ;
}
}
const messageInputSection = document . querySelector ( ".message-input-section" ) ;
const editor = document . querySelector ( ".ql-editor" ) ;
if ( messageInputSection ) {
messageInputSection . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
}
if ( editor ) {
editor . focus ( ) ;
}
}
} ) ;
} ) ;
// Render pagination controls
const totalMessages = await searchAllCountOnly ( ` ${ messageIdentifierPrefix } - ${ room } ` ) ;
renderPaginationControls ( room , totalMessages , limit ) ;
} catch ( error ) {
console . error ( 'Error loading messages from QDN:' , error ) ;
}
}
// Polling function to check for new messages without clearing existing ones
function startPollingForNewMessages ( ) {
setInterval ( async ( ) => {
const activeRoom = document . querySelector ( '.room-title' ) ? . innerText . toLowerCase ( ) . split ( " " ) [ 0 ] ;
if ( activeRoom ) {
await loadMessagesFromQDN ( activeRoom , currentPage , true ) ;
}
} , 20000 ) ;
}