Update web/templates/entry_view.html

This commit is contained in:
Joshua Laymon 2025-08-23 16:13:00 +00:00
parent 148e3e6bce
commit d47425b1f0

View File

@ -467,11 +467,10 @@ function showToast(message, duration = 3000) {
} }
</script> </script>
<!-- Highlighter: robust triggers (DOMContentLoaded + pageshow + readyState) --> <!-- Highlighter: wait until search data is present -->
<script> <script>
(function(){ (function(){
// EXACT field -> selector mapping for your template IDs const FIELD_TO_SELECTOR = {
const fieldToSelector = {
subject: "#subject-list", subject: "#subject-list",
illustration: "#illustration-text", illustration: "#illustration-text",
application: "#application-text", application: "#application-text",
@ -481,31 +480,11 @@ function showToast(message, duration = 3000) {
talk_number: "#talk_title-text", talk_number: "#talk_title-text",
}; };
// Kickoff with multiple safety nets so it also runs after bfcache restores let tried = 0;
if (document.readyState === "complete" || document.readyState === "interactive") { function tryRun() {
run(); tried++;
} else {
document.addEventListener("DOMContentLoaded", run, { once: true });
}
// pageshow fires on bfcache restores and some soft navigations
window.addEventListener("pageshow", run);
async function run() {
// Respect per-user toggle
let enabled = true;
try {
const res = await fetch("/api/get-prefs/");
const prefs = await res.json();
if (prefs && typeof prefs.highlight_search_hits !== "undefined") {
enabled = !!prefs.highlight_search_hits;
}
} catch (_) {}
if (!enabled) return;
// Read last search (prefer JS-literal fallback, then JSON blob)
let q = (window.__lastSearchQ || "").trim(); let q = (window.__lastSearchQ || "").trim();
let fields = Array.isArray(window.__lastSearchFields) ? window.__lastSearchFields : []; let fields = Array.isArray(window.__lastSearchFields) ? window.__lastSearchFields : [];
if ((!q || !fields.length)) { if ((!q || !fields.length)) {
const dataEl = document.getElementById("last-search-data"); const dataEl = document.getElementById("last-search-data");
if (dataEl) { if (dataEl) {
@ -516,15 +495,36 @@ function showToast(message, duration = 3000) {
} catch (_) {} } catch (_) {}
} }
} }
if (!q || !fields.length) return;
// Tokenize like your search (keep quoted phrases, strip * and ?) if (q && fields.length) {
highlight(q, fields);
return true;
}
if (tried < 20) { // retry up to ~2s
setTimeout(tryRun, 100);
}
return false;
}
async function highlight(q, fields) {
// Respect per-user toggle
let enabled = true;
try {
const res = await fetch("/api/get-prefs/", { cache:"no-store", credentials:"same-origin" });
if (res.ok) {
const prefs = await res.json();
if (prefs && typeof prefs.highlight_search_hits !== "undefined") {
enabled = !!prefs.highlight_search_hits;
}
}
} catch(_) {}
if (!enabled) return;
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) return; if (!tokens.length) return;
// Highlight only within fields that were searched
for (const f of fields) { for (const f of fields) {
const sel = fieldToSelector[f]; const sel = FIELD_TO_SELECTOR[f];
if (!sel) continue; if (!sel) continue;
const container = document.querySelector(sel); const container = document.querySelector(sel);
if (!container) continue; if (!container) continue;
@ -532,7 +532,7 @@ function showToast(message, duration = 3000) {
} }
} }
// ---- helpers ---- // helpers
function tokenize(s) { function tokenize(s) {
const out = []; const out = [];
let i = 0, buf = "", inQ = false; let i = 0, buf = "", inQ = false;
@ -558,9 +558,14 @@ 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");
walkTextNodes(root, (textNode) => { const nodes = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while (walker.nextNode()) nodes.push(walker.currentNode);
for (const textNode of nodes) {
const text = textNode.nodeValue; const text = textNode.nodeValue;
if (!re.test(text)) return; if (!text || !re.test(text)) continue;
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
let lastIndex = 0; let lastIndex = 0;
text.replace(re, (m, idx) => { text.replace(re, (m, idx) => {
@ -573,35 +578,16 @@ function showToast(message, duration = 3000) {
return m; return m;
}); });
if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.slice(lastIndex))); if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.slice(lastIndex)));
textNode.parentNode.replaceChild(frag, textNode);
}); if (textNode.parentNode) textNode.parentNode.replaceChild(frag, textNode);
}
} }
function walkTextNodes(node, cb) { // start
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null, false); tryRun();
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(cb);
}
})(); })();
</script> </script>
<!-- DEBUG: prove scripts run on load -->
<script>
(function(){
// 1) blatant visual badge
const badge = document.createElement('div');
badge.textContent = 'HL script loaded';
badge.style.cssText = 'position:fixed;bottom:8px;right:8px;background:#111;color:#fff;padding:6px 10px;border-radius:6px;font:12px system-ui;z-index:99999;opacity:.85';
document.body.appendChild(badge);
setTimeout(()=>badge.remove(), 1500);
// 2) console beacons for all relevant lifecycle events
console.log('[HL] inline script evaluated. readyState=', document.readyState);
document.addEventListener('DOMContentLoaded', () => console.log('[HL] DOMContentLoaded'));
window.addEventListener('pageshow', (e) => console.log('[HL] pageshow (bfcache=', e.persisted, ')'));
})();
</script>
{% endblock %} {% endblock %}