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${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);
}
}