vuejs / vitepress

Vite & Vue powered static site generator.
https://vitepress.dev
MIT License
13.25k stars 2.14k forks source link

CSS Custom Highlight API #4280

Open ghost opened 1 month ago

ghost commented 1 month ago

Is your feature request related to a problem? Please describe.

Highlighting search results keywords using string replacement is outdated

Describe the solution you'd like

Native API CSS.highlights https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API

const query = document.getElementById("query");
const article = document.querySelector("article");

// 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();
}

// Listen to the input event to run the search.
query.addEventListener("input", () => {
  // If the CSS Custom Highlight API is not supported,
  // display a message and bail-out.
  if (!CSS.highlights) {
    article.textContent = "CSS Custom Highlight API not supported.";
    return;
  }

  // Clear the HighlightRegistry to remove the
  // previous search results.
  CSS.highlights.clear();

  // Clean-up the search query and bail-out if
  // if it's empty.
  const str = query.value.trim().toLowerCase();
  if (!str) {
    return;
  }

  // Iterate over all text nodes and find matches.
  const ranges = allTextNodes
    .map((el) => {
      return { el, text: el.textContent.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;
      }

      // Create a range object for each instance of
      // str we found in the text node.
      return indices.map((index) => {
        const range = new Range();
        range.setStart(el, index);
        range.setEnd(el, index + str.length);
        return range;
      });
    });

  // 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);
});

css

::highlight(search-results) {
  background-color: #f06;
  color: white;
}

Describe alternatives you've considered

No response

Additional context

No response

Validations

ghost commented 1 month ago

Adding keyword highlighting to the search results page would be even better

brc-dd commented 1 month ago

Very nice. But it currently doesn't match our browser support criteria (chrome/edge >= 88, firefox >= 78, safari >= 14). In future, we might replace mark.js with it 👀 Keeping this open for now.