module.exports = function highlightSearchTerm(card, searchTerm) { if (!card || !searchTerm) return; const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); try { // 兼容 projects repo 卡片:title/desc 不一定是 h3/p const titleElement = card.querySelector('h3') || card.querySelector('.repo-title'); const descriptionElement = card.querySelector('p') || card.querySelector('.repo-desc'); const hasPinyinMatch = typeof PinyinMatch !== 'undefined' && PinyinMatch && typeof PinyinMatch.match === 'function'; const applyRangeHighlight = (element, start, end) => { const text = element.textContent || ''; const safeStart = Math.max(0, Math.min(text.length, start)); const safeEnd = Math.max(safeStart, Math.min(text.length - 1, end)); const fragment = document.createDocumentFragment(); fragment.appendChild(document.createTextNode(text.slice(0, safeStart))); const span = document.createElement('span'); span.className = 'highlight'; span.textContent = text.slice(safeStart, safeEnd + 1); fragment.appendChild(span); fragment.appendChild(document.createTextNode(text.slice(safeEnd + 1))); while (element.firstChild) { element.removeChild(element.firstChild); } element.appendChild(fragment); }; const highlightInElement = (element) => { if (!element) return; const rawText = element.textContent || ''; const lowerText = rawText.toLowerCase(); if (!rawText) return; if (lowerText.includes(searchTerm)) { const regex = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi'); const fragment = document.createDocumentFragment(); let lastIndex = 0; let match; while ((match = regex.exec(rawText)) !== null) { if (match.index > lastIndex) { fragment.appendChild( document.createTextNode(rawText.substring(lastIndex, match.index)) ); } const span = document.createElement('span'); span.className = 'highlight'; span.textContent = match[0]; fragment.appendChild(span); lastIndex = match.index + match[0].length; // 防止无限循环 if (regex.lastIndex === 0) break; } if (lastIndex < rawText.length) { fragment.appendChild(document.createTextNode(rawText.substring(lastIndex))); } while (element.firstChild) { element.removeChild(element.firstChild); } element.appendChild(fragment); return; } if (hasPinyinMatch) { const arr = PinyinMatch.match(rawText, searchTerm); if (Array.isArray(arr) && arr.length >= 2) { const [start, end] = arr; applyRangeHighlight(element, start, end); } } }; highlightInElement(titleElement); highlightInElement(descriptionElement); } catch (error) { console.error('Error highlighting search term'); } };