seperate out WOL publication codes, offer GUI for viewing, and add new KC publication source #6

Merged
joshlaymon merged 21 commits from develop into main 2025-09-09 00:52:21 +00:00
Showing only changes of commit 2cf518b903 - Show all commits

View File

@ -1,22 +1,47 @@
/* source-validator.v1.js /* source-validator.v1.js
Centralizes logic for deciding if a "Source" string should link to WOL. Centralizes logic for deciding if a "Source" string should link to WOL.
Loads publication codes from /static/data/wol-pub-codes.v1.json.
Exposes: Exposes:
- SourceValidator.isWOLSource(text) -> boolean - SourceValidator.isWOLSource(text) -> boolean
- SourceValidator.buildWOLSearchURL(text) -> string - SourceValidator.buildWOLSearchURL(text) -> string
*/ */
window.SourceValidator = (function () { window.SourceValidator = (function () {
// Publications / codes that produce valid WOL links. // ---- Load publication codes (sync so callers can use API immediately) ----
// Added: uw, su, re, lvs, rs (rs was already present). function loadPubCodesSync() {
const PUB_CODES = [ try {
"wp","ws","yb","mwb","w","g","ap","apf","be","bh","br","bt","btg","cf","cl","ct","dp", var xhr = new XMLHttpRequest();
"fg","fy","gt","hb","im","ip","it","jv","ka","kj","kl","lf","lff","ll","ly","my","od", xhr.open("GET", "/static/data/wol-pub-codes.v1.json", false); // synchronous
"pe","po","pt","rr","rs","sg","sh","si","td","tp","tr","ts","un","jy", xhr.send(null);
"uw","su","re","lvs","lp","yy","yp2","yp","jv","sl","pm"// new if (xhr.status >= 200 && xhr.status < 300) {
]; var data = JSON.parse(xhr.responseText || "{}");
if (data && Array.isArray(data.pub_codes)) {
// de-duplicate and normalize to lowercase strings
var uniq = Object.create(null), out = [];
for (var i = 0; i < data.pub_codes.length; i++) {
var c = String(data.pub_codes[i] || "").trim().toLowerCase();
if (!c) continue;
if (!uniq[c]) { uniq[c] = 1; out.push(c); }
}
return out;
}
}
} catch (e) {
// fall through to fallback
}
// Fallback (very small set) — only used if JSON cannot be loaded
return ["w", "wp", "ws", "g", "rs"];
}
// Publications / codes loaded from JSON
var PUB_CODES = loadPubCodesSync();
// Choose the longest matching code at the start (so "ws" beats "w").
var PUB_CODES_SORTED = PUB_CODES.slice().sort(function (a, b) { return b.length - a.length; });
// Year validation rules (applies only if a year can be parsed from the source). // Year validation rules (applies only if a year can be parsed from the source).
// Watchtower (w/wp/ws) back to 1950; Awake (g) back to 1970. // Watchtower (w/wp/ws) back to 1950; Awake (g) back to 1970.
const YEAR_RULES = [ var YEAR_RULES = [
{ codes: ["w","wp","ws"], minYear: 1950 }, { codes: ["w","wp","ws"], minYear: 1950 },
{ codes: ["g"], minYear: 1970 } { codes: ["g"], minYear: 1970 }
]; ];
@ -24,12 +49,10 @@ window.SourceValidator = (function () {
// Normalize helper // Normalize helper
function normalize(s) { return (s || "").trim().toLowerCase(); } function normalize(s) { return (s || "").trim().toLowerCase(); }
// Choose the longest matching code at the start (so "ws" beats "w").
const PUB_CODES_SORTED = [...PUB_CODES].sort((a,b)=>b.length-a.length);
function leadingCode(textLower) { function leadingCode(textLower) {
for (const code of PUB_CODES_SORTED) { for (var i = 0; i < PUB_CODES_SORTED.length; i++) {
if (textLower.startsWith(code)) return code; var code = PUB_CODES_SORTED[i];
if (textLower.indexOf(code) === 0) return code;
} }
return null; return null;
} }
@ -37,60 +60,51 @@ window.SourceValidator = (function () {
// Try to extract a year that appears right after the leading code (allow spaces), // Try to extract a year that appears right after the leading code (allow spaces),
// accepting either 4-digit (e.g., 1955, 2001) or 2-digit (e.g., 55, 95, 12) forms. // accepting either 4-digit (e.g., 1955, 2001) or 2-digit (e.g., 55, 95, 12) forms.
function extractYearAfterCode(textLower, code) { function extractYearAfterCode(textLower, code) {
let s = textLower.slice(code.length).trim(); var s = textLower.slice(code.length).trim();
// 1) Look for a 4-digit year first // 1) Look for a 4-digit year first (18002099)
let m = s.match(/\b(1[89]\d{2}|20\d{2})\b/); // 1800-2099 (broad, but OK) var m = s.match(/\b(1[89]\d{2}|20\d{2})\b/);
if (m) { if (m) return parseInt(m[1], 10);
return parseInt(m[1], 10);
}
// 2) If not found, accept a 2-digit year at the *start* of the remainder, // 2) If not found, accept a 2-digit year at the *start* of the remainder
// or right after an optional space: e.g., "w55 1/1", "w 95", "g70 1/22" // (e.g., "w55 1/1", "w 95", "g70 1/22")
m = s.match(/^\s*(\d{2})\b/); m = s.match(/^\s*(\d{2})\b/);
if (m) { if (m) {
const yy = parseInt(m[1], 10); var yy = parseInt(m[1], 10);
// Infer century based on publication + threshold logic if (code === "g") return yy >= 70 ? (1900 + yy) : (2000 + yy);
// - For Watchtower: 5099 -> 19501999; 0049 -> 20002049 if (code === "w" || code === "wp" || code === "ws")
// - For Awake: 7099 -> 19701999; 0069 -> 20002069 return yy >= 50 ? (1900 + yy) : (2000 + yy);
if (code === "g") {
return yy >= 70 ? (1900 + yy) : (2000 + yy);
}
if (code === "w" || code === "wp" || code === "ws") {
return yy >= 50 ? (1900 + yy) : (2000 + yy);
}
// For other pubs, if they ever include 2-digit years, assume 1900+yy≥70 else 2000+yy
return yy >= 70 ? (1900 + yy) : (2000 + yy); return yy >= 70 ? (1900 + yy) : (2000 + yy);
} }
// No recognizable year → don't enforce year limits // No recognizable year → don't enforce year limits
return null; return null;
} }
function passesYearRuleIfPresent(textLower, code) { function passesYearRuleIfPresent(textLower, code) {
const rule = YEAR_RULES.find(r => r.codes.includes(code)); var rule = null;
for (var i=0;i<YEAR_RULES.length;i++){
if (YEAR_RULES[i].codes.indexOf(code) !== -1) { rule = YEAR_RULES[i]; break; }
}
if (!rule) return true; // no year rule for this pub if (!rule) return true; // no year rule for this pub
const y = extractYearAfterCode(textLower, code); var y = extractYearAfterCode(textLower, code);
if (y == null) return true; // no year present → allow if (y == null) return true; // no year present → allow
return y >= rule.minYear; return y >= rule.minYear;
} }
function isWOLSource(text) { function isWOLSource(text) {
const t = normalize(text); var t = normalize(text);
if (!t) return false; if (!t) return false;
var code = leadingCode(t);
const code = leadingCode(t);
if (!code) return false; if (!code) return false;
// If starts with a known pub code, its WOL-capable — but enforce year rules where applicable. // If starts with a known pub code, its WOL-capable — but enforce year rules where applicable.
return passesYearRuleIfPresent(t, code); return passesYearRuleIfPresent(t, code);
} }
function buildWOLSearchURL(text) { function buildWOLSearchURL(text) {
const q = encodeURIComponent(text || ""); var q = encodeURIComponent(text || "");
return `https://wol.jw.org/en/wol/l/r1/lp-e?q=${q}`; return "https://wol.jw.org/en/wol/l/r1/lp-e?q=" + q;
} }
return { isWOLSource, buildWOLSearchURL }; return { isWOLSource: isWOLSource, buildWOLSearchURL: buildWOLSearchURL };
})(); })();