mirror of
https://github.com/obsqrbtz/goose-highlighter.git
synced 2026-04-08 20:19:06 +03:00
updated list items ui
This commit is contained in:
@@ -137,40 +137,86 @@ body {
|
||||
.list-hint {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.selection-hint {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.6;
|
||||
margin-top: 4px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.lists {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* Custom scrollbar styling */
|
||||
.lists::-webkit-scrollbar,
|
||||
.word-list::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.lists::-webkit-scrollbar-track,
|
||||
.word-list::-webkit-scrollbar-track {
|
||||
background: #1a1511;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.lists::-webkit-scrollbar-thumb,
|
||||
.word-list::-webkit-scrollbar-thumb {
|
||||
background: var(--input-border);
|
||||
border-radius: 10px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.lists::-webkit-scrollbar-thumb:hover,
|
||||
.word-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #4a3e36;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
background: #1f1813;
|
||||
border: 1px solid transparent;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background: #251f19;
|
||||
border-color: rgba(242, 168, 101, 0.3);
|
||||
}
|
||||
|
||||
.list-item.active {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
||||
background: #2a2218;
|
||||
}
|
||||
|
||||
.list-item.selected {
|
||||
background: rgba(242, 168, 101, 0.15);
|
||||
border-color: rgba(242, 168, 101, 0.6);
|
||||
}
|
||||
|
||||
.list-item.selected.active {
|
||||
border-color: var(--accent);
|
||||
background: rgba(242, 168, 101, 0.2);
|
||||
}
|
||||
|
||||
.list-item.drag-over {
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.list-item input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.list-meta {
|
||||
@@ -279,7 +325,7 @@ body {
|
||||
background: #1a1511;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@@ -291,20 +337,129 @@ body {
|
||||
|
||||
.word-item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto auto auto;
|
||||
grid-template-columns: 1fr auto auto auto auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
background: #201915;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.word-item:hover {
|
||||
background: #2a2218;
|
||||
border-color: rgba(242, 168, 101, 0.2);
|
||||
}
|
||||
|
||||
.word-item.selected {
|
||||
background: rgba(242, 168, 101, 0.15);
|
||||
border-color: rgba(242, 168, 101, 0.6);
|
||||
}
|
||||
|
||||
.word-item.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.word-item input[type="text"] {
|
||||
.word-text {
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.word-text.editing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.word-edit-input {
|
||||
display: none;
|
||||
width: 100%;
|
||||
background: var(--input-bg);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.word-edit-input.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.word-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.word-actions > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(242, 168, 101, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.icon-btn i {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 11px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toggle-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--text-color);
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.toggle-btn.active::after {
|
||||
left: 20px;
|
||||
background: var(--accent-text);
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -379,6 +534,7 @@ input[type="color"]::-webkit-color-swatch {
|
||||
width: 38px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.word-item input[type="color"]::-webkit-color-swatch-wrapper {
|
||||
@@ -389,6 +545,11 @@ input[type="color"]::-webkit-color-swatch {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.word-item input[type="color"]:hover {
|
||||
transform: scale(1.05);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
<button id="moveWordsBtn"><i class="fa-solid fa-arrow-right"></i> Move</button>
|
||||
<button id="copyWordsBtn"><i class="fa-solid fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="selection-hint">Click to select • Ctrl/Cmd+Click for multi-select • Click edit icon to rename</div>
|
||||
</div>
|
||||
|
||||
<div id="wordList" class="word-list"></div>
|
||||
|
||||
133
popup/popup.css
133
popup/popup.css
@@ -272,10 +272,18 @@ body.light {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto auto 1fr;
|
||||
grid-template-rows: auto auto auto 1fr auto;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
.selection-hint {
|
||||
font-size: 0.7em;
|
||||
opacity: 0.6;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
padding: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.section[data-section="addwords"] textarea {
|
||||
height: 44px;
|
||||
}
|
||||
@@ -589,23 +597,29 @@ input[type="file"] {
|
||||
left: 4px;
|
||||
height: 34px;
|
||||
display: grid;
|
||||
grid-template-columns: 18px 1fr 26px 26px;
|
||||
grid-template-columns: 1fr auto auto auto auto;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 8px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#wordList .word-item:hover {
|
||||
background: var(--highlight-tag);
|
||||
border-color: var(--accent);
|
||||
border-color: rgba(242, 168, 101, 0.3);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
#wordList .word-item.selected {
|
||||
background: rgba(242, 168, 101, 0.15);
|
||||
border-color: rgba(242, 168, 101, 0.6);
|
||||
}
|
||||
|
||||
#wordList .word-item.disabled {
|
||||
opacity: 0.5;
|
||||
background: var(--section-bg);
|
||||
@@ -615,18 +629,30 @@ input[type="file"] {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
#wordList .word-item.disabled input[type="text"] {
|
||||
color: var(--text-color) !important;
|
||||
opacity: 0.6;
|
||||
#wordList .word-item.disabled .word-text {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
#wordList input[type="text"] {
|
||||
#wordList .word-text {
|
||||
font-size: 0.8em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#wordList .word-text.editing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#wordList .word-edit-input {
|
||||
display: none;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
background-color: var(--section-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
border: 1px solid var(--input-border) !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.8em;
|
||||
@@ -634,11 +660,88 @@ input[type="file"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#wordList input[type="text"]:focus {
|
||||
#wordList .word-edit-input.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#wordList .word-edit-input:focus {
|
||||
border-color: var(--accent) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#wordList .word-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#wordList .word-actions > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#wordList .icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding: 3px 5px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.75em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
#wordList .icon-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(242, 168, 101, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
#wordList .icon-btn i {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#wordList .toggle-btn {
|
||||
width: 32px;
|
||||
height: 18px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 9px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#wordList .toggle-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: var(--text-color);
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#wordList .toggle-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
#wordList .toggle-btn.active::after {
|
||||
left: 16px;
|
||||
background: var(--accent-text);
|
||||
}
|
||||
|
||||
#wordList input[type="color"] {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
@@ -646,18 +749,14 @@ input[type="file"] {
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--input-border);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
transition: all 0.2s;
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#wordList input[type="color"]:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
#wordList input[type="checkbox"] {
|
||||
margin: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#wordCount {
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
</div>
|
||||
<input type="text" id="wordSearch" data-i18n="search_placeholder" placeholder="Search..." />
|
||||
<div id="wordList"></div>
|
||||
<div class="selection-hint">Click to select • Ctrl/Cmd+Click for multi-select</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -64,15 +64,16 @@ export class ListManagerController {
|
||||
|
||||
const listsContainer = document.getElementById('listsContainer');
|
||||
listsContainer?.addEventListener('click', (e) => this.handleListClick(e));
|
||||
listsContainer?.addEventListener('change', (e) => this.handleListCheckboxChange(e));
|
||||
listsContainer?.addEventListener('dragstart', (e) => this.handleDragStart(e));
|
||||
listsContainer?.addEventListener('dragover', (e) => this.handleDragOver(e));
|
||||
listsContainer?.addEventListener('drop', (e) => this.handleDrop(e));
|
||||
listsContainer?.addEventListener('dragend', () => this.clearDragState());
|
||||
|
||||
const wordList = document.getElementById('wordList');
|
||||
wordList?.addEventListener('click', (e) => this.handleWordListClick(e));
|
||||
wordList?.addEventListener('change', (e) => this.handleWordListChange(e));
|
||||
wordList?.addEventListener('keydown', (e) => this.handleWordListKeydown(e));
|
||||
wordList?.addEventListener('blur', (e) => this.handleWordListBlur(e), true);
|
||||
}
|
||||
|
||||
private setupStorageSync(): void {
|
||||
@@ -313,29 +314,28 @@ export class ListManagerController {
|
||||
const listItem = target.closest('.list-item') as HTMLElement | null;
|
||||
if (!listItem) return;
|
||||
|
||||
if (target.tagName === 'INPUT' || target.tagName === 'BUTTON') return;
|
||||
|
||||
const index = Number(listItem.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
|
||||
const mouseEvent = event as MouseEvent;
|
||||
// Ctrl/Cmd + click for multi-select
|
||||
if (mouseEvent.ctrlKey || mouseEvent.metaKey) {
|
||||
if (this.selectedLists.has(index)) {
|
||||
this.selectedLists.delete(index);
|
||||
} else {
|
||||
this.selectedLists.add(index);
|
||||
}
|
||||
this.renderLists();
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular click - set as current and clear multi-selection
|
||||
this.currentListIndex = index;
|
||||
this.selectedLists.clear();
|
||||
this.selectedWords.clear();
|
||||
this.render();
|
||||
}
|
||||
|
||||
private handleListCheckboxChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.classList.contains('list-checkbox')) return;
|
||||
const index = Number(target.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
|
||||
if (target.checked) {
|
||||
this.selectedLists.add(index);
|
||||
} else {
|
||||
this.selectedLists.delete(index);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDragStart(event: DragEvent): void {
|
||||
const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null;
|
||||
if (!target) return;
|
||||
@@ -384,23 +384,84 @@ export class ListManagerController {
|
||||
document.querySelectorAll('.list-item.drag-over').forEach(item => item.classList.remove('drag-over'));
|
||||
}
|
||||
|
||||
private handleWordListChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
private handleWordListClick(event: Event): void {
|
||||
const target = event.target as HTMLElement;
|
||||
const list = this.lists[this.currentListIndex];
|
||||
if (!list) return;
|
||||
|
||||
if (target.classList.contains('word-checkbox') && target.dataset.index != null) {
|
||||
const editBtn = target.closest('.edit-word-btn') as HTMLElement | null;
|
||||
if (editBtn) {
|
||||
event.stopPropagation();
|
||||
const index = Number(editBtn.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
this.startEditingWord(index);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle toggle button click
|
||||
if (target.classList.contains('toggle-btn')) {
|
||||
event.stopPropagation();
|
||||
const index = Number(target.dataset.index);
|
||||
if (target.checked) {
|
||||
this.selectedWords.add(index);
|
||||
} else {
|
||||
if (Number.isNaN(index)) return;
|
||||
const word = list.words[index];
|
||||
if (word) {
|
||||
word.active = !word.active;
|
||||
this.save();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't select if clicking on color inputs or edit input
|
||||
if (target.tagName === 'INPUT') {
|
||||
if ((target as HTMLInputElement).type === 'color') {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (target.classList.contains('word-edit-input')) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't select if clicking inside word-actions area (except on the word item itself)
|
||||
if (target.closest('.word-actions') && !target.classList.contains('word-item')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle word item selection
|
||||
const wordItem = target.closest('.word-item') as HTMLElement | null;
|
||||
if (!wordItem) return;
|
||||
|
||||
const index = Number(wordItem.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
|
||||
const mouseEvent = event as MouseEvent;
|
||||
// Ctrl/Cmd + click for multi-select
|
||||
if (mouseEvent.ctrlKey || mouseEvent.metaKey) {
|
||||
if (this.selectedWords.has(index)) {
|
||||
this.selectedWords.delete(index);
|
||||
} else {
|
||||
this.selectedWords.add(index);
|
||||
}
|
||||
this.renderWords();
|
||||
return;
|
||||
}
|
||||
|
||||
const editIndex = Number(target.dataset.bgEdit ?? target.dataset.fgEdit ?? target.dataset.activeEdit ?? -1);
|
||||
// Regular click - toggle selection
|
||||
if (this.selectedWords.has(index)) {
|
||||
this.selectedWords.delete(index);
|
||||
} else {
|
||||
this.selectedWords.add(index);
|
||||
}
|
||||
this.renderWords();
|
||||
}
|
||||
|
||||
private handleWordListChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const list = this.lists[this.currentListIndex];
|
||||
if (!list) return;
|
||||
|
||||
const editIndex = Number(target.dataset.bgEdit ?? target.dataset.fgEdit ?? -1);
|
||||
if (Number.isNaN(editIndex) || editIndex < 0) return;
|
||||
|
||||
const word = list.words[editIndex];
|
||||
@@ -408,14 +469,28 @@ export class ListManagerController {
|
||||
|
||||
if (target.dataset.bgEdit != null) word.background = target.value;
|
||||
if (target.dataset.fgEdit != null) word.foreground = target.value;
|
||||
if (target.dataset.activeEdit != null) word.active = target.checked;
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
private handleWordListKeydown(event: KeyboardEvent): void {
|
||||
if (event.key !== 'Enter') return;
|
||||
private startEditingWord(index: number): void {
|
||||
const wordItem = document.querySelector(`.word-item[data-index="${index}"]`);
|
||||
if (!wordItem) return;
|
||||
|
||||
const textSpan = wordItem.querySelector('.word-text') as HTMLElement;
|
||||
const input = wordItem.querySelector('.word-edit-input') as HTMLInputElement;
|
||||
if (!textSpan || !input) return;
|
||||
|
||||
textSpan.classList.add('editing');
|
||||
input.classList.add('active');
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
private handleWordListBlur(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.classList.contains('word-edit-input')) return;
|
||||
|
||||
const list = this.lists[this.currentListIndex];
|
||||
if (!list) return;
|
||||
|
||||
@@ -425,8 +500,26 @@ export class ListManagerController {
|
||||
const word = list.words[index];
|
||||
if (!word) return;
|
||||
|
||||
word.wordStr = target.value;
|
||||
const newValue = target.value.trim();
|
||||
if (newValue && newValue !== word.wordStr) {
|
||||
word.wordStr = newValue;
|
||||
this.save();
|
||||
} else {
|
||||
this.renderWords();
|
||||
}
|
||||
}
|
||||
|
||||
private handleWordListKeydown(event: KeyboardEvent): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.classList.contains('word-edit-input')) return;
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
target.blur();
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
this.renderWords();
|
||||
}
|
||||
}
|
||||
|
||||
private getSelectedListIndices(): number[] {
|
||||
@@ -459,10 +552,10 @@ export class ListManagerController {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-item';
|
||||
if (index === this.currentListIndex) item.classList.add('active');
|
||||
if (this.selectedLists.has(index)) item.classList.add('selected');
|
||||
item.draggable = true;
|
||||
item.dataset.index = index.toString();
|
||||
item.innerHTML = `
|
||||
<input type="checkbox" class="list-checkbox" data-index="${index}" ${this.selectedLists.has(index) ? 'checked' : ''}>
|
||||
<div class="list-meta">
|
||||
<div class="list-name">${DOMUtils.escapeHtml(list.name)}</div>
|
||||
<div class="list-stats">${total} words • ${activeCount} active</div>
|
||||
@@ -519,13 +612,19 @@ export class ListManagerController {
|
||||
wordList.innerHTML = entries.map(entry => {
|
||||
const word = entry.word;
|
||||
const index = entry.index;
|
||||
const isSelected = this.selectedWords.has(index);
|
||||
return `
|
||||
<div class="word-item ${word.active ? '' : 'disabled'}">
|
||||
<input type="checkbox" class="word-checkbox" data-index="${index}" ${this.selectedWords.has(index) ? 'checked' : ''}>
|
||||
<input type="text" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${index}">
|
||||
<input type="color" value="${word.background || list.background}" data-bg-edit="${index}">
|
||||
<input type="color" value="${word.foreground || list.foreground}" data-fg-edit="${index}">
|
||||
<input type="checkbox" data-active-edit="${index}" ${word.active ? 'checked' : ''} title="Active">
|
||||
<div class="word-item ${word.active ? '' : 'disabled'} ${isSelected ? 'selected' : ''}" data-index="${index}">
|
||||
<span class="word-text">${DOMUtils.escapeHtml(word.wordStr)}</span>
|
||||
<input type="text" class="word-edit-input" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${index}">
|
||||
<div class="word-actions">
|
||||
<button class="icon-btn edit-word-btn" data-index="${index}" title="Edit word">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
<input type="color" value="${word.background || list.background}" data-bg-edit="${index}" title="Background color">
|
||||
<input type="color" value="${word.foreground || list.foreground}" data-fg-edit="${index}" title="Foreground color">
|
||||
<button class="toggle-btn ${word.active ? 'active' : ''}" data-index="${index}" title="Toggle active"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
@@ -201,17 +201,79 @@ export class PopupController {
|
||||
}
|
||||
|
||||
private setupWordListEvents(wordList: HTMLDivElement): void {
|
||||
wordList.addEventListener('change', (e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.type === 'checkbox' && target.dataset.index != null) {
|
||||
const index = +target.dataset.index;
|
||||
if (target.checked) {
|
||||
this.selectedCheckboxes.add(index);
|
||||
} else {
|
||||
wordList.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const list = this.lists[this.currentListIndex];
|
||||
if (!list) return;
|
||||
|
||||
// Handle edit button click
|
||||
const editBtn = target.closest('.icon-btn.edit-word-btn') as HTMLElement | null;
|
||||
if (editBtn) {
|
||||
e.stopPropagation();
|
||||
const index = Number(editBtn.dataset.index);
|
||||
if (!Number.isNaN(index)) {
|
||||
this.startEditingWord(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle toggle button click
|
||||
if (target.classList.contains('toggle-btn')) {
|
||||
e.stopPropagation();
|
||||
const index = Number(target.dataset.index);
|
||||
if (!Number.isNaN(index)) {
|
||||
const word = list.words[index];
|
||||
if (word) {
|
||||
word.active = !word.active;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't select if clicking on color inputs or edit input
|
||||
if (target.tagName === 'INPUT') {
|
||||
if ((target as HTMLInputElement).type === 'color') {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (target.classList.contains('word-edit-input')) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't select if clicking inside word-actions area
|
||||
if (target.closest('.word-actions') && !target.classList.contains('word-item')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle word item selection
|
||||
const wordItem = target.closest('.word-item') as HTMLElement | null;
|
||||
if (!wordItem) return;
|
||||
|
||||
const index = Number(wordItem.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
|
||||
const mouseEvent = e as MouseEvent;
|
||||
// Ctrl/Cmd + click for multi-select
|
||||
if (mouseEvent.ctrlKey || mouseEvent.metaKey) {
|
||||
if (this.selectedCheckboxes.has(index)) {
|
||||
this.selectedCheckboxes.delete(index);
|
||||
} else {
|
||||
this.selectedCheckboxes.add(index);
|
||||
}
|
||||
this.renderWords();
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular click - toggle selection
|
||||
if (this.selectedCheckboxes.has(index)) {
|
||||
this.selectedCheckboxes.delete(index);
|
||||
} else {
|
||||
this.selectedCheckboxes.add(index);
|
||||
}
|
||||
this.renderWords();
|
||||
});
|
||||
|
||||
wordList.addEventListener('change', (e) => {
|
||||
@@ -227,19 +289,40 @@ export class PopupController {
|
||||
});
|
||||
|
||||
wordList.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const index = +(target.dataset.wordEdit ?? -1);
|
||||
if (index === -1) return;
|
||||
if (!target.classList.contains('word-edit-input')) return;
|
||||
|
||||
const word = this.lists[this.currentListIndex].words[index];
|
||||
if (target.dataset.wordEdit != null) {
|
||||
word.wordStr = target.value;
|
||||
this.save();
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
target.blur();
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.renderWords();
|
||||
}
|
||||
});
|
||||
|
||||
wordList.addEventListener('blur', (e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (!target.classList.contains('word-edit-input')) return;
|
||||
|
||||
const list = this.lists[this.currentListIndex];
|
||||
if (!list) return;
|
||||
|
||||
const index = Number(target.dataset.wordEdit ?? -1);
|
||||
if (Number.isNaN(index) || index < 0) return;
|
||||
|
||||
const word = list.words[index];
|
||||
if (!word) return;
|
||||
|
||||
const newValue = target.value.trim();
|
||||
if (newValue && newValue !== word.wordStr) {
|
||||
word.wordStr = newValue;
|
||||
this.save();
|
||||
} else {
|
||||
this.renderWords();
|
||||
}
|
||||
}, true);
|
||||
|
||||
let scrolling = false;
|
||||
wordList.addEventListener('scroll', () => {
|
||||
if (scrolling) return;
|
||||
@@ -251,6 +334,20 @@ export class PopupController {
|
||||
});
|
||||
}
|
||||
|
||||
private startEditingWord(index: number): void {
|
||||
const wordItem = document.querySelector(`.word-item[data-index="${index}"]`);
|
||||
if (!wordItem) return;
|
||||
|
||||
const textSpan = wordItem.querySelector('.word-text') as HTMLElement;
|
||||
const input = wordItem.querySelector('.word-edit-input') as HTMLInputElement;
|
||||
if (!textSpan || !input) return;
|
||||
|
||||
textSpan.classList.add('editing');
|
||||
input.classList.add('active');
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
private setupWordSelection(): void {
|
||||
document.getElementById('selectAllBtn')?.addEventListener('click', () => {
|
||||
const list = this.lists[this.currentListIndex];
|
||||
@@ -670,7 +767,7 @@ export class PopupController {
|
||||
}
|
||||
|
||||
const itemHeight = 32;
|
||||
const itemSpacing = 2;
|
||||
const itemSpacing = 4;
|
||||
const totalItemHeight = itemHeight + itemSpacing;
|
||||
const containerHeight = wordList.clientHeight || 250;
|
||||
const scrollTop = wordList.scrollTop;
|
||||
@@ -682,7 +779,7 @@ export class PopupController {
|
||||
|
||||
const spacer = document.createElement('div');
|
||||
spacer.style.position = 'relative';
|
||||
spacer.style.height = `${filteredWords.length * totalItemHeight}px`;
|
||||
spacer.style.height = `${filteredWords.length * totalItemHeight - itemSpacing}px`;
|
||||
spacer.style.width = '100%';
|
||||
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
@@ -705,21 +802,31 @@ export class PopupController {
|
||||
private createWordItem(word: HighlightWord, realIndex: number, displayIndex: number, itemHeight: number): HTMLDivElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'word-item';
|
||||
if (this.selectedCheckboxes.has(realIndex)) {
|
||||
container.classList.add('selected');
|
||||
}
|
||||
if (word.active === false) {
|
||||
container.classList.add('disabled');
|
||||
}
|
||||
container.style.cssText = `
|
||||
position: absolute;
|
||||
top: ${displayIndex * (itemHeight + 2)}px;
|
||||
top: ${displayIndex * (itemHeight + 4)}px;
|
||||
`;
|
||||
container.dataset.index = realIndex.toString();
|
||||
|
||||
const list = this.lists[this.currentListIndex];
|
||||
|
||||
container.innerHTML = `
|
||||
<input type="checkbox" class="word-checkbox" data-index="${realIndex}" ${this.selectedCheckboxes.has(realIndex) ? 'checked' : ''} title="${chrome.i18n.getMessage('select_title') || 'Select'}">
|
||||
<input type="text" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${realIndex}" placeholder="${chrome.i18n.getMessage('word_placeholder') || 'Word or phrase'}">
|
||||
<span class="word-text">${DOMUtils.escapeHtml(word.wordStr)}</span>
|
||||
<input type="text" class="word-edit-input" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${realIndex}">
|
||||
<div class="word-actions">
|
||||
<button class="icon-btn edit-word-btn" data-index="${realIndex}" title="${chrome.i18n.getMessage('edit') || 'Edit'}">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
<input type="color" value="${word.background || list.background}" data-bg-edit="${realIndex}" title="${chrome.i18n.getMessage('background_color_title') || 'Background color'}">
|
||||
<input type="color" value="${word.foreground || list.foreground}" data-fg-edit="${realIndex}" title="${chrome.i18n.getMessage('text_color_title') || 'Text color'}">
|
||||
<button class="toggle-btn ${word.active !== false ? 'active' : ''}" data-index="${realIndex}" title="${chrome.i18n.getMessage('toggle_active') || 'Toggle active'}"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return container;
|
||||
|
||||
Reference in New Issue
Block a user