Подсветка поиска (CSS Highlight API)

Я пытаюсь реализовать функцию подсветки поиска. Но проблема в том, что это работает только тогда, когда выбран тест 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>
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
CSS: FlexBox
CSS: FlexBox
Ранее разработчики использовали макеты с помощью Position и Float. После появления flexbox сценарий полностью изменился.
0
0
131
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Обязательно включите этот блок кода в функцию 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);
}

Другие вопросы по теме