From 86b143f5a0148246159461de928a1c8eb9f0b307 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Sat, 22 Nov 2025 05:29:06 +0300 Subject: [PATCH] chore: refactor badge handling and highlight rendering for textareas --- src/content/HighlightEngine.ts | 170 +++++++++++++-------------------- 1 file changed, 68 insertions(+), 102 deletions(-) diff --git a/src/content/HighlightEngine.ts b/src/content/HighlightEngine.ts index 18c018c..aa4ac16 100644 --- a/src/content/HighlightEngine.ts +++ b/src/content/HighlightEngine.ts @@ -3,6 +3,73 @@ import { DOMUtils } from '../utils/DOMUtils.js'; export class HighlightEngine { + private static escapeHtml(text: string): string { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + private static renderHighlighted(text: string, pattern: RegExp): string { + let html = ''; + let lastIndex = 0; + pattern.lastIndex = 0; + let match; + while ((match = pattern.exec(text)) !== null) { + html += HighlightEngine.escapeHtml(text.substring(lastIndex, match.index)); + html += `${HighlightEngine.escapeHtml(match[0])}`; + lastIndex = match.index + match[0].length; + } + html += HighlightEngine.escapeHtml(text.substring(lastIndex)); + return html; + } + + private static createOrUpdateBadge(input: HTMLTextAreaElement | HTMLInputElement, pattern: RegExp): void { + const text = input.value; + let matchCount = 0; + pattern.lastIndex = 0; + let match; + while ((match = pattern.exec(text)) !== null) { + matchCount++; + } + const oldBadge = input.parentElement?.querySelector('.goose-highlighter-textarea-badge'); + if (oldBadge) oldBadge.remove(); + if (matchCount > 0) { + const badge = document.createElement('div'); + badge.className = 'goose-highlighter-textarea-badge'; + badge.textContent = matchCount.toString(); + badge.setAttribute('data-round', matchCount > 9 ? 'false' : 'true'); + badge.style.position = 'absolute'; + badge.style.left = '4px'; + badge.style.top = '4px'; + badge.style.zIndex = '10000'; + const parent = input.parentElement; + if (parent && window.getComputedStyle(parent).position === 'static') { + parent.style.position = 'relative'; + } + parent?.appendChild(badge); + badge.addEventListener('click', () => { + document.querySelectorAll('.goose-highlighter-textarea-popup').forEach(p => p.remove()); + const popup = document.createElement('div'); + popup.className = 'goose-highlighter-textarea-popup'; + popup.innerHTML = ` +
+ +
+
${HighlightEngine.renderHighlighted(text, pattern)}
+ `; + document.body.appendChild(popup); + const closeBtn = popup.querySelector('.gh-popup-close'); + closeBtn?.addEventListener('click', () => popup.remove()); + }); + } + } + + private static attachBadgeListener(input: HTMLTextAreaElement | HTMLInputElement, pattern: RegExp): void { + const updateBadge = () => HighlightEngine.createOrUpdateBadge(input, pattern); + updateBadge(); + input.removeEventListener('input', updateBadge); + input.addEventListener('input', updateBadge); + } private _textareaMatchInfo: Array<{ input: HTMLTextAreaElement | HTMLInputElement; count: number; text: string }> = []; private styleSheet: CSSStyleSheet | null = null; private highlights = new Map(); @@ -327,114 +394,13 @@ export class HighlightEngine { } `; document.head.appendChild(style); - } - // Helper to escape HTML - function escapeHtml(text: string): string { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - - function renderHighlighted(text: string): string { - let html = ''; - let lastIndex = 0; - pattern.lastIndex = 0; - let match; - while ((match = pattern.exec(text)) !== null) { - html += escapeHtml(text.substring(lastIndex, match.index)); - html += `${escapeHtml(match[0])}`; - lastIndex = match.index + match[0].length; - } - html += escapeHtml(text.substring(lastIndex)); - return html; } const textareas = document.querySelectorAll('textarea, input[type="text"], input[type="search"], input[type="email"], input[type="url"]'); this._textareaMatchInfo = []; document.querySelectorAll('.goose-highlighter-textarea-badge').forEach(badge => badge.remove()); for (const element of Array.from(textareas)) { const input = element as HTMLTextAreaElement | HTMLInputElement; - const text = input.value; - if (!text) continue; - let matchCount = 0; - pattern.lastIndex = 0; - let match; - while ((match = pattern.exec(text)) !== null) { - matchCount++; - } - if (matchCount > 0) { - this._textareaMatchInfo.push({ input, count: matchCount, text }); - const badge = document.createElement('div'); - badge.className = 'goose-highlighter-textarea-badge'; - badge.textContent = matchCount.toString(); - badge.setAttribute('data-round', matchCount > 9 ? 'false' : 'true'); - badge.style.position = 'absolute'; - badge.style.left = '4px'; - badge.style.top = '4px'; - badge.style.zIndex = '10000'; - const parent = input.parentElement; - if (parent && window.getComputedStyle(parent).position === 'static') { - parent.style.position = 'relative'; - } - parent?.appendChild(badge); - - badge.addEventListener('click', () => { - document.querySelectorAll('.goose-highlighter-textarea-popup').forEach(p => p.remove()); - const popup = document.createElement('div'); - popup.className = 'goose-highlighter-textarea-popup'; - popup.innerHTML = ` -
- -
-
${renderHighlighted(text)}
- `; - document.body.appendChild(popup); - const closeBtn = popup.querySelector('.gh-popup-close'); - closeBtn?.addEventListener('click', () => popup.remove()); - }); - } - const updateBadge = () => { - const text = input.value; - let matchCount = 0; - pattern.lastIndex = 0; - let match; - while ((match = pattern.exec(text)) !== null) { - matchCount++; - } - const oldBadge = input.parentElement?.querySelector('.goose-highlighter-textarea-badge'); - if (oldBadge) oldBadge.remove(); - if (matchCount > 0) { - const badge = document.createElement('div'); - badge.className = 'goose-highlighter-textarea-badge'; - badge.textContent = matchCount.toString(); - badge.setAttribute('data-round', matchCount > 9 ? 'false' : 'true'); - badge.style.position = 'absolute'; - badge.style.left = '4px'; - badge.style.top = '4px'; - badge.style.zIndex = '10000'; - const parent = input.parentElement; - if (parent && window.getComputedStyle(parent).position === 'static') { - parent.style.position = 'relative'; - } - parent?.appendChild(badge); - badge.addEventListener('click', () => { - document.querySelectorAll('.goose-highlighter-textarea-popup').forEach(p => p.remove()); - const popup = document.createElement('div'); - popup.className = 'goose-highlighter-textarea-popup'; - popup.innerHTML = ` -
- -
-
${renderHighlighted(text)}
- `; - document.body.appendChild(popup); - const closeBtn = popup.querySelector('.gh-popup-close'); - closeBtn?.addEventListener('click', () => popup.remove()); - }); - } - }; - updateBadge(); - input.removeEventListener('input', updateBadge); - input.addEventListener('input', updateBadge); + HighlightEngine.attachBadgeListener(input, pattern); } }