X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit-ng%2Fstatic%2Fjs%2Fview_forum.js;h=e5884d400b32ee133b757e459778ac0c1bfe88b1;hb=545fc1a847d9401923ef312f995d4f385da84e56;hp=3711a2aed10f3bad9dc7bff11ae2e754a3967f96;hpb=c994a997c95509a5c43b0f7b0e97e720f6e84c95;p=citadel.git diff --git a/webcit-ng/static/js/view_forum.js b/webcit-ng/static/js/view_forum.js index 3711a2aed..e5884d400 100644 --- a/webcit-ng/static/js/view_forum.js +++ b/webcit-ng/static/js/view_forum.js @@ -1,24 +1,16 @@ +// This module handles the view for "forum" (message board) rooms. // -// Copyright (c) 2016-2021 by the citadel.org team +// Copyright (c) 2016-2023 by the citadel.org team // -// This program is open source software. It runs great on the -// Linux operating system (and probably elsewhere). You can use, -// copy, and run it under the terms of the GNU General Public -// License version 3. Richard Stallman is an asshole communist. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. +// This program is open source software. Use, duplication, or +// disclosure is subject to the GNU General Public License v3. // Forum view (flat) -// -function forum_readmessages(target_div, gt_msg, lt_msg) { - original_text = document.getElementById(target_div).innerHTML; // in case we need to replace it after an error - document.getElementById(target_div).innerHTML = - "
  " - + _("Loading messages from server, please wait") + "
"; +function forum_readmessages(target_div_name, gt_msg, lt_msg) { + target_div = document.getElementById(target_div_name); + original_text = target_div.innerHTML; // in case we need to replace it after an error + target_div.innerHTML = "" if (lt_msg < 9999999999) { url = "/ctdl/r/" + escapeHTMLURI(current_room) + "/msgs.lt|" + lt_msg; @@ -31,7 +23,7 @@ function forum_readmessages(target_div, gt_msg, lt_msg) { response = await fetch(url); msgs = await(response.json()); if (response.ok) { - document.getElementById(target_div).innerHTML = "" ; + target_div.innerHTML = "" ; // If we were given an explicit starting point, by all means start there. // Note that we don't have to remove them from the array because we did a 'msgs gt|xxx' command to Citadel. @@ -44,40 +36,40 @@ function forum_readmessages(target_div, gt_msg, lt_msg) { if (msgs.length > messages_per_page) { msgs = msgs.slice(msgs.length - messages_per_page); } - new_old_div_name = randomString(5); + new_old_div_name = randomString(); if (msgs.length < 1) { newlt = lt_msg; } else { newlt = msgs[0]; } - document.getElementById(target_div).innerHTML += + target_div.innerHTML += "
" + - "
" + - "" + + "
" ; + _("Older posts") + "  " ; } - // Render an empty div for each message. We will fill them in later. - for (var i in msgs) { - document.getElementById(target_div).innerHTML += "
" ; - document.getElementById("ctdl_msg_"+msgs[i]).style.display = "none"; - } + // The messages will go here. + let msgs_div_name = randomString(); + target_div.innerHTML += "
" ; + if (lt_msg == 9999999999) { - new_new_div_name = randomString(5); + new_new_div_name = randomString(); if (msgs.length <= 0) { newgt = gt_msg; } else { newgt = msgs[msgs.length-1]; } - document.getElementById(target_div).innerHTML += + target_div.innerHTML += "
" + - "" ; + _("Newer posts") + "  
" ; } // Now figure out where to scroll to after rendering. @@ -95,159 +87,303 @@ function forum_readmessages(target_div, gt_msg, lt_msg) { } // Render the individual messages in the divs - forum_render_messages(msgs, "ctdl_msg_", scroll_to) + forum_render_messages(msgs, msgs_div_name, scroll_to) } else { // if xhr fails, this will make the link reappear so the user can try again - document.getElementById(target_div).innerHTML = original_text; + target_div.innerHTML = original_text; } } fetch_msg_list(); + + // make the nav buttons appear (post a new message, skip this room, goto next room) + + document.getElementById("ctdl-newmsg-button").innerHTML = ` ` + _("Post message"); + document.getElementById("ctdl-newmsg-button").style.display = "block"; + + document.getElementById("ctdl-skip-button").innerHTML = _("Skip this room") + ` `; + document.getElementById("ctdl-skip-button").style.display = "block"; + + document.getElementById("ctdl-goto-button").innerHTML = _("Goto next room") + ` `; + document.getElementById("ctdl-goto-button").style.display = "block"; } -// Render a range of messages, with the div prefix specified -// -function forum_render_messages(msgs, prefix, scroll_to) { - for (i=0; i response.json()) + .catch((error) => { + response => null; + }) + ; + } + + // Here is the async function that waits for all the messages to be loaded, and then renders them. + fetch_msg_list = async() => { + document.body.style.cursor = "wait"; + activate_loading_modal(); + await Promise.all(msg_promises); + deactivate_loading_modal(); + document.body.style.cursor = "default"; + + // At this point all of the Promises are resolved and we can render. + // Note: "let" keeps "i" in scope even through the .then scope + let scroll_to_div = null; + for (let i=0; i { + let new_msg_div = forum_render_one(one_message, null); + document.getElementById(msgs_div_name).append(new_msg_div); + if (message_numbers[i] == scroll_to) { + scroll_to_div = new_msg_div; + } + if (i == num_msgs - 1) { + scroll_to_div.scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"}); + } + }); + } + } + + fetch_msg_list(); + + // Make a note of the highest message number we saw, so we can mark it when we "Goto next room" + // (Compared to the text client, this is actually more like bandon than oto) + if ((num_msgs > 0) && (message_numbers[num_msgs-1] > last_seen)) { + last_seen = message_numbers[num_msgs-1]; } } -// We have to put each XHR for forum_render_messages() into its own stack frame, otherwise it jumbles them together. I don't know why. -function forum_render_one(prefix, msgnum, scroll_to) { - fetch_message = async() => { - response = await fetch("/ctdl/r/" + escapeHTMLURI(current_room) + "/" + msgs[i] + "/json"); - msg = await response.json(); - if (response.ok) { - outmsg = - "
" // begin message wrapper - + "
" // begin avatar - + "" - + "
" // end avatar - + "
" // begin content - + "
" // begin header - + "" // begin header info on left side - + "" // FIXME link to user profile - + msg.from - + "" // end username - + "" - + msg.time - + "" // end msgdate - + "" // end header info on left side - + "" // begin buttons on right side - - + "" // Reply button FIXME make this work - + "" - + " " - + _("Reply") - + "" - - + "" // ReplyQuoted , only show in forums FIXME - + "" - + " " - + _("ReplyQuoted") - + "" - - + "" // Delete , show only with permission FIXME - + " " - + _("Delete") - + "" +// Render a message. Returns a div object. +function forum_render_one(msg, existing_div) { + let div = null; + if (existing_div != null) { // If an existing div was supplied, render into it + div = existing_div; + } + else { // Otherwise, create a new one + div = document.createElement("div"); + } - + ""; // end buttons on right side - if (msg.subj) { - outmsg += - "
" + msg.subj + ""; - } + mdiv = randomString(); // Give the div a new name + div.id = mdiv; + + try { + outmsg = + "
" // begin message wrapper + + render_userpic(msg.from) // user avatar + + "
" // begin content + + "
" // begin header + + "" // begin header info on left side + + render_msg_author(msg, views.VIEW_BBS) // author + + "" + + string_timestamp(msg.time,0) + + "" // end msgdate + + "" // end header info on left side + + "" // begin buttons on right side + + + "" // Reply + + "" + + " " + + _("Reply") + + "" + + + "" // ReplyQuoted + + "" + + " " + + _("ReplyQuoted") + + ""; + + if (can_delete_messages) { outmsg += - "

" // end header - + "
" // begin body - + msg.text - + "
" // end body - + "
" // end content - + "
" // end wrapper - ; - document.getElementById(prefix+msgnum).innerHTML = outmsg; + "" + + "" + + " " + + _("Delete") + + ""; } - else { - document.getElementById(prefix+msgnum).innerHTML = "ERROR"; + + outmsg += + ""; // end buttons on right side + if (msg.subj) { + outmsg += + "
" + msg.subj + ""; } - document.getElementById(prefix+msgnum).style.display = "inline"; - if (msgnum == scroll_to) { - document.getElementById(prefix+msgnum).scrollIntoView({behavior: "smooth", block: "start", inline: "nearest"}); + outmsg += + "
" // end header + + "
" // begin body + + msg.text + + "
" // end body + + "
" // end content + + "
" // end wrapper + ; + } + catch(err) { + outmsg = "
" + err.message + "
"; + } + + div.innerHTML = outmsg; + return(div); +} + + +// Compose a references string using existing references plus the message being replied to +function compose_references(references, msgid) { + if (references.includes("@")) { + refs = references + "|"; + } + else { + refs = ""; + } + refs += msgid; + + // If the resulting string is too big, we can trim it here + while (refs.length > 900) { + r = refs.split("|"); + r.splice(1,1); // remove the second element so we keep the root + refs = r.join("|"); + } + return refs; +} + +// Delete a message. +// We don't bother checking for permission because the button only appears if we have permission, +// and even if someone hacks the client, the server will deny any unauthorized deletes. +function forum_delete_message(message_div, message_number) { + if (confirm(_("Delete this message?")) == true) { + async_forum_delete_message = async() => { + response = await fetch( + "/ctdl/r/" + escapeHTMLURI(current_room) + "/" + message_number, + { method: "DELETE" } + ); + if (response.ok) { // If the server accepted the delete, blank out the message div. + document.getElementById(message_div).outerHTML = ""; + } } + async_forum_delete_message(); } - fetch_message(); } // Open a reply box directly below a specific message -function open_reply_box(prefix, msgnum, is_quoted) { - target_div_name = prefix+msgnum; - new_div_name = prefix + "_reply_to_" + msgnum; - document.getElementById(target_div_name).outerHTML += "
reply box put here
"; +function open_reply_box(parent_div, is_quoted, references, msgid) { + let new_div = document.createElement("div"); + let new_div_name = randomString(); + new_div.id = new_div_name; + + document.getElementById(parent_div).append(new_div); replybox = - "
" // begin message wrapper + "
" // begin message wrapper + "
" // begin avatar - + "" + "
" // end avatar - + "
" // begin content + + "
" // begin content + "
" // begin header + "" // begin header info on left side - + "" // FIXME link to user profile - + "FIXME my name" - + "" // end username + + "" + + current_user // user = me ! + + "" + "" - + "FIXME now time" - + "" // end msgdate + + string_timestamp((Date.now() / 1000),0) // the current date/time (temporary for display) + + "" + "" // end header info on left side + "" // begin buttons on right side - + "x" + + + "" // bold button + + "" + + "" + + "" + + + "" // italic button + + "" + + "" + + "" + + + "" // list button + + "" + + "" + + "" + + + "" // link button + + "" + + "" + + "" + + ""; // end buttons on right side - if (msg.subj) { - replybox += - "
" + "FIXME subject" + ""; - } + //if (msg.subj) { + //replybox += + //"
" + "FIXME subject" + ""; + //} + //else { // hidden filed for empty subject + replybox += ""; + //} replybox += "

" // end header + + "" // hidden field for references + + compose_references(references,msgid) + "" + // begin body + + "
" + + "\n"; // empty initial content - + "
" // begin body - + "This is where the reply text will go." - + "
" // end body + if (is_quoted) { + replybox += "
" + + document.getElementById(parent_div+"_body").innerHTML + + "
"; + } + replybox += + "
" // end body + "
" // begin footer + "" // begin footer info on left side - + "x" + + " " // (nothing here for now) + "" // end footer info on left side + "" // begin buttons on right side - + "" // FIXME save and cancel buttons - + " " + + "" + + " " // save button + _("Post message") + "" - + "" // FIXME save and cancel buttons - + " " + + "" + + " " // cancel button + _("Cancel") + "" - + ""; // end buttons on right side - if (msg.subj) { - replybox += - "
" + "FIXME subject" + ""; - } - replybox += - "

" // end footer + + "" // end buttons on right side + + "

" // end footer + "
" // end content + "
" // end wrapper + + + "
" // begin URL entry modal + + "
" + + "

URL:

" + + "
" + + "
" + + " " + + "
" + + " " + + " " // hidden fields + + " " // to store selection range + + "
" // end URL entry modal ; document.getElementById(new_div_name).innerHTML = replybox; @@ -260,3 +396,106 @@ function open_reply_box(prefix, msgnum, is_quoted) { window.getSelection().collapse(tag.firstChild, 0); // positions the cursor }, 0); } + + +// Abort a message post (it simply destroys the div) +function forum_cancel_post(div_name) { + document.getElementById(div_name).outerHTML = ""; // make it cease to exist +} + + +// Save the posted message to the server +function forum_save_message(editor_div_name) { + + document.body.style.cursor = "wait"; + wefw = (document.getElementById("ctdl-replyreferences").innerHTML).replaceAll("|","!"); // references (if present) + subj = document.getElementById("ctdl-subject").innerHTML; // subject (if present) + + url = "/ctdl/r/" + escapeHTMLURI(current_room) + + "/dummy_name_for_new_message" + + "?wefw=" + wefw + + "&subj=" + subj + body_text = "" + document.getElementById("ctdl-editor-body").innerHTML + "\r\n"; + + var request = new XMLHttpRequest(); + request.open("PUT", url, true); + request.setRequestHeader("Content-type", "text/html"); + request.onreadystatechange = function() { + if (request.readyState == 4) { + document.body.style.cursor = "default"; + if (Math.trunc(request.status / 100) == 2) { + headers = request.getAllResponseHeaders().split("\n"); + for (var i in headers) { + if (headers[i].startsWith("etag: ")) { + new_msg_num = headers[i].split(" ")[1]; + } + } + + // After saving the message, load it back from the server and replace the editor div with it. + replace_editor_with_final_message = async() => { + response = await fetch("/ctdl/r/" + escapeHTMLURI(current_room) + "/" + new_msg_num + "/json"); + if (response.ok) { + newly_posted_message = await(response.json()); + forum_render_one(newly_posted_message, document.getElementById(editor_div_name)); + } + } + replace_editor_with_final_message(); + + } + else { + error_message = request.responseText; + if (error_message.length == 0) { + error_message = _("An error has occurred."); + } + alert(error_message); // editor remains open + } + } + }; + request.send(body_text); +} + + +// Bold, italics, etc. +function forum_format(command, value) { + document.execCommand(command, false, value); +} + + +// Make the URL entry box appear. +// When the user clicks into the URL box it will make the previous focus disappear, so we have to save it. +function forum_display_urlbox() { + document.getElementById("forum_selection_start").value = window.getSelection().anchorOffset; + document.getElementById("forum_selection_end").value = window.getSelection().focusOffset; + document.getElementById("forum_url_entry_box").style.display = "block"; +} + + +// When the URL box is closed, this gets called. do_save is true for Save, false for Cancel. +function forum_close_urlbox(do_save) { + if (do_save) { + var tag = document.getElementById("ctdl-editor-body"); + var start_replace = document.getElementById("forum_selection_start").value; // use saved selection range + var end_replace = document.getElementById("forum_selection_end").value; + new_text = tag.innerHTML.substring(0, start_replace) + + "" + + tag.innerHTML.substring(start_replace, end_replace) + + "" + + tag.innerHTML.substring(end_replace); + tag.innerHTML = new_text; + } + document.getElementById("ctdl-forum-urlbox").value = ""; // clear url box for next time + document.getElementById("forum_url_entry_box").style.display = "none"; +} + + +// User has clicked the "Post message" button. This is roughly the same as "reply" except there is no parent message. +function forum_entmsg() { + open_reply_box("ctdl-newmsg-here", false, "", ""); +} + + +// RENDERER FOR THIS VIEW +function view_render_forums() { + document.getElementById("ctdl-main").innerHTML = `
`; + forum_readmessages("ctdl-mrp", 0, 9999999999); +}