Update web/templates/entry_add.html

This commit is contained in:
Joshua Laymon 2025-09-02 02:03:35 +00:00
parent e2a2b7239d
commit 9299795d66

View File

@ -180,14 +180,15 @@ if (entryCodeEl && !entryCodeEl.value) {
entryCodeEl.value = "{{ request.user.username }}"; entryCodeEl.value = "{{ request.user.username }}";
} }
/* =========================== <!-- ===========================
Live Scripture Validation Live Scripture Validation (WOL + full names; space allowed in numbered)
=========================== */ =========================== -->
<script>
(function () { (function () {
const el = document.getElementById("id_scripture_raw"); const el = document.getElementById("id_scripture_raw");
if (!el) return; if (!el) return;
// Exact WOL abbreviations (case-sensitive) // WOL abbreviations (must match exactly; numbered allow optional space before letters)
const WOL = new Set([ const WOL = new Set([
// OT // OT
"Ge","Ex","Le","Nu","De","Jos","Jg","Ru","1Sa","2Sa","1Ki","2Ki","1Ch","2Ch","Ezr","Ne","Es","Job","Ps","Pr","Ec","Ca","Isa","Jer","La","Eze","Da","Ho","Joe","Am","Ob","Jon","Mic","Na","Hab","Zep","Hag","Zec","Mal", "Ge","Ex","Le","Nu","De","Jos","Jg","Ru","1Sa","2Sa","1Ki","2Ki","1Ch","2Ch","Ezr","Ne","Es","Job","Ps","Pr","Ec","Ca","Isa","Jer","La","Eze","Da","Ho","Joe","Am","Ob","Jon","Mic","Na","Hab","Zep","Hag","Zec","Mal",
@ -195,67 +196,73 @@ if (entryCodeEl && !entryCodeEl.value) {
"Mt","Mr","Lu","Joh","Ac","Ro","1Co","2Co","Ga","Eph","Php","Col","1Th","2Th","1Ti","2Ti","Tit","Phm","Heb","Jas","1Pe","2Pe","1Jo","2Jo","3Jo","Jude","Re" "Mt","Mr","Lu","Joh","Ac","Ro","1Co","2Co","Ga","Eph","Php","Col","1Th","2Th","1Ti","2Ti","Tit","Phm","Heb","Jas","1Pe","2Pe","1Jo","2Jo","3Jo","Jude","Re"
]); ]);
// Normalize some gentle user quirks: // Full canonical names → WOL abbreviation (accept full names case-insensitively)
// - Allow trailing dot after the book token: "Ps." -> "Ps" const FULL = new Map([
// - Allow a space after the number in numbered books: "1 Pe" -> "1Pe" // OT
function normalizeBookToken(raw) { ["genesis","Ge"],["exodus","Ex"],["leviticus","Le"],["numbers","Nu"],["deuteronomy","De"],
let t = raw.trim(); ["joshua","Jos"],["judges","Jg"],["ruth","Ru"],
// collapse internal multiple spaces ["1 samuel","1Sa"],["2 samuel","2Sa"],["1 kings","1Ki"],["2 kings","2Ki"],
t = t.replace(/\s+/g, " "); ["1 chronicles","1Ch"],["2 chronicles","2Ch"],
// remove trailing period on the book token ["ezra","Ezr"],["nehemiah","Ne"],["esther","Es"],["job","Job"],["psalms","Ps"],["psalm","Ps"],
t = t.replace(/\.$/, ""); ["proverbs","Pr"],["ecclesiastes","Ec"],["song of solomon","Ca"],["song of songs","Ca"],
// join patterns like "1 Pe" or "2 Co" -> "1Pe" / "2Co" ["isaiah","Isa"],["jeremiah","Jer"],["lamentations","La"],["ezekiel","Eze"],["daniel","Da"],
t = t.replace(/^([1-3])\s+([A-Za-z]+)/, (_, n, b) => n + b); ["hosea","Ho"],["joel","Joe"],["amos","Am"],["obadiah","Ob"],["jonah","Jon"],["micah","Mic"],
return t; ["nahum","Na"],["habakkuk","Hab"],["zephaniah","Zep"],["haggai","Hag"],["zechariah","Zec"],["malachi","Mal"],
// NT
["matthew","Mt"],["mark","Mr"],["luke","Lu"],["john","Joh"],["acts","Ac"],["romans","Ro"],
["1 corinthians","1Co"],["2 corinthians","2Co"],["galatians","Ga"],["ephesians","Eph"],
["philippians","Php"],["colossians","Col"],
["1 thessalonians","1Th"],["2 thessalonians","2Th"],
["1 timothy","1Ti"],["2 timothy","2Ti"],
["titus","Tit"],["philemon","Phm"],["hebrews","Heb"],["james","Jas"],
["1 peter","1Pe"],["2 peter","2Pe"],
["1 john","1Jo"],["2 john","2Jo"],["3 john","3Jo"],
["jude","Jude"],["revelation","Re"]
]);
// Split: (book) + (chapter[:verses…])
function splitBookAndRest(s) {
const m = s.match(/^(.+?)\s+(\d{1,3}(?:\s*:\s*.*)?)$/);
return m ? {book: m[1], rest: m[2]} : null;
} }
// Validate one reference like: // Accept either exact WOL abbr (with optional space between leading number and letters),
// "Ro 12" (chapter only) // or the full canonical name (case-insensitive).
// "Ro 12:1" (single verse) function lookupBookCode(bookRaw) {
// "Ro 12:1, 3, 5-7" (lists and ranges) let b = bookRaw.trim();
// "Joh 3:16-4:2" (cross-chapter range)
// "Ps. 23" (trailing dot on book handled by normalization) // 1) Full name check (case-insensitive, as typed, including numbered with a space)
const fullKey = b.toLowerCase().replace(/\s+/g, " ");
if (FULL.has(fullKey)) return FULL.get(fullKey);
// 2) Abbreviation check
// Allow optional space between leading number and letters: e.g., "1 Pe" → "1Pe"
const maybeAbbr = b.replace(/^([1-3])\s+([A-Za-z]+)/, (_, n, letters) => n + letters);
if (WOL.has(maybeAbbr)) return maybeAbbr;
return null;
}
// Verse pattern: chapter only | chapter:verses (lists/ranges) | cross-chapter range
const versesRe = /^(\d{1,3})$|^(\d{1,3}):\s*(\d{1,3}(?:\s*-\s*(?:\d{1,3}|\d{1,3}:\d{1,3}))?(?:\s*,\s*\d{1,3}(?:\s*-\s*(?:\d{1,3}|\d{1,3}:\d{1,3}))?)*)$/;
function isValidSingleRef(ref) { function isValidSingleRef(ref) {
const s = ref.trim(); const s = ref.trim();
if (!s) return false; if (!s) return false;
const parts = splitBookAndRest(s);
if (!parts) return false;
// Split book token from the rest (book is everything before first space) const code = lookupBookCode(parts.book);
const firstSpace = s.indexOf(" "); if (!code) return false;
if (firstSpace < 0) return false;
const rawBook = s.slice(0, firstSpace); const rest = (parts.rest || "").trim();
const book = normalizeBookToken(rawBook);
if (!WOL.has(book)) return false;
const rest = s.slice(firstSpace + 1).trim();
if (!rest) return false; if (!rest) return false;
// Accept "chapter" only (e.g., "Ps 23") return versesRe.test(rest);
if (/^\d{1,3}$/.test(rest)) return true;
// Accept "chapter:verses" where verses can be:
// - n
// - n-m
// - n, m, p-q
// and allow a cross-chapter range like 3:16-4:2
// Pattern:
// chap: ( part(, part)* )
// where part is either:
// v -> \d{1,3}
// v-v -> \d{1,3}\s*-\s*\d{1,3}
// v-ch:v2 -> \d{1,3}\s*-\s*\d{1,3}:\d{1,3}
const re = /^(\d{1,3}):\s*(\d{1,3}(?:\s*-\s*(?:\d{1,3}|\d{1,3}:\d{1,3}))?(?:\s*,\s*\d{1,3}(?:\s*-\s*(?:\d{1,3}|\d{1,3}:\d{1,3}))?)*)$/;
if (re.test(rest)) {
return true;
}
return false;
} }
function validateAll() { function validateAll() {
const raw = el.value || ""; const raw = el.value || "";
// split by ';' as multiple references
const parts = raw.split(";").map(t => t.trim()).filter(Boolean); const parts = raw.split(";").map(t => t.trim()).filter(Boolean);
if (parts.length === 0) { if (parts.length === 0) {
el.classList.remove("scripture-valid","scripture-invalid"); el.classList.remove("scripture-valid","scripture-invalid");
@ -266,10 +273,8 @@ if (entryCodeEl && !entryCodeEl.value) {
el.classList.toggle("scripture-invalid", !allValid); el.classList.toggle("scripture-invalid", !allValid);
} }
// Wire events
el.addEventListener("input", validateAll); el.addEventListener("input", validateAll);
el.addEventListener("change", validateAll); el.addEventListener("change", validateAll);
// Run once on load (in case form is prefilled)
validateAll(); validateAll();
})(); })();
</script> </script>