diff --git a/popup/popup.css b/popup/popup.css
index 618718f..22f57e3 100644
--- a/popup/popup.css
+++ b/popup/popup.css
@@ -645,6 +645,72 @@ body {
text-align: center;
}
+/* Pagination (Words tab) */
+.pagination-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 6px 8px;
+ background: var(--section-bg);
+ border: 1px solid var(--input-border);
+ border-radius: 8px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+ font-size: 11px;
+}
+
+.pagination-info {
+ font-size: 11px;
+ opacity: 0.8;
+ white-space: nowrap;
+}
+
+.pagination-controls {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.pagination-btn {
+ background: var(--input-bg);
+ border: 1px solid var(--input-border);
+ color: var(--text-color);
+ border-radius: 6px;
+ padding: 3px 5px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 24px;
+ min-height: 24px;
+ font-size: 11px;
+}
+
+.pagination-btn:hover:not(:disabled) {
+ background: var(--section-bg);
+ border-color: var(--accent);
+}
+
+.pagination-btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.pagination-pages {
+ display: flex;
+ align-items: center;
+ padding: 0 4px;
+}
+
+.pagination-pages .page-info {
+ font-size: 11px;
+ opacity: 0.9;
+ font-weight: 500;
+}
+
/* Word item 3-dot menu dropdown */
.word-item-menu-dropdown {
display: none;
diff --git a/popup/popup.html b/popup/popup.html
index cb974ee..2a2e4d0 100644
--- a/popup/popup.html
+++ b/popup/popup.html
@@ -195,6 +195,7 @@
+
diff --git a/src/popup/PopupController.ts b/src/popup/PopupController.ts
index c207911..0094d16 100644
--- a/src/popup/PopupController.ts
+++ b/src/popup/PopupController.ts
@@ -9,6 +9,9 @@ export class PopupController {
private selectedCheckboxes = new Set();
private globalHighlightEnabled = true;
private wordSearchQuery = '';
+ private currentPage = 1;
+ private pageSize = 100;
+ private totalWords = 0;
private matchCaseEnabled = false;
private matchWholeEnabled = false;
private exceptionsList: string[] = [];
@@ -271,6 +274,7 @@ export class PopupController {
wordSearch.addEventListener('input', (e) => {
this.wordSearchQuery = (e.target as HTMLInputElement).value;
+ this.currentPage = 1;
this.renderWords();
});
}
@@ -1023,6 +1027,7 @@ export class PopupController {
if (!Number.isNaN(index)) {
this.selectedCheckboxes.clear();
this.currentListIndex = index;
+ this.currentPage = 1;
this.renderWords();
this.updateListForm();
this.renderLists();
@@ -1063,14 +1068,25 @@ export class PopupController {
filteredWords = list.words.filter(w => w.wordStr.toLowerCase().includes(q));
}
+ this.totalWords = filteredWords.length;
+
if (filteredWords.length === 0) {
wordList.innerHTML = 'No words found
';
const wordCount = document.getElementById('wordCount');
if (wordCount) wordCount.textContent = '0';
+ this.renderPaginationControls();
return;
}
- wordList.innerHTML = filteredWords.map(w => {
+ const totalPages = Math.ceil(this.totalWords / this.pageSize);
+ if (this.currentPage > totalPages) {
+ this.currentPage = Math.max(1, totalPages);
+ }
+ const startIndex = (this.currentPage - 1) * this.pageSize;
+ const endIndex = Math.min(startIndex + this.pageSize, this.totalWords);
+ const paginatedWords = filteredWords.slice(startIndex, endIndex);
+
+ wordList.innerHTML = paginatedWords.map(w => {
const realIndex = list.words.indexOf(w);
const isSelected = this.selectedCheckboxes.has(realIndex);
return this.createWordItemHTML(w, realIndex, isSelected);
@@ -1078,8 +1094,94 @@ export class PopupController {
const wordCount = document.getElementById('wordCount');
if (wordCount) {
- wordCount.textContent = filteredWords.length.toString();
+ wordCount.textContent = this.totalWords.toString();
}
+
+ this.renderPaginationControls();
+ }
+
+ private renderPaginationControls(): void {
+ const paginationContainer = document.getElementById('paginationControls');
+ if (!paginationContainer) return;
+
+ const totalPages = Math.ceil(this.totalWords / this.pageSize);
+
+ if (totalPages <= 1) {
+ paginationContainer.style.display = 'none';
+ return;
+ }
+
+ const startItem = (this.currentPage - 1) * this.pageSize + 1;
+ const endItem = Math.min(this.currentPage * this.pageSize, this.totalWords);
+
+ const showingText = chrome.i18n.getMessage('showing_items')
+ ?.replace('{start}', String(startItem))
+ .replace('{end}', String(endItem))
+ .replace('{total}', String(this.totalWords))
+ || `Showing ${startItem}-${endItem} of ${this.totalWords} words`;
+
+ const pageInfoText = chrome.i18n.getMessage('page_info')
+ ?.replace('{current}', String(this.currentPage))
+ .replace('{total}', String(totalPages))
+ || `Page ${this.currentPage} of ${totalPages}`;
+
+ const firstPageTitle = chrome.i18n.getMessage('first_page') || 'First page';
+ const prevPageTitle = chrome.i18n.getMessage('previous_page') || 'Previous page';
+ const nextPageTitle = chrome.i18n.getMessage('next_page') || 'Next page';
+ const lastPageTitle = chrome.i18n.getMessage('last_page') || 'Last page';
+
+ paginationContainer.style.display = 'flex';
+ paginationContainer.innerHTML = `
+
+
+ `;
+
+ this.setupPaginationEventListeners();
+ }
+
+ private setupPaginationEventListeners(): void {
+ document.getElementById('firstPageBtn')?.addEventListener('click', () => {
+ this.goToPage(1);
+ });
+
+ document.getElementById('prevPageBtn')?.addEventListener('click', () => {
+ this.goToPage(this.currentPage - 1);
+ });
+
+ document.getElementById('nextPageBtn')?.addEventListener('click', () => {
+ this.goToPage(this.currentPage + 1);
+ });
+
+ document.getElementById('lastPageBtn')?.addEventListener('click', () => {
+ const totalPages = Math.ceil(this.totalWords / this.pageSize);
+ this.goToPage(totalPages);
+ });
+ }
+
+ private goToPage(page: number): void {
+ const totalPages = Math.ceil(this.totalWords / this.pageSize);
+ if (page < 1 || page > totalPages) return;
+
+ this.currentPage = page;
+ this.renderWords();
}
private createWordItemHTML(word: HighlightWord, realIndex: number, isSelected: boolean): string {