From 18a04b884cacab33f653450b99ff2118c785b3c1 Mon Sep 17 00:00:00 2001 From: Florian Wathling Date: Sat, 21 Mar 2026 19:40:43 +0100 Subject: [PATCH] refactor(app): Sticky-Note durch Widget-System ersetzen Migration alter Sticky-Daten in das neue Widget-System, Notes.init() statt initStickyNote(), Toolbar-Position in Settings, JSON-Export/Import um Notes erweitert, Onboarding-Text aktualisiert. --- src/js/app.js | 47 ++++++++++++++++++++++++++++++++++++++++++-- src/js/data.js | 38 +++++++++++++++++++++++++++++++---- src/js/onboarding.js | 3 ++- src/js/settings.js | 20 +++++++++++++++++-- src/js/state.js | 3 ++- 5 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/js/app.js b/src/js/app.js index 5c16187..e2ac866 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -16,7 +16,8 @@ async function init() { bindGlobalEvents(); bindSettingsEvents(); initSearch(); - initStickyNote(); + await migrateSticky(); + await Notes.init(); initDataButtons(); Store.checkQuota(); @@ -30,6 +31,46 @@ async function init() { } } +// ---- STICKY NOTE MIGRATION ---- +async function migrateSticky() { + const stickyText = await Store.get('stickyNote'); + const stickyPos = await Store.get('stickyPos'); + const existingWidgets = await Store.get('widgetStates'); + + // Nur migrieren wenn alte Daten vorhanden UND noch keine Widgets existieren + if (!stickyText && !stickyPos) return; + if (existingWidgets && Array.isArray(existingWidgets.notes) && existingWidgets.notes.length > 0) return; + + const noteData = { + id: 'note_' + uid(), + title: (stickyText || '').split('\n')[0].trim().slice(0, 20) || 'Note', + content: stickyText || '', + template: 'text', + x: stickyPos ? stickyPos.x : 120, + y: stickyPos ? stickyPos.y : 80, + width: 280, + height: 220, + open: true, + checkedItems: [], + checklistItems: [] + }; + + await Store.set('widgetStates', { notes: [noteData] }); + + // Alte Keys aufraeumen + try { + if (typeof chrome !== 'undefined' && chrome.storage) { + chrome.storage.local.remove(['stickyNote', 'stickyPos', 'stickyVisible']); + } else { + localStorage.removeItem('stickyNote'); + localStorage.removeItem('stickyPos'); + localStorage.removeItem('stickyVisible'); + } + } catch (e) { + console.warn('Sticky-Migration: Alte Keys konnten nicht entfernt werden', e); + } +} + // ---- BACKUP REMINDER ---- const BACKUP_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000; // 7 Tage @@ -55,7 +96,9 @@ async function checkBackupReminder() { if (doBackup) { // JSON-Export auslösen (gleiche Logik wie btnExportJSON) - const data = { version: '1.5.2', exported: new Date().toISOString(), boards, settings }; + const widgetData = await Store.get('widgetStates'); + const notesData = (widgetData && Array.isArray(widgetData.notes)) ? widgetData.notes : []; + const data = { version: '1.6.0', exported: new Date().toISOString(), boards, settings, notes: notesData }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); diff --git a/src/js/data.js b/src/js/data.js index 6f1a2c6..c51b42a 100644 --- a/src/js/data.js +++ b/src/js/data.js @@ -9,9 +9,16 @@ function initDataButtons() { const jsonInput = document.getElementById('jsonImportInput'); if (!btnExport || !btnImport) return; - // Export - btnExport.addEventListener('click', () => { - const data = { version: '1.5.2', exported: new Date().toISOString(), boards, settings }; + // Export (inkl. Notes) + btnExport.addEventListener('click', async () => { + const widgetData = await Store.get('widgetStates'); + const data = { + version: '1.6.0', + exported: new Date().toISOString(), + boards, + settings, + notes: widgetData && Array.isArray(widgetData.notes) ? widgetData.notes : [] + }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -50,8 +57,31 @@ function initDataButtons() { boards = [...boards, ...validBoards]; await saveBoards(); renderBoards(); + + // Notes importieren (falls vorhanden) + let notesImported = 0; + if (Array.isArray(data.notes) && data.notes.length > 0) { + const existingWidgets = await Store.get('widgetStates'); + const existingNotes = (existingWidgets && Array.isArray(existingWidgets.notes)) ? existingWidgets.notes : []; + const importNotes = data.notes.filter(n => { + if (!n || !n.id || !n.template) return false; + n.checklistItems = Array.isArray(n.checklistItems) ? n.checklistItems : []; + return true; + }); + // Limit beachten + const spaceLeft = Notes.MAX_NOTES - existingNotes.length; + const toImport = importNotes.slice(0, spaceLeft); + if (toImport.length > 0) { + const merged = [...existingNotes, ...toImport]; + await Store.set('widgetStates', { notes: merged }); + Notes._notes = merged; + notesImported = toImport.length; + } + } + + const noteMsg = notesImported > 0 ? ` + ${notesImported} Note(s)` : ''; await HellionDialog.alert( - `${validBoards.length} Board(s) erfolgreich importiert.`, + `${validBoards.length} Board(s)${noteMsg} erfolgreich importiert.`, { type: 'success', title: 'Import erfolgreich' } ); } catch (err) { diff --git a/src/js/onboarding.js b/src/js/onboarding.js index dfb35d1..9d5c08a 100644 --- a/src/js/onboarding.js +++ b/src/js/onboarding.js @@ -33,7 +33,8 @@ const Onboarding = { title: 'Weitere Features', features: [ 'Suchleiste mit Google, DuckDuckGo oder Bing', - 'Sticky Notes f\u00FCr schnelle Notizen', + 'Widget-Toolbar rechts \u2014 Notes und Checklisten erstellen', + 'Notebook-Sidebar \u00FCber den \u201ENote\u201C Button oder die Toolbar', 'Funktioniert komplett offline \u2014 alles lokal gespeichert' ] }, diff --git a/src/js/settings.js b/src/js/settings.js index 543a8c5..7f9bc95 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -25,7 +25,7 @@ function closeThemeModal() { // ---- ACCORDION ---- function initAccordion() { - const defaultOpen = new Set(['appearance', 'behavior', 'data', 'help']); + const defaultOpen = new Set(['appearance', 'behavior', 'widgets', 'data', 'help']); const sections = document.querySelectorAll('.settings-section[data-section]'); sections.forEach(section => { @@ -71,6 +71,11 @@ function applySettings() { const showSearchEl = document.getElementById('settingShowSearch'); if (showSearchEl) showSearchEl.checked = settings.showSearch; + // Toolbar-Position + document.body.classList.toggle('toolbar-left', settings.toolbarPos === 'left'); + const toolbarPosEl = document.getElementById('settingToolbarPos'); + if (toolbarPosEl) toolbarPosEl.value = settings.toolbarPos || 'right'; + applyTheme(settings.theme || 'nebula', !!settings.bgUrl); if (settings.bgUrl) { @@ -172,6 +177,17 @@ function bindSettingsEvents() { reader.readAsDataURL(file); }); + // Toolbar-Position Setting + const toolbarPosEl = document.getElementById('settingToolbarPos'); + if (toolbarPosEl) { + toolbarPosEl.value = settings.toolbarPos || 'right'; + toolbarPosEl.addEventListener('change', async (e) => { + settings.toolbarPos = e.target.value; + document.body.classList.toggle('toolbar-left', e.target.value === 'left'); + await saveSettings(); + }); + } + // Onboarding wiederholen document.getElementById('btnRestartOnboarding').addEventListener('click', () => { closeSettings(); @@ -188,7 +204,7 @@ function bindSettingsEvents() { boards = []; settings = { compact: false, shortenTitles: false, newTab: true, showDesc: false, hideExtra: false, visibleCount: 10, bgUrl: '', theme: 'nebula', - showSearch: true, searchEngine: 'google' }; + showSearch: true, searchEngine: 'google', toolbarPos: 'right' }; await saveBoards(); await saveSettings(); applySettings(); diff --git a/src/js/state.js b/src/js/state.js index 5ba982f..8ceff72 100644 --- a/src/js/state.js +++ b/src/js/state.js @@ -15,7 +15,8 @@ let settings = { bgUrl: '', theme: 'nebula', showSearch: true, - searchEngine: 'google' + searchEngine: 'google', + toolbarPos: 'right' }; function uid() {