Update web/templates/entry_view.html
This commit is contained in:
parent
20e0d593bb
commit
b880085720
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user