Я пытаюсь реализовать функцию подсветки поиска. Но проблема в том, что это работает только тогда, когда выбран тест 1. На других страницах это не работает, и я понятия не имею, почему. Я предполагаю, что это потому, что функция выделения вызывается только один раз, и когда я выбираю, например, тест 2, она не вызывается снова. Пробовал разные варианты, но не получилось. Поскольку это была часть конкурса, важно не менять другие части кода, а только финальный скрипт.
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8" />
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
<title></title>
</head>
<style>
:root {
--bg-primary: #fbfbfb;
--bg-secondary: #fff;
--control-primary: #fdde55;
--color-primary: #000;
--depot-color-stroke: rgba(7, 28, 71, 0.12);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #111112;
--bg-secondary: #18181a;
--color-primary: #fff;
--depot-color-stroke: rgba(255, 255, 255, 0.12);
}
}
body {
background-color: var(--bg-primary);
font-family: Helvetica, Arial, sans-serif;
}
header {
box-shadow: 0 1px var(--depot-color-stroke);
margin-block-end: 12px;
padding-block-end: 8px;
}
.select-wrapper {
color: var(--color-primary);
margin-block-end: 12px;
}
.select-wrapper select {
min-width: 40px;
cursor: pointer;
font-size: 20px;
}
.search {
display: flex;
overflow: hidden;
flex: 1 1;
box-sizing: border-box;
height: 44px;
border: 2px solid #fc0;
border: 2px solid var(--control-primary);
border-radius: 12px;
}
.search input {
flex: 1 1;
box-sizing: border-box;
padding-left: 14px;
font-family: inherit;
font-size: 16px;
text-overflow: clip;
color: var(--color-primary);
border: 0;
outline: 0;
background: initial;
}
.card-item {
padding: 12px 16px;
border-radius: 16px;
color: var(--color-primary);
background-color: var(--bg-secondary);
box-shadow: 0 4px 12px #0d234308;
}
</style>
<style>
::highlight(search-results) {
background-color: orange;
text-decoration: underline;
}
.search-results {
background-color: orange;
text-decoration: underline;
}
</style>
<body>
<header>
<div class = "select-wrapper">
<label for = "tests-select">Choose test</label>
<select name = "tests" id = "tests-select"></select>
</div>
<form class = "search" role = "search" aria-label = "Search">
<input
id = "site-search"
type = "text"
autocomplete = "off"
aria-label = "Query"
/>
</form>
</header>
<div id = "root" class = "card-item"></div>
</body>
<script>
const rootElement = document.getElementById("root");
const testsSelect = document.getElementById("tests-select");
rootElement.addEventListener("onSolutionReady", (event) => {
const { detail } = event;
if (detail) {
testsSelect.innerHTML = detail
.map((t, index) => `<option value = "${t.id}">${index + 1}</option>`)
.join("");
rootElement.innerHTML = detail[0].content;
testsSelect.addEventListener("change", (e) => {
const test = detail.find((t) => t.id === e.target.value);
rootElement.innerHTML = test.content;
});
}
});
</script>
<script>
const onSolutionReady = new CustomEvent("onSolutionReady", {
bubbles: true,
cancelable: true,
composed: false,
detail: [
{
id: "f38d0cca-167c-46dc-9504-69ebe13c1e47",
comment:
"One text node. Content contains in the middle of a single tag",
content: `<p>sit amet, Lorem ipsum. Sed non risus</p>`,
searchFor: "Lorem ipsum",
},
{
id: "20b81641-b065-492d-801a-e786d2a6894b",
comment: "One text node. Content contains in the end of a single tag",
content: `<p>Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, Lorem ipsum</p>`,
searchFor: "Lorem ipsum",
},
{
id: "c8b707f7-91e9-4778-acc6-4f06849bd323",
comment: "One text node and content contains in a single tag",
content: `
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.</p>
`,
searchFor: "Lorem ipsum",
},
{
id: "eb375bed-bf98-4150-b8ac-711a6c0fe33a",
comment:
"The two text nodes and content are contained in sibling tags",
content: `
<div><p>Lorem </p><p>ipsum</p></div>
`,
searchFor: "Lorem ipsum",
},
],
});
document.getElementById("root").dispatchEvent(onSolutionReady);
</script>
<script>
if (!CSS.highlights) {
document.getElementById("root").innerHTML =
"CSS Custom Highlight API is not supported. <br />Please, choose another browser. <a href='https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API#browser_compatibility'>More</a>";
}
</script>
<script>
// Copy paste this script
const root = document.getElementById("root");
const siteSearch = document.getElementById("site-search");
const selectTest = document.getElementById("tests-select");
const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
testsSelect.addEventListener("change", highlight);
siteSearch.addEventListener("input", highlight);
function highlight() {
const str = siteSearch.value.trim().toLowerCase();
if (!str) {
CSS.highlights.clear();
return;
}
const ranges = allTextNodes
.map((el) => {
return { el, text: el.nodeValue.toLowerCase() };
})
.map(({ text, el }) => {
const indices = [];
let startPos = 0;
while (startPos < text.length) {
const index = text.indexOf(str, startPos);
if (index === -1) break;
indices.push(index);
startPos = index + str.length;
}
return indices.map((index) => {
const range = new Range();
range.setStart(el, index);
range.setEnd(el, index + str.length);
return range;
});
});
const searchResultsHighlight = new Highlight(...ranges.flat());
// Register the Highlight object in the registry.
CSS.highlights.set("search-results", searchResultsHighlight);
}
</script>
</html>






Обязательно включите этот блок кода в функцию Highlight():
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
С такой же задачей я столкнулся во время теста в летней школе Яндекса. Хотя мне удалось решить эту проблему и у меня все работало нормально, тестер указал, что это неправильно. Похоже, рекрутеры и пожилые люди ожидали другого алгоритма.
Лучшее, что я мог сделать, это заставить работать выделение в первых трех тестах, в четвертом выделялось только одно слово «Лорем».
const root = document.getElementById("root");
const siteSearch = document.getElementById("site-search");
const selectTest = document.getElementById("tests-select");
siteSearch.addEventListener("input", highlight);
selectTest.addEventListener("change", highlight);
function highlight() {
const article = document.querySelector(".card-item");
// Clear the HighlightRegistry to remove the previous search results.
CSS.highlights.clear();
// Clean-up the search query and bail-out if it's empty.
const str = siteSearch.value.trim().toLowerCase();
if (!str) {
return;
}
// Find all text nodes in the article. We'll search within these text nodes.
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
allTextNodes.push(currentNode);
currentNode = treeWalker.nextNode();
}
// Concatenate all text nodes' content to find matches across nodes.
const fullText = allTextNodes.map(node => node.textContent.toLowerCase()).join("");
const indices = [];
let startPos = 0;
while (startPos < fullText.length) {
const index = fullText.indexOf(str, startPos);
if (index === -1) break;
indices.push(index);
startPos = index + str.length;
}
// Create a range object for each instance of str we found in the full text.
const ranges = [];
indices.forEach(index => {
let count = index;
for (const node of allTextNodes) {
const nodeLength = node.textContent.toLowerCase().length;
if (count >= nodeLength) {
count -= nodeLength;
} else {
const range = new Range();
range.setStart(node, count);
// Ensure the range does not exceed the node's length
const endOffset = count + str.length > nodeLength ? nodeLength : count + str.length;
range.setEnd(node, endOffset);
ranges.push(range);
break;
}
}
});
// Flatten the ranges array and create a Highlight object for the ranges.
const searchResultsHighlight = new Highlight(...ranges.flat());
// Register the Highlight object in the registry.
CSS.highlights.set("search-results", searchResultsHighlight);
}