Update web/templates/entry_view.html

This commit is contained in:
Joshua Laymon 2025-08-24 01:26:32 +00:00
parent 20e0d593bb
commit b880085720

View File

@ -474,7 +474,7 @@ function showToast(message, duration = 3000) {
}
</script>
<!-- Highlighter: apply to ALL fields; for Subjects, color the whole chip instead of inline marks -->
<!-- Highlighter: apply to ALL fields; for Subjects, color the whole chip; highlight WHOLE WORDS -->
<script>
(function(){
// Target EVERY field we render on entry_view
@ -556,47 +556,79 @@ function showToast(message, duration = 3000) {
return out;
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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;
}
}
function highlightAll(root, needle) {
if (!needle) return;
const re = new RegExp(escapeRegExp(needle), "gi");
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 (re.test(chip.textContent || "")) {
if ((chip.textContent || "").toLowerCase().includes(needleLower)) {
chip.classList.add("chip-hit");
}
});
return; // do not inject <mark> inside subject chips
}
// Normal inline highlighting for text fields
const nodes = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
while (walker.nextNode()) nodes.push(walker.currentNode);
const wordRe = makeWordRegex();
for (const textNode of nodes) {
const text = textNode.nodeValue;
if (!text || !re.test(text)) continue;
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();
let lastIndex = 0;
text.replace(re, (m, idx) => {
if (idx > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, idx)));
const mark = document.createElement("mark");
mark.className = "mark-hit"; // uses your peach highlight
mark.textContent = m;
frag.appendChild(mark);
lastIndex = idx + m.length;
return m;
});
if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.slice(lastIndex)));
wordRe.lastIndex = 0;
let m;
if (textNode.parentNode) textNode.parentNode.replaceChild(frag, textNode);
while ((m = wordRe.exec(text)) !== null) {
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;
frag.appendChild(mark);
} else {
frag.appendChild(document.createTextNode(word));
}
lastIndex = end;
}
// Remainder after the last word
if (lastIndex < text.length) {
frag.appendChild(document.createTextNode(text.slice(lastIndex)));
}
if (textNode.parentNode) {
textNode.parentNode.replaceChild(frag, textNode);
}
}
}
})();