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.
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
let messagesById = { }
let messageOrder = [ ]
const MAX _MESSAGES = 2000
// Key = message.identifier
// Value = { ...the message object with timestamp, name, content, etc. }
2024-12-11 22:40:32 +00:00
// If there is a previous latest message identifiers, use them. Otherwise, use an empty.
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const storeMessageInMap = ( msg ) => {
if ( ! msg ? . identifier || ! msg || ! msg ? . timestamp ) return
messagesById [ msg . identifier ] = msg
// We will keep an array 'messageOrder' to store the messages and limit the size they take
messageOrder . push ( { identifier : msg . identifier , timestamp : msg . timestamp } )
messageOrder . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
while ( messageOrder . length > MAX _MESSAGES ) {
// Remove oldest from the front
const oldest = messageOrder . shift ( ) ;
// Delete from the map as well
delete messagesById [ oldest . identifier ] ;
}
}
function saveMessagesToLocalStorage ( ) {
try {
const data = { messagesById , messageOrder } ;
localStorage . setItem ( "forumMessages" , JSON . stringify ( data ) ) ;
console . log ( "Saved messages to localStorage. Count:" , messageOrder . length ) ;
} catch ( error ) {
console . error ( "Error saving to localStorage:" , error ) ;
}
}
function loadMessagesFromLocalStorage ( ) {
try {
const stored = localStorage . getItem ( "forumMessages" ) ;
if ( ! stored ) {
console . log ( "No saved messages in localStorage." ) ;
return ;
}
const parsed = JSON . parse ( stored ) ;
if ( parsed . messagesById && parsed . messageOrder ) {
messagesById = parsed . messagesById ;
messageOrder = parsed . messageOrder ;
console . log ( ` Loaded ${ messageOrder . length } messages from localStorage. ` ) ;
}
} catch ( error ) {
console . error ( "Error loading messages from localStorage:" , error ) ;
}
}
2024-12-11 22:40:32 +00:00
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 ( ) ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
if ( userState . isAdmin && ( localStorage . getItem ( 'savedAdminData' ) ) ) {
console . log ( 'saved admin data found (Q-Mintership.js), loading...' )
const adminData = localStorage . getItem ( 'savedAdminData' )
const parsedAdminData = JSON . parse ( adminData )
if ( ! adminPublicKeys || adminPublicKeys . length === 0 || ! Array . isArray ( adminPublicKeys ) ) {
console . log ( 'no adminPublicKey variable data found and/or data did not pass checks, using fetched localStorage data...' , adminPublicKeys )
if ( parsedAdminData . publicKeys . length === 0 || ! parsedAdminData . publicKeys || ! Array . isArray ( parsedAdminData . publicKeys ) ) {
console . log ( 'loaded data from localStorage also did not pass checks... fetching from API...' , parsedAdminData . publicKeys )
adminPublicKeys = await fetchAdminGroupsMembersPublicKeys ( )
} else {
adminPublicKeys = parsedAdminData . publicKeys
}
}
}
2024-12-18 06:24:40 +00:00
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-24 08:27:17 +00:00
if ( ( typeof userState . isAdmin === 'undefined' ) || ( ! userState . isAdmin ) ) {
2024-12-15 03:40:31 +00:00
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 ;
}
2024-12-24 08:27:17 +00:00
if ( userState . isAdmin ) {
await loadOrFetchAdminGroupsData ( )
}
2024-12-13 05:49:14 +00:00
// 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 >
2024-12-24 08:27:17 +00:00
< button id = "add-images-to-publish-button" style = "display: none" disabled > Add Images to Multi - Publish < / b u t t o n >
2024-12-13 05:49:14 +00:00
< 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 ) ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
//TODO - maybe turn this into its own function and put it as a button? But for now it's fine to just load the latest message's position by default I think.
const latestId = latestMessageIdentifiers [ room ] ? . latestIdentifier ;
if ( latestId ) {
const page = await findMessagePage ( room , latestId , 10 )
currentPage = page ;
await loadMessagesFromQDN ( room , currentPage )
scrollToMessage ( latestId . latestIdentifier )
} else {
await loadMessagesFromQDN ( room , currentPage )
}
;
2024-12-13 05:49:14 +00:00
} ;
// 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 = [ ] ;
2024-12-24 08:27:17 +00:00
imageFileInput . value = "" ;
2024-12-13 05:49:14 +00:00
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"
2024-12-24 08:27:17 +00:00
? ` ${ messageIdentifierPrefix } - ${ room } -e- ${ randomID ( ) } `
: ` ${ messageIdentifierPrefix } - ${ room } - ${ randomID ( ) } ` ;
2024-12-15 03:40:31 +00:00
2024-12-24 08:27:17 +00:00
// const checkedAdminPublicKeys = room === "admins" && userState.isAdmin
// ? adminPublicKeys
// : await loadOrFetchAdminGroupsData().publicKeys;
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 ,
2024-12-24 08:27:17 +00:00
} )
2024-12-15 03:40:31 +00:00
} else {
multiResource . push ( {
name : userState . accountName ,
service : "BLOG_POST" ,
identifier : messageIdentifier ,
data64 : base64Message ,
2024-12-24 08:27:17 +00:00
} )
2024-12-15 03:40:31 +00:00
}
2024-12-13 05:49:14 +00:00
2024-12-15 03:40:31 +00:00
// Publish resources
if ( room === "admins" ) {
2024-12-24 08:27:17 +00:00
if ( ! userState . isAdmin ) {
2024-12-15 03:40:31 +00:00
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
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const findMessagePage = async ( room , identifier , limit ) => {
const { service , query } = getServiceAndQuery ( room )
const allMessages = await searchAllWithOffset ( service , query , 0 , 0 , room )
const idx = allMessages . findIndex ( msg => msg . identifier === identifier ) ;
if ( idx === - 1 ) {
// Not found, default to last page or page=0
return 0 ;
}
return Math . floor ( idx / limit )
}
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 ) ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
for ( const msg of fetchMessages ) {
if ( ! msg ) continue ;
storeMessageInMap ( msg ) ;
}
2024-12-24 08:27:17 +00:00
const { firstNewMessageIdentifier , updatedMostRecentMessage } = await renderNewMessages (
2024-12-20 05:28:36 +00:00
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 ) ;
}
} ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
function scrollToMessage ( identifier ) {
const targetElement = document . querySelector ( ` .message-item[data-identifier=" ${ identifier } "] ` ) ;
if ( targetElement ) {
targetElement . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
}
}
2024-12-20 05:28:36 +00:00
/** 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 ;
} ;
2024-12-24 08:27:17 +00:00
// 1) Convert fetchAllMessages to fully async
2024-12-20 05:28:36 +00:00
const fetchAllMessages = async ( response , service , room ) => {
2024-12-24 08:27:17 +00:00
// Instead of returning Promise.all(...) directly,
// we explicitly map each resource to a try/catch block.
const messages = await Promise . all (
response . map ( async ( resource ) => {
try {
const msg = await fetchFullMessage ( resource , service , room ) ;
return msg ; // This might be null if you do that check in fetchFullMessage
} catch ( err ) {
console . error ( ` Skipping resource ${ resource . identifier } due to error: ` , err ) ;
// Return null so it doesn't break everything
return null ;
}
} )
) ;
// Filter out any that are null/undefined (missing or errored)
return messages . filter ( Boolean ) ;
2024-12-20 05:28:36 +00:00
} ;
2024-12-15 03:40:31 +00:00
2024-12-24 08:27:17 +00:00
// 2) fetchFullMessage is already async. We keep it async/await-based
2024-12-20 05:28:36 +00:00
const fetchFullMessage = async ( resource , service , room ) => {
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
// 1) Skip if we already have it in memory
if ( messagesById [ resource . identifier ] ) {
// Possibly also check if the local data is "up to date," //TODO when adding 'edit' ability to messages, will also need to verify timestamp in saved data.
// but if you trust your local data, skip the fetch entirely.
console . log ( ` Skipping fetch. Found in local store: ${ resource . identifier } ` ) ;
return messagesById [ resource . identifier ] ;
}
2024-12-20 05:28:36 +00:00
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 ) ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const builtMsg = {
2024-12-20 05:28:36 +00:00
name : resource . name ,
content : messageObject ? . messageHtml || "<em>Message content missing</em>" ,
date : formattedTimestamp ,
identifier : resource . identifier ,
replyTo : messageObject ? . replyTo || null ,
timestamp ,
attachments : messageObject ? . attachments || [ ] ,
} ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
// 3) Store it in the map so we skip future fetches
storeMessageInMap ( builtMsg ) ;
return builtMsg ;
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
} ;
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const fetchReplyData = async ( service , name , identifier , room , replyTimestamp ) => {
try {
console . log ( ` Fetching message with identifier: ${ identifier } ` ) ;
const messageResponse = await qortalRequest ( {
action : "FETCH_QDN_RESOURCE" ,
name ,
service ,
identifier ,
... ( room === "admins" ? { encoding : "base64" } : { } ) ,
} )
console . log ( 'reply response' , messageResponse )
const messageObject = await processMessageObject ( messageResponse , room )
console . log ( 'reply message object' , messageObject )
const formattedTimestamp = await timestampToHumanReadableDate ( replyTimestamp )
return {
name ,
content : messageObject ? . messageHtml || "<em>Message content missing</em>" ,
date : formattedTimestamp ,
identifier ,
replyTo : messageObject ? . replyTo || null ,
timestamp : replyTimestamp ,
attachments : messageObject ? . attachments || [ ] ,
} ;
} catch ( error ) {
console . error ( ` Failed to fetch message ${ identifier } : ${ error . message } ` )
return {
name ,
content : "<em>Error loading message</em>" ,
date : "Unknown" ,
identifier ,
replyTo : null ,
timestamp : null ,
attachments : [ ] ,
}
}
}
2024-12-24 08:27:17 +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 ;
}
} ;
2024-12-24 08:27:17 +00:00
const renderNewMessages = async ( fetchMessages , existingIdentifiers , messagesContainer , room , mostRecentMessage ) => {
2024-12-20 05:28:36 +00:00
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 ;
}
2024-12-24 08:27:17 +00:00
const messageHTML = await buildMessageHTML ( message , fetchMessages , room , isNewMessage ) ;
2024-12-20 05:28:36 +00:00
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 ) ;
} ;
2024-12-24 08:27:17 +00:00
const buildMessageHTML = async ( message , fetchMessages , room , isNewMessage ) => {
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const replyHtml = await buildReplyHtml ( message , room ) ;
2024-12-24 08:27:17 +00:00
const attachmentHtml = await 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
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const buildReplyHtml = async ( message , room ) => {
2024-12-21 06:07:18 +00:00
if ( ! message . replyTo ) return ""
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const replyService = ( room === "admins" ) ? "MAIL_PRIVATE" : "BLOG_POST" ;
const replyIdentifier = message . replyTo
const savedRepliedToMessage = messagesById [ message . replyTo ] ;
if ( savedRepliedToMessage ) {
const processedMessage = await processMessageObject ( savedRepliedToMessage , room )
console . log ( 'message is saved in saved message data, returning from that' , savedRepliedToMessage )
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" > $ { processedMessage . name } < / s p a n >
< span class = "reply-timestamp" > $ { processedMessage . date } < / s p a n >
< / d i v >
< div class = "reply-content" > $ { processedMessage . content } < / d i v >
< / d i v >
` ;
}
try {
2024-12-20 05:28:36 +00:00
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
const replyData = await searchSimple ( replyService , replyIdentifier , "" , 1 )
if ( ( ! replyData ) || ( ! replyData . name ) ) {
// No result found. You can either return an empty string or handle differently
console . log ( "No data found via searchSimple. Skipping reply rendering." ) ;
return "" ;
}
const replyName = await replyData . name
const replyTimestamp = await replyData . updated || await replyData . created
console . log ( 'message not found in saved message data, using searchSimple' , replyData )
2024-12-20 05:28:36 +00:00
This version includes many changes and performance improvements. Further performance improvements will be coming soon. This change includes a cache for the published data on the forum. Messages of up to 2000 in number, will be stored locally in browser storage, that way if the message has already been loaded by that computer, it will not have to pull the data again from QDN. It will be stored in encrypted format for the Admin room. This same caching will be applied to the Minter and Admin boards in the future. Also, reply issues that were present before should be resolved, all replies, regardless of when they were published, will now show their previews in the message pane as they are supposed to. Previously if a reply was on another page, it would not load this preview. The encrypted portions of the app now include a method of caching the admin public keys, for faster publishing. The Minter and Admin boards have a new comment loading display when the comments button is clicked to let users know that data is being loaded, on top of the existing comment count. Other new features and additional performance improvements are in planning. Also, the issue preventing comments from those that had not already loaded the forum, in the Admin Board, has been resolved as well.
2024-12-27 04:06:51 +00:00
// const repliedMessage = fetchMessages.find(m => m && m.identifier === message.replyTo)
// const repliedMessageIdentifier = message.replyTo
const repliedMessage = await fetchReplyData ( replyService , replyName , replyIdentifier , room , replyTimestamp )
storeMessageInMap ( repliedMessage )
if ( ! repliedMessage ) return ""
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 >
`
} catch ( error ) {
throw ( error )
}
2024-12-21 06:07:18 +00:00
}
2024-12-20 05:28:36 +00:00
2024-12-24 08:27:17 +00:00
const buildAttachmentHtml = async ( message , room ) => {
if ( ! message . attachments || message . attachments . length === 0 ) {
return "" ;
}
2024-12-20 05:28:36 +00:00
2024-12-24 08:27:17 +00:00
// Map over attachments -> array of Promises
const attachmentsHtmlPromises = message . attachments . map ( attachment =>
buildSingleAttachmentHtml ( attachment , room )
) ;
// Wait for all Promises to resolve -> array of HTML strings
const attachmentsHtmlArray = await Promise . all ( attachmentsHtmlPromises ) ;
// Join them into a single string
return attachmentsHtmlArray . join ( "" ) ;
} ;
2024-12-20 05:28:36 +00:00
2024-12-24 08:27:17 +00:00
const buildSingleAttachmentHtml = async ( attachment , room ) => {
2024-12-20 05:28:36 +00:00
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" / >
2024-12-24 08:27:17 +00:00
< button onclick = "fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')" >
Save $ { attachment . filename }
< / b u t t o n >
2024-12-20 05:28:36 +00:00
< / d i v >
2024-12-21 06:07:18 +00:00
`
} else if
( room === "admins" && attachment . mimeType && attachment . mimeType . startsWith ( 'image/' ) ) {
2024-12-24 08:27:17 +00:00
// const imageUrl = `/arbitrary/${attachment.service}/${attachment.name}/${attachment.identifier}`;
const decryptedBase64 = await fetchEncryptedImageBase64 ( attachment . service , attachment . name , attachment . identifier , attachment . mimeType )
const dataUrl = ` data:image/png;base64, ${ decryptedBase64 } `
return `
< div class = "attachment" >
< img src = "${dataUrl}" alt = "${attachment.filename}" class = "inline-image" / >
< button onclick = "fetchAndSaveAttachment('${attachment.service}', '${attachment.name}', '${attachment.identifier}', '${attachment.filename}', '${attachment.mimeType}')" >
Save $ { 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
} 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
}