diff --git a/webv4/pages/001-forum.xjs b/webv4/pages/001-forum.xjs index 85ace20d863e7fc622e88cba12f8de87f007fb4b..0f2f3eadc23c250d19a13bda74d2497de1b191df 100644 --- a/webv4/pages/001-forum.xjs +++ b/webv4/pages/001-forum.xjs @@ -1,6 +1,6 @@ -<!--HIDDEN:New Forum--> +<!--HIDDEN:XJS Forum--> -<script type="text/javascript" src="./js/forum.js"></script> +<script type="text/javascript" src="./js/xjs-forum.js"></script> <?xjs load(settings.web_lib + 'request.js'); @@ -10,12 +10,67 @@ <!-- To do: Search (forum, group, sub, or thread), Sortation and data-attributes --> +<div data-loading-template hidden> + <li class="list-group-item striped"> + <? locale.write('loading_message', 'main'); ?> + <span data-spinning-cursor></span> + </li> +</div> + +<!-- Viewing a thread (list of messages) --> <? if (Request.has_param('sub') && Request.has_param('thread')) { ?> - <?xjs - var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0; - var thread = getMessageThreads(Request.get_param('sub'), settings.max_messages).thread[Request.get_param('thread')]; - ?> + <? var thread = getMessageThreads(Request.get_param('sub'), settings.max_messages).thread[Request.get_param('thread')]; ?> + + <script type="text/javascript" src="./js/graphics-converter.js"></script> + <script type="text/javascript" src="./js/avatars.js"></script> + + <div hidden> + <li id="forum-message-template" class="list-group-item striped"> + <a data-message-anchor></a> + <h4> + <strong data-message-subject></strong> + </h4> + <div class="clearfix"> + <div class="pull-left" style="margin-right:1em;" data-avatar></div> + <div> + <div data-badge-unread hidden> + <span class="badge unread"> + <? locale.write('label_new_message'); ?> + </span> + </div> + <? locale.write('label_message_from'); ?> + <strong data-message-from></strong><span data-message-from-address></span> + <br /> + <? locale.write('label_message_to'); ?> + <strong data-message-to></strong> + <br /> + <? locale.write('label_message_date'); ?> + <strong data-message-date></strong> + </div> + </div> + <div class="message" data-message-body></div> + <div class="clearfix"> + <button class="btn btn-default icon" title="<? locale.write('label_delete'); ?>" aria-label="<? locale.write('label_delete'); ?>"> + <span class="glyphicon glyphicon-trash"></span> + </button> + <a class="btn btn-default icon" title="<? locale.write('label_direct_link'); ?>" aria-label="<? locale.write('label_direct_link'); ?>" href="#" data-direct-link> + <span class="glyphicon glyphicon-link"></span> + </a> + <button class="btn btn-default icon" title="<? locale.write('label_reply'); ?>" aria-label="<? locale.write('label_reply'); ?>"> + <span class="glyphicon glyphicon-comment"></span> + </button> + <button class="btn-uv btn btn-default icon" data-button-upvote> + <span title="Upvotes" class="glyphicon glyphicon-arrow-up"></span> + <span title="Upvotes" data-upvote-count>0</span> + </button> + <button class="btn-dv btn btn-default icon" data-button-downvote> + <span title="Downvotes" class="glyphicon glyphicon-arrow-down"></span> + <span title="Downvotes" data-downvote-count>0</span> + </button> + </div> + </li> + </div> <ol class="breadcrumb"> <li> @@ -40,19 +95,185 @@ </li> </ol> - <div id="jump-unread-container" style="margin-bottom:1em;"> - <a class="btn btn-default" id="jump-unread" title="Jump to first unread message" href="#"> - <span class="glyphicon glyphicon-star"></span> + <div id="jump-unread-container" style="margin-bottom:1em;" hidden> + <a class="btn btn-default" id="jump-unread" title="<? locale.write('button_jump_to_unread'); ?>" href="#"> + <? locale.write('label_new_message'); ?> </a> </div> - <div id="forum-list-container" class="list-group"> + <div id="forum-list-container" class="list-group"></div> + <div id="forum-load-more-messages" class="text-center" hidden> + <button id="load-more-messages" class="btn btn-primary" aria-label="<? locale.write('button_load_more', 'main'); ?>" title="<? locale.write('button_load_more', 'main'); ?>" onclick="listMessages('<? Request.write_param('sub'); ?>', '<? Request.write_param('thread'); ?>', <? write(settings.page_size); ?>, true)"> + <? locale.write('button_load_more', 'main'); ?> + </button> + </div> - </div> + <script type="text/javascript"> + listMessages('<? Request.write_param('sub'); ?>', <? Request.write_param('thread'); ?>, <? write(settings.page_size); ?>); + // Should subscribe here for updates to the thread; need to add some functions to lib/forum.js, lib/events/forum.js, root/js/xjs-forum.js + </script> + + <!-- get n messages of thread and append to forum-list-container --> +<!-- Viewing a sub (list of threads) --> <? } else if (Request.has_param('sub') && msg_area.sub[Request.get_param('sub')] !== undefined) { ?> - <ol class="breadcrumb"> + <!-- Template for a link to a thread --> + <div hidden> + <a id="forum-thread-link-template" href="./?page=<? Request.write_param('page'); ?>&sub=<? Request.write_param('sub'); ?>" class="list-group-item striped"> + <div class="row"> + <div class="col-sm-9"> + <strong data-thread-subject></strong> + <p> + <? locale.write('label_thread_from'); ?> + <strong data-thread-from></strong> + <? locale.write('label_message_date'); ?> + <span data-thread-date-start></span> + </p> + <div data-replies hidden> + <strong data-message-count></strong> + <span data-suffix-reply hidden><? locale.write('suffix_reply_count'); ?>.</span> + <span data-suffix-replies hidden><? locale.write('suffix_replies_count'); ?>.</span> + <? locale.write('label_thread_latest_reply'); ?> + <strong data-last-from></strong> + <? locale.write('label_message_date'); ?> + <span data-last-time></span> + </div> + </div> + <div class="col-sm-3"> + <div class="pull-right" data-stats hidden> + <? if (is_user()) { ?> + <span data-unread-messages title="<? locale.write('badge_unread_messages'); ?>" class="badge" hidden></span> + <? } ?> + <span data-upvotes-badge title="<? locale.write('badge_upvotes'); ?>" class="badge upvote-bg" style="display:none;"> + <span class="glyphicon glyphicon-arrow-up"></span> + <span data-upvotes></span> + </span> + <span data-downvotes-badge title="<? locale.write('badge_downvotes'); ?>" class="badge downvote-bg" style="display:none;"> + <span class="glyphicon glyphicon-arrow-down"></span> + <span data-downvotes></span> + </span> + </div> + </div> + </div> + </a> + </div> + + <!-- Template for a new message input --> + <div id="forum-new-message-template" hidden> + <input class="form-control" type="text" placeholder="<? locale.write('label_message_to'); ?>" /> + <br /> + <input class="form-control" type="text" placeholder="<? locale.write('label_message_subject'); ?>" /> + <br /> + <textarea class="form-control" rows="8"></textarea> + <br /> + <input class="btn btn-primary" type="submit" value="<? locale.write('button_submit', 'main') ?>" onclick="postNew('SUB')" /> + </div> + + <!-- Template for a message reply input --> + <div id="forum-message-reply-template" class="reply" hidden> + <strong> + <? locale.write('label_form_reply'); ?> + </strong> + <textarea rows="8" class="form-control reply" id="replytext-ID"></textarea> + <button id="quote-ID" class="btn" onclick="quotify('ID')"> + <? locale.write('button_quote_message'); ?> + </button> + <input id="reply-button-ID" class="btn btn-primary" type="submit" value="Submit" onclick="postReply('SUB', 'ID')" /> + </div> + + <!-- Template for adding a new poll --> + <div id="forum-new-poll-template" hidden> + <strong> + <p><? locale.write('label_poll_add'); ?></p> + </strong> + <form id="newpoll-form-template" class="form-horizontal"> + <div class="clearfix"> + <strong> + <? locale.write('label_poll_add_question'); ?> + </strong> + <br /> + <div class="col-sm-10 form-group"> + <input id="newpoll-subject-template" class="form-control" type="text" placeholder="<? locale.write('placeholder_required', 'main'); ?>" maxlength="70" /> + </div> + </div> + <div id="newpoll-comment-group-template"> + <strong> + <? locale.write('label_poll_add_comments'); ?> + </strong> + <br /> + </div> + <div class="form-group"> + <label for="newpoll-answers-template" class="col-sm-2 control-label"> + <? locale.write('label_poll_add_selection'); ?> + </label> + <div class="col-sm-10"> + <label class="radio-inline"> + <input type="radio" name="newpoll-answers-template" value="1" checked /> <? locale.write('label_poll_selection_single'); ?> + </label> + <label class="radio-inline"> + <input type="radio" name="newpoll-answers-template" value="2" /> <? locale.write('label_poll_selection_multiple'); ?> + <input type="number" name="newpoll-answer-count-template" min="1" max="15" value="1" /> + </label> + </div> + </div> + <div class="form-group"> + <label for="newpoll-results-template" class="col-sm-2 control-label"> + <? locale.write('label_poll_show_results'); ?> + </label> + <div class="col-sm-10"> + <label class="radio-inline"> + <input type="radio" name="newpoll-results-template" value="0" checked /> <? locale.write('label_poll_show_results_voters'); ?> + </label> + <label class="radio-inline"> + <input type="radio" name="newpoll-results-template" value="1" /> <? locale.write('label_poll_show_results_everyone'); ?> + </label> + <label class="radio-inline"> + <input type="radio" name="newpoll-results-template" value="2" /> <? locale.write('label_poll_show_results_me_only_a'); ?> + </label> + <label class="radio-inline"> + <input type="radio" name="newpoll-results-template" value="3" /> <? locale.write('label_poll_show_results_me_only_b'); ?> + </label> + </div> + </div> + <div id="newpoll-answer-group-template"> + <strong> + <? locale.write('label_poll_add_answers'); ?> + </strong> + <br /> + </div> + <div id="newpoll-button-template" class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button id="newpoll-submit-template" type="button" class="btn btn-primary" onclick="postNewPoll('SUB')"> + <? locale.write('button_submit', 'main'); ?> + </button> + <div class="pull-right"> + <button type="button" title="<? locale.write('button_poll_add_comment'); ?>" class="btn btn-success" onclick="addPollField('comment', 'newpoll-comment-group')"> + <span class="glyphicon glyphicon-pencil"></span> + </button> + <button type="button" title="<? locale.write('button_poll_add_answer'); ?>" class="btn btn-success" onclick="addPollField('answer', 'newpoll-answer-group')"> + <span class="glyphicon glyphicon-plus"></span> + </button> + </div> + </div> + </div> + </form> + </div> + + <!-- Template for adding a poll field (comment or answer) --> + <div id="forum-new-poll-field-container-template" name="forum-new-poll-TYPE" class="form-group" hidden> + <div class="col-sm-9"> + <input id="forum-new-poll-field-TYPE-NUMBER-template" class="form-control" name="forum-new-poll-field-TYPE" type="text" maxlength="70"> + </div> + <div class="col-sm-1"> + <button type="button" class="btn btn-danger" onclick="$('#forum-new-poll-field-container-template').remove()"> + <span class="glyphicon glyphicon-remove"></span> + </button> + </div> + </div> + + + <ol class="breadcrumb"> <li> <a href="./?page=<? Request.write_param('page'); ?>"> <? locale.write('title'); ?> @@ -70,11 +291,7 @@ </li> </ol> - <?xjs - var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0; - var threads = listThreads(Request.get_param('sub'), offset * settings.page_size, settings.page_size, true) || { total: 0, threads: [] }; - var sub = msg_area.sub[Request.get_param('sub')]; - ?> + <? var sub = msg_area.sub[Request.get_param('sub')]; ?> <button class="btn btn-default icon" aria-label="<? locale.write('button_post_message'); ?>" title="<? locale.write('button_post_message'); ?>" onclick="addNew('<? write(sub.code); ?>')"> <span class="glyphicon glyphicon-pencil"></span> @@ -95,80 +312,12 @@ </button> </div> - <div id="forum-thread-replies-template" hidden> - <p> - <strong data-message-count></strong> - <span data-suffix-reply hidden><? locale.write('suffix_reply_count'); ?>.</span> - <span data-suffix-replies hidden><? locale.write('suffix_replies_count'); ?>.</span> - <? locale.write('label_thread_latest_reply'); ?> - <strong data-last-from></strong> - <span data-last-time></span> - </p> - </div> - - <div id="forum-thread-stats-template" hidden> - <? if (is_user()) { ?> - <span data-unread-messages title="<? locale.write('badge_unread_messages'); ?>" class="badge <? write(sub.scan_cfg&SCAN_CFG_NEW || sub.scan_cfg&SCAN_CFG_YONLY ? 'scanned' : ''); ?>" hidden></span> - <? } ?> - <span data-upvotes-badge title="<? locale.write('badge_upvotes'); ?>" class="badge upvote-bg" style="display:none;"> - <span class="glyphicon glyphicon-arrow-up"></span> - <span data-upvotes></span> - </span> - <span data-downvotes-badge title="<? locale.write('badge_downvotes'); ?>" class="badge downvote-bg" style="display:none;"> - <span class="glyphicon glyphicon-arrow-down"></span> - <span data-downvotes></span> - </span> - </div> - - <div id="forum-list-container" class="list-group"> - <? threads.threads.forEach(function (e) { ?> - <a href="./?page=<? Request.write_param('page'); ?>&sub=<? Request.write_param('sub'); ?>&thread=<? write(e.id); ?>" class="list-group-item striped"> - <div class="row"> - <div id="left-<? write(e.id); ?>" class="col-sm-9"> - <strong><? write(e.subject); ?></strong> - <p><? locale.write('label_thread_from'); ?> <strong><? write(e.first.from); ?></strong> <? locale.write('label_message_date'); ?> <? write(system.timestr(e.first.when_written_time)); ?></p> - </div> - <div class="col-sm-3"> - <div id="right-<? write(e.id); ?>" class="pull-right"></div> - </div> - </div> - </a> - <? }); ?> - </div> - - <? if (threads.total / settings.page_size > 1) { ?> - <? var qs = http_request.query_string.replace(/&offset=\d+/g, ''); ?> - <div class="btn-group"> - <? if (offset > 0) { ?> - <a href="./?<? write(qs); ?>&offset=0" role="button" class="btn btn-default" title="<? locale.write('button_thread_first_page'); ?>"> - <span class="glyphicon glyphicon-fast-backward"></span> - </a> - <a href="./?<? write(qs); ?>&offset=<? write(Math.max(0, offset - 9)); ?>" role="button" class="btn btn-default" title="<? locale.writef('button_thread_back_pages', Math.min(9, offset)); ?>"> - <span class="glyphicon glyphicon-backward"></span> - </a> - <a href="./?<? write(qs); ?>&offset=<? write(Math.max(0, offset - 1)); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_previous_page'); ?>"> - <span class="glyphicon glyphicon-step-backward"></span> - </a> - <? } ?> - <a href="./?<? write(qs); ?>&offset=<? write(offset); ?>" role="button" class="btn btn-default" disabled> - <? write(offset + 1); ?> - </a> - <? for (var n = offset + 1; n < threads.total / settings.page_size && n - offset < 9; n++) { ?> - <a href="./?<? write(qs); ?>&offset=<? write(n); ?>" role="button" class="btn btn-default"><? write(n + 1); ?></a> - <? } ?> - <? if (threads.total / settings.page_size > offset + 9) { ?> - <a href="./?<? write(qs); ?>&offset=<? write(offset + 1); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_next_page'); ?>"> - <span class="glyphicon glyphicon-step-forward"></span> - </a> - <a href="./?<? write(qs); ?>&offset=<? write(offset + 9); ?>" role="button" class="btn btn-default" title="<? locale.writef('button_thread_forward_pages', Math.min(9, Math.floor(threads.total / settings.page_size))); ?>"> - <span class="glyphicon glyphicon-forward"></span> - </a> - <a href="./?<? write(qs); ?>&offset=<? write(Math.floor(threads.total / settings.page_size)); ?>" role="button" class="btn btn-default" title="<? locale.write('button_thread_last_page'); ?>"> - <span class="glyphicon glyphicon-fast-forward"></span> - </a> - <? } ?> - </div class="btn-group"> - <? } ?> + <div id="forum-list-container" class="list-group"></div> + <div id="forum-load-more-threads" class="text-center" hidden> + <button id="load-more-threads" class="btn btn-primary" aria-label="<? locale.write('button_load_more', 'main'); ?>" title="<? locale.write('button_load_more', 'main'); ?>" onclick="listThreads('<? write(sub.code); ?>', <? write(settings.page_size); ?>, true)"> + <? locale.write('button_load_more', 'main'); ?> + </button> + </div> <script type="text/javascript"> @@ -177,15 +326,35 @@ if (data.type != 'threads') return; onThreadStats(data.data); }, { - threads: '<? Request.write_param("sub"); ?>', - offset: '<? write(offset); ?>', - page_size: '<? write(settings.page_size); ?>', - }); + sub: '<? Request.write_param("sub"); ?>', + }); + + listThreads('<? write(sub.code); ?>', <? write(settings.page_size); ?>); </script> +<!-- Viewing a group (list of subs) --> <? } else if (Request.has_param('group') && msg_area.grp_list[Request.get_param('group')] !== undefined) { ?> + <div hidden> + <a id="forum-sub-link-template" href="./?page=<? Request.write_param('page'); ?>" class="list-group-item striped"> + <h4> + <strong data-sub-name></strong> + </h4> + <span title="<? locale.write('badge_unread_messages'); ?>" class="badge ignored" data-unread-unscanned></span> + <span title="<? locale.write('badge_unread_messages'); ?>" class="badge scanned" data-unread-scanned></span> + <p data-sub-description></p> + <span data-newest-message-container hidden> + <? locale.write('label_sub_newest_message'); ?>: + <strong data-newest-message-subject></strong> + <? locale.write('label_thread_from'); ?> + <span data-newest-message-from></span> + <? locale.write('label_message_date'); ?> + <span data-newest-message-date></span> + </span> + </a> + </div> + <ol class="breadcrumb"> <li> <a href="./?page=<? Request.write_param('page'); ?>"> @@ -199,48 +368,35 @@ </li> </ol> - <div id="forum-list-container" class="list-group"> - <? var subs = listSubs(Request.get_param('group')); ?> - <? subs.forEach(function (e) { ?> - <a href="./?page=<? Request.write_param('page'); ?>&sub=<? write(e.code); ?>" class="list-group-item striped"> - <h4> - <strong> - <? write(e.name); ?> - </strong> - </h4> - <span title="<? locale.write('badge_unread_messages'); ?>" class="badge <? write(e.scan_cfg&SCAN_CFG_NEW || e.scan_cfg&SCAN_CFG_YONLY ? 'scanned' : 'total'); ?>" id="badge-<? write(e.code); ?>"></span> - <p><? write(e.description); ?></p> - <span id="newest-msg-<? write(e.code); ?>"></span> - </a> - <? }); ?> - </div> + <div id="forum-list-container" class="list-group"></div> - <? if (is_user()) { ?> - <script type="text/javascript"> + <script type="text/javascript"> + (async () => await listSubs('<? Request.write_param('group'); ?>'))(); + getNewestMessagePerSub('<? Request.write_param('group'); ?>'); + <? if (is_user()) { ?> registerEventListener('forum', e => { const data = JSON.parse(e.data); if (data.type != 'subs_unread') return; onSubUnreadCount(data.data); }, { subs_unread: '<? Request.write_param("group"); ?>' }); - </script> - <? } ?> - - <script type="text/javascript"> - (async function getNewestMessagePerSub() { - try { - const response = await fetch('./api/forum.ssjs?call=get-newest-message-per-sub&group=<? Request.write_param('group'); ?>'); - const data = await response.json(); - Object.entries(data).forEach(e => { - document.getElementById(`newest-msg-${e[0]}`).innerHTML = `<? locale.write('label_sub_newest_message'); ?>: ${e[1].subject}, <? locale.write('label_thread_from'); ?> ${e[1].from} <? locale.write('label_message_date'); ?> ${new Date(e[1].when_written_time * 1000)}`; - }); - } catch (err) { - console.error('Error fetching latest messages', err); - } - })(); + <? } ?> </script> +<!-- Viewing the group list --> <? } else { ?> + <div hidden> + <a id="forum-group-link-template" href="./?page=<? Request.write_param('page'); ?>" class="list-group-item striped"> + <h3> + <strong data-group-name></strong> + </h3> + <span title="<? locale.write('badge_unread_messages'); ?>" class="badge ignored" data-unread-unscanned></span> + <span title="<? locale.write('badge_unread_messages'); ?>" class="badge scanned" data-unread-scanned></span> + <span data-group-description></span>: + <span data-group-sub-count></span> <? locale.write('sub_boards'); ?> + </a> + </div> + <ol class="breadcrumb"> <li> <a href="./?page=<? Request.write_param('page'); ?>"> @@ -249,31 +405,16 @@ </li> </ol> - <div id="forum-list-container" class="list-group"> - <? const groups = listGroups(); ?> - <? groups.forEach(function (e) { ?> - <a href="./?page=<? Request.write_param('page'); ?>&group=<? write(e.index); ?>" class="list-group-item striped"> - <h3> - <strong> - <? write(e.name); ?> - </strong> - </h3> - <span title="<? locale.write('badge_unread_messages'); ?>" class="badge ignored" id="badge-ignored-<? write(e.index); ?>"></span> - <span title="<? locale.write('badge_unread_messages'); ?>" class="badge scanned" id="badge-scanned-<? write(e.index); ?>"></span> - <p> - <? write(e.description); ?>: <? write(e.sub_count); ?> <? locale.write('sub_boards'); ?> - </p> - </a> - <? }); ?> - </div> + <div id="forum-list-container" class="list-group"></div> <? if (is_user()) { ?> <script type="text/javascript"> + (async () => await listGroups())(); registerEventListener('forum', e => { const data = JSON.parse(e.data); if (data.type != 'groups_unread') return; onGroupUnreadCount(data.data); - }, { groups_unread: null }); + }, { groups_unread: null }); </script> <? } ?>