Update web/templates/entry_view.html
This commit is contained in:
parent
bdaef505a2
commit
151515058d
@ -467,20 +467,20 @@ function showToast(message, duration = 3000) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Highlighter: choose the best field source (most fields wins), then sync LS -->
|
<!-- Highlighter: apply to ALL fields (ignores selected fields) -->
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const FIELD_TO_SELECTOR = {
|
// Target EVERY field we render on entry_view
|
||||||
subject: "#subject-list",
|
const ALL_SELECTORS = [
|
||||||
illustration: "#illustration-text",
|
"#subject-list",
|
||||||
application: "#application-text",
|
"#illustration-text",
|
||||||
scripture_raw: "#scripture-text",
|
"#application-text",
|
||||||
source: "#source-text",
|
"#scripture-text",
|
||||||
talk_title: "#talk_title-text",
|
"#source-text",
|
||||||
talk_number: "#talk_title-text"
|
"#talk_title-text"
|
||||||
};
|
];
|
||||||
|
|
||||||
// Fire on first paint and on bfcache restores
|
// Run on fresh load + bfcache restores
|
||||||
if (document.readyState === "complete" || document.readyState === "interactive") run();
|
if (document.readyState === "complete" || document.readyState === "interactive") run();
|
||||||
else document.addEventListener("DOMContentLoaded", run, { once: true });
|
else document.addEventListener("DOMContentLoaded", run, { once: true });
|
||||||
window.addEventListener("pageshow", run);
|
window.addEventListener("pageshow", run);
|
||||||
@ -489,7 +489,7 @@ function showToast(message, duration = 3000) {
|
|||||||
async function run(){
|
async function run(){
|
||||||
if (ran) return;
|
if (ran) return;
|
||||||
|
|
||||||
// 1) Respect per-user toggle (defaults ON if flag missing)
|
// Respect per-user toggle (defaults ON if not present)
|
||||||
let enabled = true;
|
let enabled = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/get-prefs/", { cache: "no-store", credentials: "same-origin" });
|
const res = await fetch("/api/get-prefs/", { cache: "no-store", credentials: "same-origin" });
|
||||||
@ -500,62 +500,28 @@ function showToast(message, duration = 3000) {
|
|||||||
} catch(_) {}
|
} catch(_) {}
|
||||||
if (!enabled) { ran = true; return; }
|
if (!enabled) { ran = true; return; }
|
||||||
|
|
||||||
// 2) Gather candidates from all sources
|
// Get the query (ignore fields entirely)
|
||||||
const cand = { tmpl: {q:"", fields:[]}, json: {q:"", fields:[]}, ls: {q:"", fields:[]} };
|
let q = (window.__lastSearchQ || "").trim();
|
||||||
|
|
||||||
// 2a) Template/script-literal variables (set by view)
|
// JSON script fallback
|
||||||
cand.tmpl.q = (window.__lastSearchQ || '').trim();
|
if (!q) {
|
||||||
cand.tmpl.fields = Array.isArray(window.__lastSearchFields) ? window.__lastSearchFields.slice() : [];
|
const dataEl = document.getElementById("last-search-data");
|
||||||
|
if (dataEl) {
|
||||||
// 2b) JSON <script> block (if present)
|
try { q = (JSON.parse(dataEl.textContent||"{}").q || "").trim(); } catch(_){}
|
||||||
const dataEl = document.getElementById("last-search-data");
|
|
||||||
if (dataEl) {
|
|
||||||
try {
|
|
||||||
const payload = JSON.parse(dataEl.textContent || "{}");
|
|
||||||
cand.json.q = (payload.q || "").trim();
|
|
||||||
cand.json.fields = Array.isArray(payload.fields) ? payload.fields.slice() : [];
|
|
||||||
} catch(_){}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2c) localStorage (may be stale)
|
|
||||||
cand.ls.q = (localStorage.getItem('lastSearchQ') || '').trim();
|
|
||||||
try { cand.ls.fields = JSON.parse(localStorage.getItem('lastSearchFields') || '[]') || []; } catch(_) { cand.ls.fields = []; }
|
|
||||||
|
|
||||||
// 3) Pick the best candidate: longest non-empty "fields" wins (ties: prefer template, then JSON, then LS)
|
|
||||||
const order = ["tmpl","json","ls"];
|
|
||||||
let bestKey = "";
|
|
||||||
let best = { q:"", fields:[] };
|
|
||||||
|
|
||||||
for (const k of order) {
|
|
||||||
const item = cand[k];
|
|
||||||
// prefer any candidate with more fields
|
|
||||||
if (item.fields && item.fields.length > (best.fields ? best.fields.length : 0)) {
|
|
||||||
bestKey = k;
|
|
||||||
best = item;
|
|
||||||
} else if (item.fields.length === (best.fields ? best.fields.length : 0)) {
|
|
||||||
// tie-breaker: keep earlier (tmpl > json > ls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still empty, nothing to do
|
// localStorage fallback
|
||||||
const q = (best.q || "").trim();
|
if (!q) q = (localStorage.getItem("lastSearchQ") || "").trim();
|
||||||
const fields = Array.isArray(best.fields) ? best.fields.filter(Boolean) : [];
|
|
||||||
if (!q || !fields.length) { ran = true; return; }
|
|
||||||
|
|
||||||
// 4) Sync LS to the chosen "best" so stale subject-only arrays don't keep overriding
|
if (!q) { ran = true; return; }
|
||||||
try {
|
|
||||||
localStorage.setItem('lastSearchQ', q);
|
|
||||||
localStorage.setItem('lastSearchFields', JSON.stringify(fields));
|
|
||||||
} catch(_) {}
|
|
||||||
|
|
||||||
// 5) Tokenize query (keep quoted phrases; strip wildcards)
|
// Tokenize like your search: keep quoted phrases; strip wildcards
|
||||||
const tokens = tokenize(q).map(t => t.replaceAll("*","").replaceAll("?","")).filter(Boolean);
|
const tokens = tokenize(q).map(t => t.replaceAll("*","").replaceAll("?","")).filter(Boolean);
|
||||||
if (!tokens.length) { ran = true; return; }
|
if (!tokens.length) { ran = true; return; }
|
||||||
|
|
||||||
// 6) Highlight only in the fields that were actually selected
|
// Highlight across ALL selectors
|
||||||
for (const f of fields) {
|
for (const sel of ALL_SELECTORS) {
|
||||||
const sel = FIELD_TO_SELECTOR[f];
|
|
||||||
if (!sel) continue;
|
|
||||||
const container = document.querySelector(sel);
|
const container = document.querySelector(sel);
|
||||||
if (!container) continue;
|
if (!container) continue;
|
||||||
for (const tok of tokens) highlightAll(container, tok);
|
for (const tok of tokens) highlightAll(container, tok);
|
||||||
@ -564,7 +530,7 @@ function showToast(message, duration = 3000) {
|
|||||||
ran = true;
|
ran = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- helpers ---
|
// ---- helpers ----
|
||||||
function tokenize(s) {
|
function tokenize(s) {
|
||||||
const out = [];
|
const out = [];
|
||||||
let i = 0, buf = "", inQ = false;
|
let i = 0, buf = "", inQ = false;
|
||||||
@ -590,6 +556,8 @@ function showToast(message, duration = 3000) {
|
|||||||
function highlightAll(root, needle) {
|
function highlightAll(root, needle) {
|
||||||
if (!needle) return;
|
if (!needle) return;
|
||||||
const re = new RegExp(escapeRegExp(needle), "gi");
|
const re = new RegExp(escapeRegExp(needle), "gi");
|
||||||
|
|
||||||
|
// Snapshot text nodes first to avoid modifying while walking
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
|
||||||
while (walker.nextNode()) nodes.push(walker.currentNode);
|
while (walker.nextNode()) nodes.push(walker.currentNode);
|
||||||
@ -603,7 +571,7 @@ function showToast(message, duration = 3000) {
|
|||||||
text.replace(re, (m, idx) => {
|
text.replace(re, (m, idx) => {
|
||||||
if (idx > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, idx)));
|
if (idx > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, idx)));
|
||||||
const mark = document.createElement("mark");
|
const mark = document.createElement("mark");
|
||||||
mark.className = "mark-hit";
|
mark.className = "mark-hit"; // uses your existing gray style
|
||||||
mark.textContent = m;
|
mark.textContent = m;
|
||||||
frag.appendChild(mark);
|
frag.appendChild(mark);
|
||||||
lastIndex = idx + m.length;
|
lastIndex = idx + m.length;
|
||||||
@ -623,4 +591,5 @@ function showToast(message, duration = 3000) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user