/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
const HIGHLIGHT_NAME = 'settings-search-highlight';
export function isHighlightAPISupported(): boolean {
return typeof CSS !== 'undefined' && 'highlights' in CSS;
}
export function clearHighlights(): void {
if (!isHighlightAPISupported()) return;
CSS.highlights.clear();
}
function findAllTextNodes(container: HTMLElement): Array {
const textNodes: Array = [];
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
let currentNode = walker.nextNode();
while (currentNode) {
const textNode = currentNode as Text;
if (textNode.textContent && textNode.textContent.trim().length > 0) {
textNodes.push(textNode);
}
currentNode = walker.nextNode();
}
return textNodes;
}
function createRangesForMatches(textNodes: Array, query: string): Array {
const ranges: Array = [];
const cleanQuery = query.trim().toLowerCase();
if (!cleanQuery) return ranges;
textNodes.forEach((textNode) => {
const text = textNode.textContent || '';
const lowerText = text.toLowerCase();
let startPos = 0;
while (startPos < lowerText.length) {
const index = lowerText.indexOf(cleanQuery, startPos);
if (index === -1) break;
const range = new Range();
range.setStart(textNode, index);
range.setEnd(textNode, index + cleanQuery.length);
ranges.push(range);
startPos = index + cleanQuery.length;
}
});
return ranges;
}
export function createRangesForSection(container: HTMLElement, query: string): Array {
if (!isHighlightAPISupported()) {
return [];
}
const cleanQuery = query.trim();
if (!cleanQuery) {
return [];
}
const textNodes = findAllTextNodes(container);
return createRangesForMatches(textNodes, cleanQuery);
}
export function setHighlightRanges(ranges: Array): void {
if (!isHighlightAPISupported()) return;
CSS.highlights.clear();
if (ranges.length === 0) {
return;
}
const highlight = new Highlight(...ranges);
CSS.highlights.set(HIGHLIGHT_NAME, highlight);
}