diff --git a/src/js/app.js b/src/js/app.js index ca578e2..c9a7f8f 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -241,12 +241,12 @@ function bindStorageSync() { if (area !== 'local' || !changes.boards) return; const next = changes.boards.newValue; if (!Array.isArray(next)) return; - // Guard (W-c): nicht mitten in einer offenen Interaktion das boards-Array ersetzen und - // neu rendern. Ein offener Dialog (Delete/Edit) haelt evtl. eine board-Referenz per Closure; - // ein laufender Board-Drag (Phase 5) wuerde durch renderBoards() (replaceChildren) abgerissen. - // In dem Fall den Sync verwerfen — der naechste eigene Save/Render zieht den Stand nach. - if (document.querySelector('.dialog-overlay.active') || - document.body.classList.contains('board-dragging')) return; + // Guard (W-c, nach Phase-4-Review auf REALE Klassen korrigiert): nicht mitten in einer offenen + // Interaktion das boards-Array ersetzen und neu rendern, sonst verwaist eine per-Closure gehaltene + // board-Referenz oder ein laufender Drag/Render wird abgerissen. Abgedeckt: Settings (.panel-overlay), + // Modals Add-Board/Add-Bookmark/Rename (.modal-overlay), HellionDialog/Onboarding (.dialog-overlay), + // Board-Drag (.board.dragging), Bookmark-Drag (.bm-item.dragging-source). + if (document.querySelector('.panel-overlay.active, .modal-overlay.active, .dialog-overlay.active, .board.dragging, .bm-item.dragging-source')) return; boards = next; renderBoards(); }); diff --git a/src/js/background.js b/src/js/background.js index aab1540..8ac2673 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -5,10 +5,13 @@ synchron auf Top-Level. Geteilte Logik via importScripts. ============================================= */ -// Geteiltes DOM-freies Helfer-Modul aus Phase 1: ensureInbox(boards), uid(), -// normalizeBookmark(...). importScripts ist im Service-Worker UND in der -// Firefox-Event-Page verfuegbar. -importScripts('quicksave-core.js'); +// Geteiltes DOM-freies Helfer-Modul aus Phase 1: ensureInbox(boards), uid(), normalizeBookmark(...). +// Chrome-Service-Worker laedt es via importScripts. Firefox-Event-Page hat KEIN importScripts — +// dort kommt das Modul ueber background.scripts (manifest.firefox.json) in den Scope, ensureInbox +// ist dann schon definiert. Der Guard verhindert den ReferenceError in der Event-Page. +if (typeof importScripts === 'function' && typeof ensureInbox === 'undefined') { + importScripts('quicksave-core.js'); +} // chrome.storage.local-Lese-/Schreib-Helfer als Promises (kein Store-Modul im Worker, // das ist DOM/Seiten-gebunden). Identisches Verhalten: get -> Wert oder null. @@ -30,22 +33,26 @@ function bgSet(key, value) { }); } -// Kurze Badge-Bestaetigung, dann automatisch wieder leeren. -function flashBadge(text) { +// Kurze Badge-Bestaetigung, dann automatisch wieder leeren. color optional (Default gruen). +function flashBadge(text, color) { chrome.action.setBadgeText({ text }); // Hintergrundfarbe optional, ohne extra Permission moeglich. if (chrome.action.setBadgeBackgroundColor) { - chrome.action.setBadgeBackgroundColor({ color: '#1f9d55' }); + chrome.action.setBadgeBackgroundColor({ color: color || '#1f9d55' }); } setTimeout(() => chrome.action.setBadgeText({ text: '' }), 2000); } +// Interne/nicht speicherbare Seiten (Browser-UI, Extension-Seiten) — kein sinnvolles Bookmark. +const UNSAVEABLE_URL = /^(chrome|chrome-extension|about|edge|opera|moz-extension|brave|vivaldi|view-source|devtools):/i; + // Quick-Save: aktiven Tab lesen, in die Inbox haengen (read-modify-write). async function quickSaveActiveTab() { const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); const tab = tabs && tabs[0]; - if (!tab || !tab.url) { - flashBadge(chrome.i18n.getMessage('quickSaveNoTab')); + if (!tab || !tab.url || UNSAVEABLE_URL.test(tab.url)) { + // Kein speicherbarer Tab: kurzer roter Marker (langer Text wird im Badge abgeschnitten). + flashBadge('×', '#c0392b'); return; } @@ -63,9 +70,12 @@ async function quickSaveActiveTab() { } } -// Listener SYNCHRON auf Top-Level registrieren (Event-Page/Worker-Anforderung). +// Quick-Saves serialisieren: zwei schnelle Tastendruecke (Key-Repeat) wuerden sonst parallel +// read-modify-write machen und sich gegenseitig ueberschreiben (lost update). Promise-Kette +// sorgt fuer sequentielle Ausfuehrung. Listener bleibt SYNCHRON auf Top-Level registriert. +let quickSaveChain = Promise.resolve(); chrome.commands.onCommand.addListener(command => { if (command === 'quick-save') { - quickSaveActiveTab(); + quickSaveChain = quickSaveChain.then(() => quickSaveActiveTab()).catch(e => console.error('Quick-Save:', e && e.message)); } });