diff --git a/webv4/lib/events/forum.js b/webv4/lib/events/forum.js index 96bf879c08851a2aee27ba4b11850bb5c427579c..c7719bf32ffdb3e80aed289b10ced3fb4d1cf526 100644 --- a/webv4/lib/events/forum.js +++ b/webv4/lib/events/forum.js @@ -4,6 +4,7 @@ var last_subs; var last_groups; var last_threads; var last_run = 0; +const is_real_user = is_user(); const frequency = (settings.refresh_interval || 60000) / 1000; // Where 'a' is the previous data and 'b' is new @@ -46,7 +47,7 @@ function scan_subs(group) { } function scan_threads(sub, offset, page_size) { - const scan = getThreadStats(sub, offset, page_size); + const scan = getThreadStats(sub, offset, page_size, !is_real_user); if (!last_threads) { forum_emit('threads', scan); } else { @@ -66,11 +67,10 @@ function scan_threads(sub, offset, page_size) { } function cycle() { - if (!is_user()) return; if (time() - last_run <= frequency) return; last_run = time(); - if (Request.has_param('groups_unread')) scan_groups(); - if (Request.has_param('subs_unread')) scan_subs(Request.get_param('subs_unread')); + if (is_real_user && Request.has_param('groups_unread')) scan_groups(); + if (is_real_user && Request.has_param('subs_unread')) scan_subs(Request.get_param('subs_unread')); if (Request.has_param('threads')) scan_threads(Request.get_param('threads'), Request.get_param('offset'), Request.get_param('page_size')); } diff --git a/webv4/lib/forum.js b/webv4/lib/forum.js index 2793ec4f979f391db43e047372f98170b7f919ae..630e51543c99c1088e68edcbd56ab67c91ca7a8a 100644 --- a/webv4/lib/forum.js +++ b/webv4/lib/forum.js @@ -45,15 +45,13 @@ function listSubs(group) { }); } -function listThreads(sub, offset, count, page_offset) { +function listThreads(sub, offset, count) { offset = parseInt(offset); if (isNaN(offset) || offset < 0) return false; count = parseInt(count); if (isNaN(count) || count < 1) return false; - if (page_offset) offset = offset * count; - var threads = getMessageThreads(sub, settings.max_messages); if (offset >= threads.order.length) return false; @@ -72,10 +70,40 @@ function listThreads(sub, offset, count, page_offset) { } +function getNewestMessageInSub(sub) { + const mb = new MsgBase(sub.code); + if (!mb.open()) return; + var h; + var ret; + for (var m = mb.last_msg; m >= mb.first_msg; m--) { + h = mb.get_msg_header(m); + if (h === null) continue; + ret = { + from: h.from, + subject: h.subject, + when_written_time: h.when_written_time, + }; + break; + } + mb.close(); + return ret; +} + +function getNewestMessagePerSub(grp) { + grp = parseInt(grp, 10); + if (isNaN(grp) || grp < 0 || !msg_area.grp_list[grp]) return []; + return msg_area.grp_list[grp].sub_list.reduce(function (a, c) { + const s = getNewestMessageInSub(c); + if (s !== undefined) a[c.code] = s; + log(LOG_DEBUG, JSON.stringify(s)); + return a; + }, {}); +} + function getSubUnreadCount(sub) { var ret = { - scanned : 0, - total : 0 + scanned: 0, + total: 0 }; if (typeof msg_area.sub[sub] === 'undefined') return ret; try { @@ -157,7 +185,7 @@ function getThreadVoteTotals(thread, mkeys) { } // { [thread_id]: { total, unread, votes: { up: { parent, total }, down: { parent, total }, total }, newest } } -function getThreadStats(sub, offset, page_size) { +function getThreadStats(sub, offset, page_size, guest) { const threads = getMessageThreads(sub, settings.max_messages); const ret = {}; if (!offset) offset = 0; @@ -169,7 +197,7 @@ function getThreadStats(sub, offset, page_size) { var mkeys = Object.keys(thread.messages); ret[threads.order[n]] = { total: mkeys.length, - unread: getUnreadInThread(sub, thread, mkeys), + unread: guest ? 0 : getUnreadInThread(sub, thread, mkeys), votes: getThreadVoteTotals(thread, mkeys), newest: { from: thread.messages[mkeys[mkeys.length - 1]].from, diff --git a/webv4/lib/locale/en_us.ini b/webv4/lib/locale/en_us.ini index d231320aa24ac4d6f163a9731ef62d2249b0d262..fa9bfb0e921a51334734db2433ea0d345ac6e872 100644 --- a/webv4/lib/locale/en_us.ini +++ b/webv4/lib/locale/en_us.ini @@ -74,6 +74,7 @@ label_message_to = To label_message_date = on label_message_subject = Subject label_thread_latest_reply = Latest reply from +label_sub_newest_message = Latest message suffix_reply_count = reply suffix_replies_count = replies badge_poll = POLL diff --git a/webv4/pages/001-forum.xjs b/webv4/pages/001-forum.xjs index 2f40948612a3618cecbc8142a00a00eb5c465efe..85ace20d863e7fc622e88cba12f8de87f007fb4b 100644 --- a/webv4/pages/001-forum.xjs +++ b/webv4/pages/001-forum.xjs @@ -10,11 +10,11 @@ <!-- To do: Search (forum, group, sub, or thread), Sortation and data-attributes --> -<?xjs if (Request.has_param('sub') && Request.has_param('thread')) { ?> +<? if (Request.has_param('sub') && Request.has_param('thread')) { ?> <?xjs var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0; - const 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')]; ?> <ol class="breadcrumb"> @@ -50,7 +50,7 @@ </div> -<?xjs } else if (Request.has_param('sub') && typeof msg_area.sub[Request.get_param('sub')] != 'undefined') { ?> +<? } else if (Request.has_param('sub') && msg_area.sub[Request.get_param('sub')] !== undefined) { ?> <ol class="breadcrumb"> <li> @@ -72,8 +72,8 @@ <?xjs var offset = Request.has_param('offset') ? parseInt(Request.get_param('offset')) : 0; - const threads = listThreads(Request.get_param('sub'), offset, settings.page_size, true) || { total: 0, threads: [] }; - const sub = msg_area.sub[Request.get_param('sub')]; + 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')]; ?> <button class="btn btn-default icon" aria-label="<? locale.write('button_post_message'); ?>" title="<? locale.write('button_post_message'); ?>" onclick="addNew('<? write(sub.code); ?>')"> @@ -107,9 +107,9 @@ </div> <div id="forum-thread-stats-template" hidden> - <?xjs if (is_user()) { ?> + <? 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> - <?xjs } ?> + <? } ?> <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> @@ -121,25 +121,25 @@ </div> <div id="forum-list-container" class="list-group"> - <?xjs threads.threads.forEach(function (e) { ?> + <? 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><strong><? write(e.first.from); ?></strong>, <? write(system.timestr(e.first.when_written_time)); ?></p> + <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> - <?xjs }); ?> + <? }); ?> </div> - <?xjs if (threads.total / settings.page_size > 1) { ?> - <?xjs qs = http_request.query_string.replace(/&offset=\d+/g, ''); ?> + <? if (threads.total / settings.page_size > 1) { ?> + <? var qs = http_request.query_string.replace(/&offset=\d+/g, ''); ?> <div class="btn-group"> - <?xjs if (offset > 0) { ?> + <? 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> @@ -149,14 +149,14 @@ <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> - <?xjs } ?> + <? } ?> <a href="./?<? write(qs); ?>&offset=<? write(offset); ?>" role="button" class="btn btn-default" disabled> <? write(offset + 1); ?> </a> - <?xjs for (var n = offset + 1; n < threads.total / settings.page_size && n - offset < 9; n++) { ?> + <? 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> - <?xjs } ?> - <?xjs if (threads.total / settings.page_size > offset + 9) { ?> + <? } ?> + <? 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> @@ -166,23 +166,25 @@ <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> - <?xjs } ?> + <? } ?> </div class="btn-group"> - <?xjs } ?> + <? } ?> <script type="text/javascript"> - registerEventListener('forum', function (e) { + + registerEventListener('forum', e => { const data = JSON.parse(e.data); if (data.type != 'threads') return; onThreadStats(data.data); }, { threads: '<? Request.write_param("sub"); ?>', offset: '<? write(offset); ?>', - page_size: '<? write(settings.page_size); ?>' + page_size: '<? write(settings.page_size); ?>', }); + </script> -<?xjs } else if (Request.has_param('group') && typeof msg_area.grp_list[Request.get_param('group')] != 'undefined') { ?> +<? } else if (Request.has_param('group') && msg_area.grp_list[Request.get_param('group')] !== undefined) { ?> <ol class="breadcrumb"> <li> @@ -198,54 +200,81 @@ </ol> <div id="forum-list-container" class="list-group"> - <?xjs const subs = listSubs(Request.get_param('group')); ?> - <?xjs subs.forEach(function (e) { ?> + <? 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> + <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> - <?xjs }); ?> + <? }); ?> </div> - <?xjs if (is_user()) { ?> + <? if (is_user()) { ?> <script type="text/javascript"> - registerEventListener('forum', function (e) { + 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> - <?xjs } ?> + <? } ?> + + <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> -<?xjs } else { ?> +<? } else { ?> <ol class="breadcrumb"> <li> - <a href="./?page=<? Request.write_param('page'); ?>"><? locale.write('title'); ?></a> + <a href="./?page=<? Request.write_param('page'); ?>"> + <? locale.write('title'); ?> + </a> </li> </ol> <div id="forum-list-container" class="list-group"> - <?xjs const groups = listGroups(); ?> - <?xjs groups.forEach(function (e) { ?> + <? 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> + <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> + <p> + <? write(e.description); ?>: <? write(e.sub_count); ?> <? locale.write('sub_boards'); ?> + </p> </a> - <?xjs }); ?> + <? }); ?> </div> - <?xjs if (is_user()) { ?> + <? if (is_user()) { ?> <script type="text/javascript"> - registerEventListener('forum', function (e) { + registerEventListener('forum', e => { const data = JSON.parse(e.data); if (data.type != 'groups_unread') return; onGroupUnreadCount(data.data); }, { groups_unread: null }); </script> - <?xjs } ?> + <? } ?> -<?xjs } ?> +<? } ?> diff --git a/webv4/root/api/forum.ssjs b/webv4/root/api/forum.ssjs index 154c36fed6021b45b7b4937a2e29af0488699d09..5eb01d9364e1c2969652be19712ff078f34e3a37 100644 --- a/webv4/root/api/forum.ssjs +++ b/webv4/root/api/forum.ssjs @@ -175,6 +175,10 @@ if (Request.has_param('call') && (http_request.method === 'GET' || http_request. } break; + case 'get-newest-message-per-sub': + if (Request.has_param('group')) reply = getNewestMessagePerSub(Request.get_param('group')); + break; + default: break;