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>
|
</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>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
// Target EVERY field we render on entry_view
|
// Target EVERY field we render on entry_view
|
||||||
@ -556,47 +556,79 @@ function showToast(message, duration = 3000) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeRegExp(str) {
|
function makeWordRegex() {
|
||||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
// 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) {
|
function highlightAll(root, needle) {
|
||||||
if (!needle) return;
|
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
|
// Special handling for SUBJECT chips: color the whole chip, no <mark> injection
|
||||||
if (root.id === "subject-list") {
|
if (root.id === "subject-list") {
|
||||||
root.querySelectorAll(".chip-subject, .chip-muted").forEach(chip => {
|
root.querySelectorAll(".chip-subject, .chip-muted").forEach(chip => {
|
||||||
if (re.test(chip.textContent || "")) {
|
if ((chip.textContent || "").toLowerCase().includes(needleLower)) {
|
||||||
chip.classList.add("chip-hit");
|
chip.classList.add("chip-hit");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return; // do not inject <mark> inside subject chips
|
return; // do not inject <mark> inside subject chips
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal inline highlighting for text fields
|
|
||||||
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);
|
||||||
|
|
||||||
|
const wordRe = makeWordRegex();
|
||||||
|
|
||||||
for (const textNode of nodes) {
|
for (const textNode of nodes) {
|
||||||
const text = textNode.nodeValue;
|
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();
|
const frag = document.createDocumentFragment();
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
text.replace(re, (m, idx) => {
|
wordRe.lastIndex = 0;
|
||||||
if (idx > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, idx)));
|
let m;
|
||||||
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)));
|
|
||||||
|
|
||||||
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