mirror of
https://github.com/obsqrbtz/goose-highlighter.git
synced 2026-04-08 20:19:06 +03:00
chore: refactor badge handling and highlight rendering for textareas
This commit is contained in:
@@ -3,6 +3,73 @@ import { DOMUtils } from '../utils/DOMUtils.js';
|
|||||||
|
|
||||||
|
|
||||||
export class HighlightEngine {
|
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 += `<mark>${HighlightEngine.escapeHtml(match[0])}</mark>`;
|
||||||
|
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 = `
|
||||||
|
<div class="gh-popup-titlebar">
|
||||||
|
<button class="gh-popup-close" title="Close">×</button>
|
||||||
|
</div>
|
||||||
|
<pre class="gh-popup-pre">${HighlightEngine.renderHighlighted(text, pattern)}</pre>
|
||||||
|
`;
|
||||||
|
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 _textareaMatchInfo: Array<{ input: HTMLTextAreaElement | HTMLInputElement; count: number; text: string }> = [];
|
||||||
private styleSheet: CSSStyleSheet | null = null;
|
private styleSheet: CSSStyleSheet | null = null;
|
||||||
private highlights = new Map<string, Highlight>();
|
private highlights = new Map<string, Highlight>();
|
||||||
@@ -327,114 +394,13 @@ export class HighlightEngine {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
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 += `<mark>${escapeHtml(match[0])}</mark>`;
|
|
||||||
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"]');
|
const textareas = document.querySelectorAll('textarea, input[type="text"], input[type="search"], input[type="email"], input[type="url"]');
|
||||||
this._textareaMatchInfo = [];
|
this._textareaMatchInfo = [];
|
||||||
document.querySelectorAll('.goose-highlighter-textarea-badge').forEach(badge => badge.remove());
|
document.querySelectorAll('.goose-highlighter-textarea-badge').forEach(badge => badge.remove());
|
||||||
for (const element of Array.from(textareas)) {
|
for (const element of Array.from(textareas)) {
|
||||||
const input = element as HTMLTextAreaElement | HTMLInputElement;
|
const input = element as HTMLTextAreaElement | HTMLInputElement;
|
||||||
const text = input.value;
|
HighlightEngine.attachBadgeListener(input, pattern);
|
||||||
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 = `
|
|
||||||
<div class="gh-popup-titlebar">
|
|
||||||
<button class="gh-popup-close" title="Close">×</button>
|
|
||||||
</div>
|
|
||||||
<pre class="gh-popup-pre">${renderHighlighted(text)}</pre>
|
|
||||||
`;
|
|
||||||
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 = `
|
|
||||||
<div class="gh-popup-titlebar">
|
|
||||||
<button class="gh-popup-close" title="Close">×</button>
|
|
||||||
</div>
|
|
||||||
<pre class="gh-popup-pre">${renderHighlighted(text)}</pre>
|
|
||||||
`;
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user