From 4d9f3d64cb2fdcc40dc0f42de3798e7184a987b0 Mon Sep 17 00:00:00 2001 From: Boudbois2271 Date: Sun, 24 May 2026 18:52:04 +0200 Subject: [PATCH 1/2] Fix meetings tab: correct week navigation, reader UX improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix MWB month calculation (inverted condition + missing +1 for 1-indexed month) - MWB and Watchtower now fetch TOC and open to current week/article instead of cover or index - Add getCurrentMWBWeek(): parses first week start date, computes week offset from today - Add getCurrentWTArticle(): finds first Sunday of study month, computes article offset - Show week/article title in link text (e.g. 'Life and Ministry — May 18-24') - Show loading state while fetches are in flight - Show offline error with link to /publications.html if publication not cached - Fix missing closing tag in meetings.html - Add hashchange listener in reader.html so sidebar links work while already on reader - Add Back button in reader.html navbar (history.back()) Co-Authored-By: Claude Sonnet 4.6 --- webui/html/meetings.html | 1 + webui/html/reader.html | 5 ++ webui/html/static/meethelper.js | 107 ++++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 20 deletions(-) diff --git a/webui/html/meetings.html b/webui/html/meetings.html index 0871117..305eead 100644 --- a/webui/html/meetings.html +++ b/webui/html/meetings.html @@ -50,6 +50,7 @@ Life and Ministry
Watchtower + diff --git a/webui/html/reader.html b/webui/html/reader.html index ebfcf2c..52a1f90 100644 --- a/webui/html/reader.html +++ b/webui/html/reader.html @@ -40,6 +40,7 @@
@@ -64,6 +65,10 @@ page = location.hash.substr(1) iframe = document.getElementById('reader') iframe.src = page + // Navigate the iframe when the URL hash changes (e.g. clicking sidebar links while already on reader.html) + window.addEventListener('hashchange', function() { + iframe.src = location.hash.substr(1) + }) // Update the iframe's link iframe.onload = (() => { //console.log(this) diff --git a/webui/html/static/meethelper.js b/webui/html/static/meethelper.js index a62ba0e..01088b9 100644 --- a/webui/html/static/meethelper.js +++ b/webui/html/static/meethelper.js @@ -38,33 +38,100 @@ // with mwb things are easier // 21.01 is used in 21.01 and 21.02 function setMeeting() { + // Show loading state immediately while fetches are in flight + document.getElementById("mwb_link").textContent = "Life and Ministry — Loading..." + document.getElementById("wt_link").textContent = "Watchtower — Loading..." + // Watchtower - date = getThisSunday() + // Save the original Sunday before rolling back 2 months for the publication code + var thisSunday = getThisSunday() + var date = new Date(thisSunday) date.setMonth(date.getMonth() - 2) - month = ("00" + (date.getMonth() + 1)).slice(-2) - year = date.getFullYear() - wt = "w_"+year+month + var month = ("00" + (date.getMonth() + 1)).slice(-2) + var year = date.getFullYear() + var wt = "w_"+year+month fetch("/api/publications_json/"+wt+"/toc.xhtml") .then(response => response.json()) - .then((response) => { - toc = response.html.body.section.nav[0].ol.li[1].a['-href'] - wtlink = document.getElementById("wt_link") - wtlink.href = "/reader.html#/api/publications/"+wt+"/"+toc + .then((response) => { + var items = response.html.body.section.nav[0].ol.li + var result = getCurrentWTArticle(items, thisSunday) + var wtlink = document.getElementById("wt_link") + wtlink.href = "/reader.html#/api/publications/"+wt+"/"+result.href + wtlink.textContent = "Watchtower — " + result.title + }) + .catch(() => { + var wtlink = document.getElementById("wt_link") + wtlink.textContent = "Watchtower — not available offline (open it once with internet)" + wtlink.href = "/publications.html" }) - // Now I need: - // mwb_202105 - mwbdate = getThisSunday() - if (((mwbdate.getMonth()) % 2) === 0) { + + // MWB + // MWB is bimonthly: mwb_YYYY01 covers Jan+Feb, mwb_YYYY03 covers Mar+Apr, etc. + // getMonth() is 0-indexed: odd 0-indexed = 2nd month of pair → go back 1 + var mwbdate = getThisSunday() + if ((mwbdate.getMonth() % 2) === 1) { mwbdate.setMonth(mwbdate.getMonth() - 1) - } else { - mwbdate.setMonth(mwbdate.getMonth()) } - mwbmonth = ("00" + (mwbdate.getMonth())).slice(-2) - mwbyear = mwbdate.getFullYear() - mwb = "mwb_"+mwbyear+mwbmonth - console.log(mwb) - mwblink = document.getElementById("mwb_link") - mwblink.href = "/reader.html#/api/publications/"+mwb+"/" + var mwbmonth = ("00" + (mwbdate.getMonth() + 1)).slice(-2) + var mwbyear = mwbdate.getFullYear() + var mwb = "mwb_"+mwbyear+mwbmonth + fetch("/api/publications_json/"+mwb+"/toc.xhtml") + .then(response => response.json()) + .then((response) => { + var items = response.html.body.section.nav[0].ol.li + var result = getCurrentMWBWeek(items) + var mwblink = document.getElementById("mwb_link") + mwblink.href = "/reader.html#/api/publications/"+mwb+"/"+result.href + mwblink.textContent = "Life and Ministry — " + result.title + }) + .catch(() => { + var mwblink = document.getElementById("mwb_link") + mwblink.textContent = "Life and Ministry — not available offline (open it once with internet)" + mwblink.href = "/publications.html" + }) +} + +// Returns the href of the current week's entry in the MWB table of contents. +// items[0] = publication cover, items[1..N-1] = weekly entries, items[N] = page nav. +// Parses the start date from items[1]'s title (e.g. "May 4-10") and computes +// the week offset from today so the link always opens to the current week. +function getCurrentMWBWeek(items) { + var fallback = {href: items[1].a['-href'], title: items[1].a['#content']}; + if (!items || items.length < 2) return fallback; + var firstTitle = items[1].a['#content'] || ''; + var match = firstTitle.match(/([A-Za-z]+)\s+(\d+)/); + if (!match) return fallback; + var monthNames = { + January:0, February:1, March:2, April:3, + May:4, June:5, July:6, August:7, + September:8, October:9, November:10, December:11 + }; + var monthNum = monthNames[match[1]]; + if (monthNum === undefined) return fallback; + var today = new Date(); + var firstWeekStart = new Date(today.getFullYear(), monthNum, parseInt(match[2])); + if (firstWeekStart > today) firstWeekStart.setFullYear(today.getFullYear() - 1); + var weekOffset = Math.floor((today - firstWeekStart) / 604800000); // ms per week + var maxWeek = items.length - 2; // exclude cover (0) and page nav (last) + var weekIndex = Math.min(Math.max(1, 1 + weekOffset), maxWeek); + return {href: items[weekIndex].a['-href'], title: items[weekIndex].a['#content']}; +} + +// Returns the href of the current week's study article in the Watchtower TOC. +// items[0] = cover, items[1] = Table of Contents, items[2..N-2] = study articles, items[N-1] = page nav. +// Finds the first Sunday of the study month (= current month, before the 2-month rollback), +// then computes the week offset to determine the correct article. +function getCurrentWTArticle(items, thisSunday) { + var fallback = {href: items[2].a['-href'], title: items[2].a['#content']}; + if (!items || items.length < 3) return fallback; + var firstDay = new Date(thisSunday.getFullYear(), thisSunday.getMonth(), 1); + var dow = firstDay.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat + var firstSunday = new Date(firstDay); + firstSunday.setDate(1 + (dow === 0 ? 0 : 7 - dow)); + var weekOffset = Math.round((thisSunday - firstSunday) / 604800000); // ms per week + var maxArticle = items.length - 3; // articles are items[2..length-2], skip cover+toc+pagenav + var articleIndex = Math.min(Math.max(0, weekOffset), maxArticle); + return {href: items[2 + articleIndex].a['-href'], title: items[2 + articleIndex].a['#content']}; } function getThisSunday() { From 6cf1951d790fcc800ca8ecc56752ea85ac64788d Mon Sep 17 00:00:00 2001 From: Boudbois2271 Date: Mon, 25 May 2026 03:02:30 +0200 Subject: [PATCH 2/2] fix: guard TOC bounds before evaluating fallback in meethelper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getCurrentMWBWeek and getCurrentWTArticle both evaluated the fallback value (items[1] / items[2]) on the line before the length guard, making the guard unreachable in exactly the cases it was meant to protect against. If the TOC had fewer entries than expected, both functions threw TypeError instead of handling the error path. Fix: move the guard above the fallback declaration in both functions. Return null when the TOC is structurally invalid; callers now throw an explicit Error so the existing .catch() handler shows the offline message rather than silently swallowing an unrelated TypeError. Also: unify week-offset rounding. getCurrentWTArticle used Math.round while getCurrentMWBWeek used Math.floor. Week offsets must be floored — a day-count that is 6.9 weeks should not round up to week 7. Co-Authored-By: Claude Sonnet 4.6 --- webui/html/static/meethelper.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webui/html/static/meethelper.js b/webui/html/static/meethelper.js index 01088b9..ad74bb4 100644 --- a/webui/html/static/meethelper.js +++ b/webui/html/static/meethelper.js @@ -55,6 +55,7 @@ function setMeeting() { .then((response) => { var items = response.html.body.section.nav[0].ol.li var result = getCurrentWTArticle(items, thisSunday) + if (!result) throw new Error("Watchtower TOC has unexpected structure") var wtlink = document.getElementById("wt_link") wtlink.href = "/reader.html#/api/publications/"+wt+"/"+result.href wtlink.textContent = "Watchtower — " + result.title @@ -80,6 +81,7 @@ function setMeeting() { .then((response) => { var items = response.html.body.section.nav[0].ol.li var result = getCurrentMWBWeek(items) + if (!result) throw new Error("MWB TOC has unexpected structure") var mwblink = document.getElementById("mwb_link") mwblink.href = "/reader.html#/api/publications/"+mwb+"/"+result.href mwblink.textContent = "Life and Ministry — " + result.title @@ -96,8 +98,8 @@ function setMeeting() { // Parses the start date from items[1]'s title (e.g. "May 4-10") and computes // the week offset from today so the link always opens to the current week. function getCurrentMWBWeek(items) { + if (!items || items.length < 2) return null; var fallback = {href: items[1].a['-href'], title: items[1].a['#content']}; - if (!items || items.length < 2) return fallback; var firstTitle = items[1].a['#content'] || ''; var match = firstTitle.match(/([A-Za-z]+)\s+(\d+)/); if (!match) return fallback; @@ -122,13 +124,13 @@ function getCurrentMWBWeek(items) { // Finds the first Sunday of the study month (= current month, before the 2-month rollback), // then computes the week offset to determine the correct article. function getCurrentWTArticle(items, thisSunday) { + if (!items || items.length < 3) return null; var fallback = {href: items[2].a['-href'], title: items[2].a['#content']}; - if (!items || items.length < 3) return fallback; var firstDay = new Date(thisSunday.getFullYear(), thisSunday.getMonth(), 1); var dow = firstDay.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat var firstSunday = new Date(firstDay); firstSunday.setDate(1 + (dow === 0 ? 0 : 7 - dow)); - var weekOffset = Math.round((thisSunday - firstSunday) / 604800000); // ms per week + var weekOffset = Math.floor((thisSunday - firstSunday) / 604800000); // ms per week var maxArticle = items.length - 3; // articles are items[2..length-2], skip cover+toc+pagenav var articleIndex = Math.min(Math.max(0, weekOffset), maxArticle); return {href: items[2 + articleIndex].a['-href'], title: items[2 + articleIndex].a['#content']};