fixed sizing

This commit is contained in:
2026-02-05 16:48:53 +03:00
parent cdb2937e63
commit caaeca6a88
5 changed files with 257 additions and 107 deletions

View File

@@ -5,14 +5,18 @@
box-sizing: border-box; box-sizing: border-box;
} }
html {
height: 100%;
}
body { body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
margin: 0; margin: 0;
background: radial-gradient(120% 120% at 10% 0%, rgba(204, 106, 42, 0.08) 0%, transparent 45%), background: radial-gradient(120% 120% at 10% 0%, rgba(204, 106, 42, 0.08) 0%, transparent 45%),
linear-gradient(180deg, var(--bg-color) 0%, #f5efe9 100%); linear-gradient(180deg, var(--bg-color) 0%, #f5efe9 100%);
color: var(--text-color); color: var(--text-color);
min-width: 800px; height: 100%;
min-height: 600px; overflow: hidden;
} }
html.dark body, html.dark body,
@@ -25,6 +29,7 @@ body.dark {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
overflow: hidden;
} }
.topbar { .topbar {
@@ -70,10 +75,12 @@ body.dark .topbar {
.layout { .layout {
display: grid; display: grid;
grid-template-columns: 320px 1fr; grid-template-columns: minmax(280px, 25%) 1fr;
gap: 16px; gap: 16px;
padding: 16px; padding: 16px;
height: calc(100vh - 72px); flex: 1;
overflow: hidden;
min-height: 0;
} }
.panel { .panel {
@@ -85,6 +92,7 @@ body.dark .topbar {
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
overflow: hidden; overflow: hidden;
min-height: 0;
} }
.panel-header { .panel-header {
@@ -149,6 +157,8 @@ body.dark .topbar {
gap: 10px; gap: 10px;
overflow-y: auto; overflow-y: auto;
padding-right: 4px; padding-right: 4px;
flex: 1;
min-height: 0;
} }
/* Custom scrollbar styling - List Manager Specific */ /* Custom scrollbar styling - List Manager Specific */
@@ -291,37 +301,87 @@ body.dark .list-item.selected.active {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
min-height: 0;
overflow: hidden;
} }
.list-settings { .list-settings {
display: grid; display: none;
gap: 12px; gap: 8px;
grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 8px;
align-items: end; background: rgba(204, 106, 42, 0.05);
border-radius: 8px;
border: 1px solid var(--input-border);
} }
.list-settings label { html.dark .list-settings,
body.dark .list-settings {
background: rgba(242, 168, 101, 0.08);
}
.list-settings.expanded {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px;
font-size: 0.85rem;
} }
.list-settings input[type="text"] { .list-settings input[type="text"] {
width: 100%; width: 100%;
padding: 6px 10px;
font-size: 0.9rem;
} }
.colors { .color-row {
display: flex; display: flex;
gap: 12px; gap: 8px;
grid-column: span 2; align-items: center;
align-items: end;
} }
.colors label { .compact-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.85rem;
flex: 1; flex: 1;
} }
.compact-label span {
min-width: 24px;
font-weight: 500;
opacity: 0.8;
}
.compact-label input[type="color"] {
flex: 1;
height: 32px;
}
.compact-btn {
padding: 6px 12px;
min-width: auto;
height: 32px;
}
.compact-header {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.list-title-section {
display: flex;
align-items: center;
gap: 8px;
}
.list-title-section h2 {
margin: 0;
font-size: 1rem;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.word-controls { .word-controls {
display: flex; display: flex;
@@ -371,6 +431,8 @@ body.dark .list-item.selected.active {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
flex: 1 1 0;
min-height: 0;
} }
html.dark .word-list, html.dark .word-list,
@@ -502,13 +564,16 @@ input[type="color"] {
.pagination-container { .pagination-container {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 12px; gap: 12px;
padding: 12px; padding: 8px 12px;
background: #f9f4f0; background: #f9f4f0;
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
border-radius: 12px; border-radius: 12px;
margin-top: 8px; flex-shrink: 0;
flex-wrap: wrap;
} }
html.dark .pagination-container, html.dark .pagination-container,
@@ -517,32 +582,31 @@ body.dark .pagination-container {
} }
.pagination-info { .pagination-info {
font-size: 0.85rem; font-size: 0.8rem;
opacity: 0.8; opacity: 0.8;
text-align: center; white-space: nowrap;
} }
.pagination-controls { .pagination-controls {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; gap: 6px;
gap: 8px;
} }
.pagination-btn { .pagination-btn {
background: var(--button-bg); background: var(--button-bg);
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
color: var(--button-text); color: var(--button-text);
border-radius: 8px; border-radius: 6px;
padding: 6px 8px; padding: 4px 6px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 32px; min-width: 28px;
min-height: 32px; min-height: 28px;
font-size: 0.8rem; font-size: 0.75rem;
} }
.pagination-btn:hover:not(:disabled) { .pagination-btn:hover:not(:disabled) {
@@ -558,11 +622,11 @@ body.dark .pagination-container {
.pagination-pages { .pagination-pages {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 12px; padding: 0 8px;
} }
.page-info { .page-info {
font-size: 0.85rem; font-size: 0.8rem;
opacity: 0.9; opacity: 0.9;
font-weight: 500; font-weight: 500;
} }
@@ -570,23 +634,23 @@ body.dark .pagination-container {
.page-size-controls { .page-size-controls {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; gap: 6px;
gap: 8px; font-size: 0.8rem;
font-size: 0.85rem;
} }
.page-size-controls label { .page-size-controls label {
opacity: 0.8; opacity: 0.8;
white-space: nowrap;
} }
.page-size-select { .page-size-select {
background: var(--input-bg); background: var(--input-bg);
color: var(--text-color); color: var(--text-color);
border: 1px solid var(--input-border); border: 1px solid var(--input-border);
border-radius: 8px; border-radius: 6px;
padding: 4px 8px; padding: 3px 6px;
font-size: 0.85rem; font-size: 0.8rem;
min-width: 60px; min-width: 55px;
} }
.page-size-select:focus { .page-size-select:focus {
@@ -596,31 +660,4 @@ body.dark .pagination-container {
@media (max-width: 920px) { /* Removed single column layout - minimum width enforced for two-column layout */
body {
min-width: 100%;
}
.layout {
grid-template-columns: 1fr;
height: auto;
}
.pagination-container {
flex-wrap: wrap;
gap: 8px;
}
.pagination-controls {
order: 2;
}
.pagination-info {
order: 1;
width: 100%;
}
.page-size-controls {
order: 3;
}
}

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=1280" />
<title>Goose Highlighter - List Manager</title> <title>Goose Highlighter - List Manager</title>
<link rel="stylesheet" href="list-manager.css" /> <link rel="stylesheet" href="list-manager.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
@@ -46,31 +46,33 @@
<button id="activateListsBtn"><i class="fa-solid fa-circle-check"></i> Activate</button> <button id="activateListsBtn"><i class="fa-solid fa-circle-check"></i> Activate</button>
<button id="deactivateListsBtn"><i class="fa-solid fa-circle-xmark"></i> Deactivate</button> <button id="deactivateListsBtn"><i class="fa-solid fa-circle-xmark"></i> Deactivate</button>
</div> </div>
<div class="list-hint">Drag lists to reorder</div> <div class="list-hint">Drag lists to reorder • Ctrl+Click for multi-select</div>
<div id="listsContainer" class="lists"></div> <div id="listsContainer" class="lists"></div>
</section> </section>
<section class="panel details-panel"> <section class="panel details-panel">
<div class="panel-header"> <div class="panel-header compact-header">
<h2>Selected List</h2> <div class="list-title-section">
<h2 id="selectedListName">Selected List</h2>
<button id="editListNameBtn" class="icon-btn" title="Edit list name and colors">
<i class="fa-solid fa-pen"></i>
</button>
</div>
<div class="stats" id="listStats">0 words</div> <div class="stats" id="listStats">0 words</div>
</div> </div>
<div class="list-settings"> <div class="list-settings collapsed" id="listSettingsPanel">
<label> <input type="text" id="listName" placeholder="List name" />
Name <div class="color-row">
<input type="text" id="listName" /> <label class="compact-label">
</label> <span>BG</span>
<div class="colors">
<label>
Background
<input type="color" id="listBg" /> <input type="color" id="listBg" />
</label> </label>
<label> <label class="compact-label">
Foreground <span>FG</span>
<input type="color" id="listFg" /> <input type="color" id="listFg" />
</label> </label>
<button id="applyListSettingsBtn" class="compact-btn primary"><i class="fa-solid fa-check"></i></button>
</div> </div>
<button id="applyListSettingsBtn" class="primary"><i class="fa-solid fa-check"></i> Apply</button>
</div> </div>
<div class="word-controls"> <div class="word-controls">
@@ -91,7 +93,7 @@
<button id="moveWordsBtn"><i class="fa-solid fa-arrow-right"></i> Move</button> <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> <button id="copyWordsBtn"><i class="fa-solid fa-copy"></i> Copy</button>
</div> </div>
<div class="selection-hint">Click to select • Ctrl/Cmd+Click for multi-select • Click edit icon to rename</div> <div class="selection-hint">Click to select • Ctrl/Cmd+Click for multi-select • Drag words to lists to copy</div>
</div> </div>
<div id="wordList" class="word-list"></div> <div id="wordList" class="word-list"></div>

View File

@@ -52,8 +52,8 @@ class BackgroundService {
chrome.windows.create({ chrome.windows.create({
url: chrome.runtime.getURL('list-manager/list-manager.html'), url: chrome.runtime.getURL('list-manager/list-manager.html'),
type: 'popup', type: 'popup',
width: 800, width: 1280,
height: 600, height: 700,
focused: true focused: true
}); });
} }

View File

@@ -47,6 +47,7 @@ async initialize(): Promise<void> {
document.getElementById('deleteListsBtn')?.addEventListener('click', () => this.deleteSelectedLists()); document.getElementById('deleteListsBtn')?.addEventListener('click', () => this.deleteSelectedLists());
document.getElementById('activateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(true)); document.getElementById('activateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(true));
document.getElementById('deactivateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(false)); document.getElementById('deactivateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(false));
document.getElementById('editListNameBtn')?.addEventListener('click', () => this.toggleListSettings());
document.getElementById('applyListSettingsBtn')?.addEventListener('click', () => this.applyListSettings()); document.getElementById('applyListSettingsBtn')?.addEventListener('click', () => this.applyListSettings());
document.getElementById('importListBtn')?.addEventListener('click', () => this.triggerImport()); document.getElementById('importListBtn')?.addEventListener('click', () => this.triggerImport());
document.getElementById('exportListBtn')?.addEventListener('click', () => this.exportCurrentList()); document.getElementById('exportListBtn')?.addEventListener('click', () => this.exportCurrentList());
@@ -83,6 +84,8 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
wordList?.addEventListener('change', (e) => this.handleWordListChange(e)); wordList?.addEventListener('change', (e) => this.handleWordListChange(e));
wordList?.addEventListener('keydown', (e) => this.handleWordListKeydown(e)); wordList?.addEventListener('keydown', (e) => this.handleWordListKeydown(e));
wordList?.addEventListener('blur', (e) => this.handleWordListBlur(e), true); wordList?.addEventListener('blur', (e) => this.handleWordListBlur(e), true);
wordList?.addEventListener('dragstart', (e) => this.handleWordDragStart(e));
wordList?.addEventListener('dragend', () => this.clearDragState());
} }
private setupStorageSync(): void { private setupStorageSync(): void {
@@ -208,9 +211,24 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
list.background = listBg.value; list.background = listBg.value;
list.foreground = listFg.value; list.foreground = listFg.value;
this.toggleListSettings();
this.save(); this.save();
} }
private toggleListSettings(): void {
const panel = document.getElementById('listSettingsPanel');
if (!panel) return;
if (panel.classList.contains('expanded')) {
panel.classList.remove('expanded');
} else {
panel.classList.add('expanded');
const listName = document.getElementById('listName') as HTMLInputElement;
listName?.focus();
listName?.select();
}
}
private exportCurrentList(): void { private exportCurrentList(): void {
const list = this.lists[this.currentListIndex]; const list = this.lists[this.currentListIndex];
if (!list) return; if (!list) return;
@@ -427,6 +445,7 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
this.currentListIndex = index; this.currentListIndex = index;
this.selectedLists.clear(); this.selectedLists.clear();
this.selectedWords.clear(); this.selectedWords.clear();
this.currentPage = 1; // Reset to first page when selecting a list
this.render(); this.render();
} }
@@ -436,25 +455,59 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const index = Number(target.dataset.index); const index = Number(target.dataset.index);
if (Number.isNaN(index)) return; if (Number.isNaN(index)) return;
event.dataTransfer?.setData('text/plain', index.toString()); event.dataTransfer?.setData('text/plain', JSON.stringify({ type: 'list', index }));
event.dataTransfer?.setDragImage(target, 10, 10); event.dataTransfer?.setDragImage(target, 10, 10);
} }
private handleDragOver(event: DragEvent): void { private handleDragOver(event: DragEvent): void {
event.preventDefault(); event.preventDefault();
const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null; const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null;
if (!target) return; if (!target) {
this.clearDragState();
return;
}
// Clear drag state from all items first
this.clearDragState();
// Check if we're dragging words or lists
const data = event.dataTransfer?.types.includes('text/plain');
if (data) {
target.classList.add('drag-over'); target.classList.add('drag-over');
} }
}
private handleDrop(event: DragEvent): void { private handleDrop(event: DragEvent): void {
event.preventDefault(); event.preventDefault();
const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null; const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null;
if (!target) return; if (!target) return;
const sourceIndex = Number(event.dataTransfer?.getData('text/plain'));
const targetIndex = Number(target.dataset.index); const targetIndex = Number(target.dataset.index);
if (Number.isNaN(sourceIndex) || Number.isNaN(targetIndex) || sourceIndex === targetIndex) { if (Number.isNaN(targetIndex)) {
this.clearDragState();
return;
}
try {
const dataStr = event.dataTransfer?.getData('text/plain');
if (!dataStr) {
this.clearDragState();
return;
}
const data = JSON.parse(dataStr);
// Handle word drag
if (data.type === 'words') {
this.dropWordsOnList(data.wordIndices, targetIndex);
this.clearDragState();
return;
}
// Handle list drag (reordering)
if (data.type === 'list') {
const sourceIndex = data.index;
if (sourceIndex === targetIndex) {
this.clearDragState(); this.clearDragState();
return; return;
} }
@@ -473,11 +526,61 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
this.selectedLists.clear(); this.selectedLists.clear();
this.save(); this.save();
} }
} catch (error) {
console.error('Drop error:', error);
}
this.clearDragState();
}
private clearDragState(): void { private clearDragState(): void {
document.querySelectorAll('.list-item.drag-over').forEach(item => item.classList.remove('drag-over')); document.querySelectorAll('.list-item.drag-over').forEach(item => item.classList.remove('drag-over'));
} }
private handleWordDragStart(event: DragEvent): void {
const target = (event.target as HTMLElement).closest('.word-item') as HTMLElement | null;
if (!target) return;
const index = Number(target.dataset.index);
if (Number.isNaN(index)) return;
// If dragging a selected word, drag all selected words
let wordIndices: number[];
if (this.selectedWords.has(index)) {
wordIndices = Array.from(this.selectedWords);
} else {
wordIndices = [index];
}
event.dataTransfer?.setData('text/plain', JSON.stringify({ type: 'words', wordIndices }));
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'copy';
}
}
private dropWordsOnList(wordIndices: number[], targetListIndex: number): void {
const sourceList = this.lists[this.currentListIndex];
const targetList = this.lists[targetListIndex];
if (!sourceList || !targetList) return;
if (targetListIndex === this.currentListIndex) return; // Can't drop on same list
const wordsToCopy = wordIndices
.map(index => sourceList.words[index])
.filter(Boolean)
.map(word => ({ ...word })); // Create copies
if (wordsToCopy.length === 0) return;
targetList.words.push(...wordsToCopy);
this.save();
// Show feedback
const count = wordsToCopy.length;
const message = `Copied ${count} word${count > 1 ? 's' : ''} to "${targetList.name}"`;
console.log(message);
}
private handleWordListClick(event: Event): void { private handleWordListClick(event: Event): void {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
const list = this.lists[this.currentListIndex]; const list = this.lists[this.currentListIndex];
@@ -541,12 +644,9 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
return; return;
} }
// Regular click - toggle selection // Regular click - clear all and select only this one
if (this.selectedWords.has(index)) { this.selectedWords.clear();
this.selectedWords.delete(index);
} else {
this.selectedWords.add(index); this.selectedWords.add(index);
}
this.renderWords(); this.renderWords();
} }
@@ -664,6 +764,11 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const list = this.lists[this.currentListIndex]; const list = this.lists[this.currentListIndex];
if (!list) return; if (!list) return;
const selectedListName = document.getElementById('selectedListName');
if (selectedListName) {
selectedListName.textContent = list.name;
}
(document.getElementById('listName') as HTMLInputElement).value = list.name; (document.getElementById('listName') as HTMLInputElement).value = list.name;
(document.getElementById('listBg') as HTMLInputElement).value = list.background; (document.getElementById('listBg') as HTMLInputElement).value = list.background;
(document.getElementById('listFg') as HTMLInputElement).value = list.foreground; (document.getElementById('listFg') as HTMLInputElement).value = list.foreground;
@@ -675,6 +780,12 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
stats.textContent = `${list.words.length} words • ${activeCount} active • ${inactiveCount} inactive`; stats.textContent = `${list.words.length} words • ${activeCount} active • ${inactiveCount} inactive`;
} }
// Collapse settings panel when switching lists
const panel = document.getElementById('listSettingsPanel');
if (panel) {
panel.classList.remove('expanded');
}
this.renderTargetListOptions(); this.renderTargetListOptions();
} }
@@ -714,7 +825,7 @@ private renderWords(): void {
const index = entry.index; const index = entry.index;
const isSelected = this.selectedWords.has(index); const isSelected = this.selectedWords.has(index);
return ` return `
<div class="word-item ${word.active ? '' : 'disabled'} ${isSelected ? 'selected' : ''}" data-index="${index}"> <div class="word-item ${word.active ? '' : 'disabled'} ${isSelected ? 'selected' : ''}" data-index="${index}" draggable="true">
<span class="word-text">${DOMUtils.escapeHtml(word.wordStr)}</span> <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}"> <input type="text" class="word-edit-input" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${index}">
<div class="word-actions"> <div class="word-actions">

View File

@@ -693,8 +693,8 @@ export class PopupController {
chrome.windows.create({ chrome.windows.create({
url: chrome.runtime.getURL('list-manager/list-manager.html'), url: chrome.runtime.getURL('list-manager/list-manager.html'),
type: 'popup', type: 'popup',
width: 800, width: 1280,
height: 600, height: 700,
focused: true focused: true
}); });
} }