Update web/templates/entry_view.html
This commit is contained in:
parent
ca7a3543a0
commit
0dea62b686
@ -102,19 +102,10 @@
|
||||
<div class="meta-label">Source</div>
|
||||
<div class="meta-value" id="source-text">
|
||||
{% if entry.source %}
|
||||
{% with s=entry.source %}
|
||||
{% with sl=s|lower %}
|
||||
{% if sl|slice:":2" == "wp" or sl|slice:":2" == "ws" or sl|slice:":2" == "yb" or sl|slice:":3" == "mwb" or sl|slice:":1" == "w" or sl|slice:":1" == "g" or sl|slice:":2" == "ap" or sl|slice:":3" == "apf" or sl|slice:":2" == "be" or sl|slice:":2" == "bh" or sl|slice:":2" == "br" or sl|slice:":2" == "bt" or sl|slice:":3" == "btg" or sl|slice:":2" == "cf" or sl|slice:":2" == "cl" or sl|slice:":2" == "ct" or sl|slice:":2" == "dp" or sl|slice:":2" == "fg" or sl|slice:":2" == "fy" or sl|slice:":2" == "gt" or sl|slice:":2" == "hb" or sl|slice:":2" == "im" or sl|slice:":2" == "ip" or sl|slice:":2" == "it" or sl|slice:":2" == "jv" or sl|slice:":2" == "ka" or sl|slice:":2" == "kj" or sl|slice:":2" == "kl" or sl|slice:":2" == "lf" or sl|slice:":3" == "lff" or sl|slice:":2" == "ll" or sl|slice:":2" == "ly" or sl|slice:":2" == "my" or sl|slice:":2" == "od" or sl|slice:":2" == "pe" or sl|slice:":2" == "po" or sl|slice:":2" == "pt" or sl|slice:":2" == "rr" or sl|slice:":2" == "rs" or sl|slice:":2" == "sg" or sl|slice:":2" == "sh" or sl|slice:":2" == "si" or sl|slice:":2" == "td" or sl|slice:":2" == "tp" or sl|slice:":2" == "tr" or sl|slice:":2" == "ts" or sl|slice:":2" == "un" or sl|slice:":2" == "jy"%}
|
||||
<a class="chip chip-link"
|
||||
href="https://wol.jw.org/en/wol/l/r1/lp-e?q={{ s|urlencode }}"
|
||||
target="_blank" rel="noopener noreferrer">{{ s }}</a>
|
||||
{% else %}
|
||||
<a class="chip chip-link"
|
||||
href="https://www.google.com/search?q={{ s|urlencode }}"
|
||||
target="_blank" rel="noopener noreferrer">{{ s }}</a>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
<a class="chip chip-link js-source-link"
|
||||
data-source="{{ entry.source }}"
|
||||
href="https://www.google.com/search?q={{ entry.source|urlencode }}"
|
||||
target="_blank" rel="noopener noreferrer">{{ entry.source }}</a>
|
||||
{% else %}—{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -216,7 +207,7 @@
|
||||
border-color:#c9def5;
|
||||
}
|
||||
|
||||
/* Subject chip gets a hit → color the whole pill */
|
||||
/* Subject chip gets a hit → color the whole chip */
|
||||
.chip-subject.chip-hit{
|
||||
background:#f8e3c5; /* your chosen highlight color */
|
||||
border-color:#e0b98e;
|
||||
@ -394,10 +385,9 @@
|
||||
a.muted = false;
|
||||
|
||||
try {
|
||||
await a.play(); // <- may throw NotAllowedError on some iOS/Safari cases
|
||||
await a.play();
|
||||
playing = true;
|
||||
} catch (err) {
|
||||
// Autoplay/permission style block (Safari/iOS or Chrome heuristics)
|
||||
if (err && (err.name === 'NotAllowedError' ||
|
||||
/not allowed|denied permission/i.test(err.message))) {
|
||||
showToast("Audio was blocked. Make sure the phone isn't on Silent and tap the speaker again.");
|
||||
@ -407,9 +397,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// -------- button: toggle behavior --------
|
||||
ttsBtn.addEventListener('click', async () => {
|
||||
// Toggle OFF if something is already playing (either engine)
|
||||
if (playing) {
|
||||
try {
|
||||
stopAll();
|
||||
@ -438,7 +426,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Safety: stop audio when navigating away
|
||||
window.addEventListener('pagehide', stopAll);
|
||||
})();
|
||||
</script>
|
||||
@ -469,10 +456,7 @@ function showToast(message, duration = 3000) {
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Trigger fade in
|
||||
setTimeout(() => toast.classList.add("show"), 50);
|
||||
|
||||
// Fade out & remove
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("show");
|
||||
setTimeout(() => document.body.removeChild(toast), 500);
|
||||
@ -483,7 +467,6 @@ function showToast(message, duration = 3000) {
|
||||
<!-- Highlighter: apply to ALL fields; for Subjects, color the whole chip; highlight WHOLE WORDS -->
|
||||
<script>
|
||||
(function(){
|
||||
// Target EVERY field we render on entry_view
|
||||
const ALL_SELECTORS = [
|
||||
"#subject-list",
|
||||
"#illustration-text",
|
||||
@ -493,7 +476,6 @@ function showToast(message, duration = 3000) {
|
||||
"#talk_title-text"
|
||||
];
|
||||
|
||||
// Run on fresh load + bfcache restores
|
||||
if (document.readyState === "complete" || document.readyState === "interactive") run();
|
||||
else document.addEventListener("DOMContentLoaded", run, { once: true });
|
||||
window.addEventListener("pageshow", run);
|
||||
@ -502,7 +484,6 @@ function showToast(message, duration = 3000) {
|
||||
async function run(){
|
||||
if (ran) return;
|
||||
|
||||
// Respect per-user toggle (defaults ON if not present)
|
||||
let enabled = true;
|
||||
try {
|
||||
const res = await fetch("/api/get-prefs/", { cache: "no-store", credentials: "same-origin" });
|
||||
@ -513,27 +494,19 @@ function showToast(message, duration = 3000) {
|
||||
} catch(_) {}
|
||||
if (!enabled) { ran = true; return; }
|
||||
|
||||
// Get the query (ignore fields entirely)
|
||||
let q = (window.__lastSearchQ || "").trim();
|
||||
|
||||
// JSON script fallback
|
||||
if (!q) {
|
||||
const dataEl = document.getElementById("last-search-data");
|
||||
if (dataEl) {
|
||||
try { q = (JSON.parse(dataEl.textContent||"{}").q || "").trim(); } catch(_){}
|
||||
}
|
||||
}
|
||||
|
||||
// localStorage fallback
|
||||
if (!q) q = (localStorage.getItem("lastSearchQ") || "").trim();
|
||||
|
||||
if (!q) { ran = true; return; }
|
||||
|
||||
// Tokenize like your search: keep quoted phrases; strip wildcards
|
||||
const tokens = tokenize(q).map(t => t.replaceAll("*","").replaceAll("?","")).filter(Boolean);
|
||||
if (!tokens.length) { ran = true; return; }
|
||||
|
||||
// Highlight across ALL selectors
|
||||
for (const sel of ALL_SELECTORS) {
|
||||
const container = document.querySelector(sel);
|
||||
if (!container) continue;
|
||||
@ -543,7 +516,6 @@ function showToast(message, duration = 3000) {
|
||||
ran = true;
|
||||
}
|
||||
|
||||
// ---- helpers ----
|
||||
function tokenize(s) {
|
||||
const out = [];
|
||||
let i = 0, buf = "", inQ = false;
|
||||
@ -563,11 +535,9 @@ function showToast(message, duration = 3000) {
|
||||
}
|
||||
|
||||
function makeWordRegex() {
|
||||
// Prefer full Unicode word matching (letters + marks + numbers + ' and -)
|
||||
try {
|
||||
return new RegExp("(\\p{L}[\\p{L}\\p{M}\\p{N}'-]*|\\d+)", "gu");
|
||||
} catch (_) {
|
||||
// Fallback for older browsers: best-effort ASCII-ish words
|
||||
return /([A-Za-z][A-Za-z0-9'-]*|\d+)/g;
|
||||
}
|
||||
}
|
||||
@ -576,14 +546,13 @@ function showToast(message, duration = 3000) {
|
||||
if (!needle) return;
|
||||
const needleLower = needle.toLowerCase();
|
||||
|
||||
// Special handling for SUBJECT chips: color the whole chip, no <mark> injection
|
||||
if (root.id === "subject-list") {
|
||||
root.querySelectorAll(".chip-subject, .chip-muted").forEach(chip => {
|
||||
if ((chip.textContent || "").toLowerCase().includes(needleLower)) {
|
||||
chip.classList.add("chip-hit");
|
||||
}
|
||||
});
|
||||
return; // do not inject <mark> inside subject chips
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = [];
|
||||
@ -595,8 +564,6 @@ function showToast(message, duration = 3000) {
|
||||
for (const textNode of nodes) {
|
||||
const text = textNode.nodeValue;
|
||||
if (!text) continue;
|
||||
|
||||
// Fast skip: if token not present anywhere (case-insensitive), skip this node
|
||||
if (!text.toLowerCase().includes(needleLower)) continue;
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
@ -608,14 +575,12 @@ function showToast(message, duration = 3000) {
|
||||
const start = m.index;
|
||||
const end = start + m[0].length;
|
||||
|
||||
// Append any non-word separator text before this word
|
||||
if (start > lastIndex) {
|
||||
frag.appendChild(document.createTextNode(text.slice(lastIndex, start)));
|
||||
}
|
||||
|
||||
const word = m[0];
|
||||
if (word.toLowerCase().includes(needleLower)) {
|
||||
// Highlight the ENTIRE word
|
||||
const mark = document.createElement("mark");
|
||||
mark.className = "mark-hit";
|
||||
mark.textContent = word;
|
||||
@ -627,7 +592,6 @@ function showToast(message, duration = 3000) {
|
||||
lastIndex = end;
|
||||
}
|
||||
|
||||
// Remainder after the last word
|
||||
if (lastIndex < text.length) {
|
||||
frag.appendChild(document.createTextNode(text.slice(lastIndex)));
|
||||
}
|
||||
@ -640,8 +604,28 @@ function showToast(message, duration = 3000) {
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Include shared Scripture Validator and validate each Scripture pill individually -->
|
||||
<!-- Include shared validators -->
|
||||
<script src="{% static 'js/scripture-validator.v1.js' %}"></script>
|
||||
<script src="{% static 'js/source-validator.v1.js' %}"></script>
|
||||
|
||||
<!-- Upgrade Source link to WOL if valid (no other logic changed) -->
|
||||
<script>
|
||||
(function () {
|
||||
const link = document.querySelector('#source-text .js-source-link');
|
||||
if (!link) return;
|
||||
const srcText = link.getAttribute('data-source') || (link.textContent || '').trim();
|
||||
|
||||
if (SourceValidator.isWOLSource(srcText)) {
|
||||
link.href = SourceValidator.buildWOLSearchURL(srcText);
|
||||
link.title = (link.title ? link.title + ' • ' : '') + 'Open in WOL';
|
||||
} else {
|
||||
// already points to Google; keep it
|
||||
link.title = (link.title ? link.title + ' • ' : '') + 'Search on Google';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Scripture pill validation you already use -->
|
||||
<script>
|
||||
(function () {
|
||||
function validatePills() {
|
||||
@ -653,11 +637,9 @@ function showToast(message, duration = 3000) {
|
||||
const txt = (pill.textContent || '').trim();
|
||||
if (!txt) return;
|
||||
|
||||
// Create a temporary off-DOM input to reuse the shared validator
|
||||
const tmp = document.createElement('input');
|
||||
tmp.type = 'text';
|
||||
tmp.value = txt;
|
||||
// We attach, which triggers immediate validation (no need to keep the node)
|
||||
ScriptureValidator.attach(tmp);
|
||||
|
||||
const isValid = tmp.classList.contains('scripture-valid');
|
||||
@ -665,7 +647,6 @@ function showToast(message, duration = 3000) {
|
||||
pill.classList.add('scripture-pill-invalid');
|
||||
pill.title = (pill.title ? pill.title + ' • ' : '') + 'Unrecognized scripture format';
|
||||
}
|
||||
// Clean up
|
||||
tmp.remove();
|
||||
});
|
||||
}
|
||||
@ -675,7 +656,6 @@ function showToast(message, duration = 3000) {
|
||||
} else {
|
||||
validatePills();
|
||||
}
|
||||
// Also re-run if page is restored from bfcache
|
||||
window.addEventListener('pageshow', validatePills);
|
||||
})();
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user