diff --git a/src/content/ContentScript.ts b/src/content/ContentScript.ts index 138aad8..3dfe7ba 100644 --- a/src/content/ContentScript.ts +++ b/src/content/ContentScript.ts @@ -2,7 +2,6 @@ import { HighlightList, MessageData } from '../types.js'; import { StorageService } from '../services/StorageService.js'; import { MessageService } from '../services/MessageService.js'; import { HighlightEngine } from './HighlightEngine.js'; -import { DOMUtils } from '../utils/DOMUtils.js'; export class ContentScript { private lists: HighlightList[] = []; @@ -22,7 +21,6 @@ export class ContentScript { private async initialize(): Promise { await this.loadSettings(); this.setupMessageListener(); - this.setupScrollHandler(); this.processHighlights(); } @@ -67,10 +65,6 @@ export class ContentScript { }); } - private setupScrollHandler(): void { - const debouncedProcess = DOMUtils.debounce(() => this.processHighlights(), CONSTANTS.DEBOUNCE_DELAY); - window.addEventListener('scroll', debouncedProcess); - } private async handleWordListUpdate(): Promise { const data = await StorageService.get(['lists']); diff --git a/src/popup/PopupController.ts b/src/popup/PopupController.ts index 3ac1f26..b81793b 100644 --- a/src/popup/PopupController.ts +++ b/src/popup/PopupController.ts @@ -193,7 +193,7 @@ export class PopupController { } }); - wordList.addEventListener('input', (e) => { + wordList.addEventListener('change', (e) => { const target = e.target as HTMLInputElement; const index = +(target.dataset.bgEdit ?? target.dataset.fgEdit ?? -1); if (index === -1) return; @@ -219,13 +219,14 @@ export class PopupController { } }); - let scrollTimeout: number; + let scrolling = false; wordList.addEventListener('scroll', () => { - if (scrollTimeout) return; - scrollTimeout = window.setTimeout(() => { - requestAnimationFrame(() => this.renderWords()); - scrollTimeout = 0; - }, 16); + if (scrolling) return; + scrolling = true; + requestAnimationFrame(() => { + this.renderWords(); + scrolling = false; + }); }); } @@ -278,24 +279,38 @@ export class PopupController { const matchCase = document.getElementById('matchCase') as HTMLInputElement; const matchWhole = document.getElementById('matchWhole') as HTMLInputElement; - globalToggle.addEventListener('change', () => { + globalToggle.addEventListener('change', async () => { this.globalHighlightEnabled = globalToggle.checked; - this.updateGlobalToggleState(); + await StorageService.update('globalHighlightEnabled', this.globalHighlightEnabled); + MessageService.sendToAllTabs({ + type: 'GLOBAL_TOGGLE_UPDATED', + enabled: this.globalHighlightEnabled + }); }); - matchCase.addEventListener('change', () => { + matchCase.addEventListener('change', async () => { this.matchCaseEnabled = matchCase.checked; - this.save(); + await StorageService.update('matchCaseEnabled', this.matchCaseEnabled); + MessageService.sendToAllTabs({ + type: 'MATCH_OPTIONS_UPDATED', + matchCase: this.matchCaseEnabled, + matchWhole: this.matchWholeEnabled + }); }); - matchWhole.addEventListener('change', () => { + matchWhole.addEventListener('change', async () => { this.matchWholeEnabled = matchWhole.checked; - this.save(); + await StorageService.update('matchWholeEnabled', this.matchWholeEnabled); + MessageService.sendToAllTabs({ + type: 'MATCH_OPTIONS_UPDATED', + matchCase: this.matchCaseEnabled, + matchWhole: this.matchWholeEnabled + }); }); } private setupExceptions(): void { - document.getElementById('toggleExceptionBtn')?.addEventListener('click', () => { + document.getElementById('toggleExceptionBtn')?.addEventListener('click', async () => { if (!this.currentTabHost) return; const isException = this.exceptionsList.includes(this.currentTabHost); @@ -308,26 +323,29 @@ export class PopupController { this.updateExceptionButton(); this.renderExceptions(); - this.save(); + await StorageService.update('exceptionsList', this.exceptionsList); + MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' }); }); - document.getElementById('clearExceptionsBtn')?.addEventListener('click', () => { + document.getElementById('clearExceptionsBtn')?.addEventListener('click', async () => { if (confirm(chrome.i18n.getMessage('confirm_clear_exceptions') || 'Clear all exceptions?')) { this.exceptionsList = []; this.updateExceptionButton(); this.renderExceptions(); - this.save(); + await StorageService.update('exceptionsList', this.exceptionsList); + MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' }); } }); - document.getElementById('exceptionsList')?.addEventListener('click', (e) => { + document.getElementById('exceptionsList')?.addEventListener('click', async (e) => { const target = e.target as HTMLElement; if (target.classList.contains('exception-remove')) { const domain = target.dataset.domain!; this.exceptionsList = this.exceptionsList.filter(d => d !== domain); this.updateExceptionButton(); this.renderExceptions(); - this.save(); + await StorageService.update('exceptionsList', this.exceptionsList); + MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' }); } }); } @@ -436,29 +454,13 @@ export class PopupController { exceptionsList: this.exceptionsList }); - this.render(); + this.renderLists(); MessageService.sendToAllTabs({ type: 'WORD_LIST_UPDATED' }); - MessageService.sendToAllTabs({ - type: 'GLOBAL_TOGGLE_UPDATED', - enabled: this.globalHighlightEnabled - }); - MessageService.sendToAllTabs({ - type: 'MATCH_OPTIONS_UPDATED', - matchCase: this.matchCaseEnabled, - matchWhole: this.matchWholeEnabled - }); - MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' }); } - private async updateGlobalToggleState(): Promise { - await StorageService.update('globalHighlightEnabled', this.globalHighlightEnabled); - MessageService.sendToAllTabs({ - type: 'GLOBAL_TOGGLE_UPDATED', - enabled: this.globalHighlightEnabled - }); - } + private render(): void { this.renderLists(); @@ -500,11 +502,9 @@ export class PopupController { const totalItemHeight = itemHeight + itemSpacing; const containerHeight = wordList.clientHeight || 250; const scrollTop = wordList.scrollTop; - const startIndex = Math.floor(scrollTop / totalItemHeight); - const endIndex = Math.min( - startIndex + Math.ceil(containerHeight / totalItemHeight) + 2, - filteredWords.length - ); + const startIndex = Math.max(0, Math.floor(scrollTop / totalItemHeight) - 1); + const visibleCount = Math.ceil(containerHeight / totalItemHeight); + const endIndex = Math.min(startIndex + visibleCount + 2, filteredWords.length); wordList.innerHTML = ''; diff --git a/src/types.ts b/src/types.ts index d4c5994..26a5520 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,6 +50,6 @@ export const DEFAULT_STORAGE: StorageData = { export const CONSTANTS = { WORD_ITEM_HEIGHT: 32, - DEBOUNCE_DELAY: 300, + DEBOUNCE_DELAY: 150, SCROLL_THROTTLE: 16 } as const; \ No newline at end of file