From fb805b30a1471baf081997f5efcba27ba9fc9c54 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 01:08:17 +0200 Subject: [PATCH 01/37] feat(workspace): add help page, folder drag and drop, pinned notes section, and clean up editor UX --- BRAIN.md | 11 +- frontend/src/App.tsx | 24 +- frontend/src/features/help/HelpView.tsx | 374 ++++++++++++++++++ .../src/features/workspace/WorkspaceView.tsx | 266 ++++++++++--- internal/storage/sqlite.go | 2 +- 5 files changed, 610 insertions(+), 67 deletions(-) create mode 100644 frontend/src/features/help/HelpView.tsx diff --git a/BRAIN.md b/BRAIN.md index 41ced99..facbfd1 100644 --- a/BRAIN.md +++ b/BRAIN.md @@ -58,10 +58,19 @@ - `Shift+Tab`: Block ausrücken. - `Shift+Enter`: Weicher Zeilenumbruch innerhalb desselben Blocks. - `Backspace`: Auf leerem Block rückt aus oder entfernt den Bullet-Point. - - **Zoom-in Hoisting:** Klick auf den Bullet-Dot zoomt tief in diesen Ast hinein (Fokusmodus) mit Breadcrumbs-Navigation und „Zoom aufheben"-Button. + - **Zoom-in Hoisting:** Klick auf den Bullet-Dot zoomt tief in diesen Ast hinein (Fokusmodus) mit Breadcrumbs-Navigation (zurückspringen per Klick auf die grauen Breadcrumbs). - **Drag & Drop:** Bullet-Dots dienen als Anfasser zum Verschieben und Umsortieren von Blöcken und ganzen Unterbäumen. - **Slash-Commands (`/`):** Schnelle Umwandlung in `/todo`, `/h1`, `/h2`, `/h3`, `/code`, `/table`, `/callout`, `/progress`, `/divider`. - Physische Speicherung auf Festplatte unter `/data/workspace//...` als Standard-`.md`-Dateien mit portabler 2-Leerzeichen-Einrückung (` - [ ] ` bzw. ` - `), vollständig kompatibel mit externen Outlinern und Markdown-Viewern. +- [x] **Zentrale Hilfeseite & Dokumentation (`HelpView.tsx`, `App.tsx`):** + - Vollständige, übersichtliche Hilfe- und Tastatur-Dokumentation für Outliner, Triage, Aufgaben, geteilte Listen und Tastatur-Shortcuts. + - Direkt erreichbar über das Benutzer-Profil-Menü („Hilfe & Anleitung") sowie den „Hilfe"-Button im Workspace-Header. +- [x] **Workspace UX-Refinements & Datei-Explorer Drag & Drop (`WorkspaceView.tsx`, `sqlite.go`):** + - **Dateien in Ordner ziehen:** Notizen können im Datei-Explorer per Drag & Drop direkt auf beliebige Ordner oder in das Hauptverzeichnis gezogen werden. Das alte Ordner-Dropdown im Menü wurde entfernt. + - **Angepinnte Notizen (Favoriten):** Dokumente mit `isPinned: true` werden direkt ganz oben im Datei-Explorer in einer eigenen Schnellzugriffs-Sektion dargestellt. + - **Bereinigter Editor:** Störende Tasten-Legende unter den Notizen entfernt; klobiger Rahmen um das Notiz-Icon entfernt; Standard-Icon ist der Zettel (`📄`). + - **Breadcrumbs & Zoom:** Überflüssiger „Zoom aufheben"-Button entfernt; alle Breadcrumbs-Elemente inklusive Dokumententitel erscheinen einheitlich dezent in Grau ohne störende Link-Unterstreichung. + - **Globale To-Dos:** Tab- und Bereichs-Titel auf prägnantes „Globale To-Dos" gekürzt, unnötiger Erklärungstext entfernt. - [x] **Globaler Workspace To-Do Rollup Dashboard (`WorkspaceView.tsx`):** - Aggregierte Übersicht aller To-Dos (`- [ ] ` / `- [x] `) aus sämtlichen Notiz-Dateien über alle Ordner hinweg. - Übersichtliche Gruppierung nach Ordner ➔ Dokument ➔ Aufgaben mit einklappbaren Akkordeons und Zähler für offene Aufgaben. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 493114c..e0f7913 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,7 @@ import { ChevronDown, RotateCcw, Palette, + HelpCircle, } from 'lucide-react'; import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types'; import { api, clearToken, getToken, triggerHaptic } from './lib/api'; @@ -28,6 +29,7 @@ import { SharedListsView } from './features/lists/SharedListsView'; import { SetupAndAuthView } from './features/auth/SetupAndAuthView'; import { UserSettingsView } from './features/settings/UserSettingsView'; import { AdminSettingsView } from './features/settings/AdminSettingsView'; +import { HelpView } from './features/help/HelpView'; type ActiveTab = | 'triage' @@ -35,7 +37,8 @@ type ActiveTab = | 'workspace' | 'lists' | 'user_settings' - | 'admin_settings'; + | 'admin_settings' + | 'help'; interface ErrorBoundaryState { hasError: boolean; @@ -375,6 +378,20 @@ export const App: React.FC = () => { Kontoeinstellungen + + {isStaff && ( + +
+

+ + Hilfe & Dokumentation +

+

+ Alles Wichtige zur Tastaturbedienung, Outliner-Notizen, Triage und Zusammenarbeit +

+
+ + + {/* Search */} +
+ + setSearchQuery(e.target.value)} + placeholder="In Hilfe suchen…" + className="w-full h-8 pl-8 pr-3 input-field text-xs bg-canvas" + /> +
+ + + {/* Category Filter Pills */} +
+ {[ + { id: 'all', label: 'Alle Themen', icon: Sparkles }, + { id: 'workspace', label: 'Workspace & Outliner', icon: FileText }, + { id: 'triage', label: 'Triage & Quick-Dump', icon: Layers }, + { id: 'tasks', label: 'Aufgaben & Listen', icon: CheckSquare }, + { id: 'shortcuts', label: 'Tastatur-Kürzel', icon: Keyboard }, + ].map((cat) => { + const Icon = cat.icon; + const isActive = activeCategory === cat.id; + return ( + + ); + })} +
+ + {/* Grid of Documentation Cards */} +
+ {/* SECTION: WORKSPACE & OUTLINER */} + {(activeCategory === 'all' || activeCategory === 'workspace') && ( +
+
+

+ + Workspace: Der hierarchische Logseq-Outliner +

+

+ Jede Notiz in MindSift ist ein flexibler hierarchischer Outliner und wird als Standard-Markdown auf der Festplatte gespeichert. +

+
+ +
+
+

+ + Flüssige Tastatursteuerung +

+
    +
  • + Enter: Erzeugt sofort einen neuen Bullet-Point auf derselben Ebene. +
  • +
  • + Tab: Rückt den aktuellen Bullet-Point ein und ordnet ihn dem vorherigen Punkt unter. +
  • +
  • + Shift + Tab: Rückt den Bullet-Point wieder nach links aus (verringert die Tiefe). +
  • +
  • + Shift + Enter: Erzeugt einen weichen Zeilenumbruch innerhalb desselben Textblocks. +
  • +
  • + Backspace: Auf einem leeren Block rückt er die Ebene aus bzw. löscht den leeren Bullet. +
  • +
+
+ +
+

+ + Slash-Befehle (/) +

+

+ Tippe einfach / in einen leeren Block, um den Typ flexibel zu wechseln: +

+
+ /todo (Checkbox-Aufgabe) + /h1 (Haupt-Überschrift) + /h2 (Abschnitts-Titel) + /h3 (Unter-Überschrift) + /code (Code-Block) + /table (Markdown-Tabelle) + /callout (Hinweis-Box) + /progress (Fortschritt 0-100%) +
+
+ +
+

+ + Ordnerstruktur & Drag & Drop +

+

+ Notizen können im linken Datei-Explorer per Drag & Drop direkt auf beliebige Ordner gezogen werden, um sie zu verschieben. + Ziehst du eine Notiz auf das Hauptverzeichnis, wird sie aus allen Unterordnern herausgenommen. +

+
+ +
+

+ + Zoom-Hoisting & Favoriten anpinnen +

+
    +
  • + Klick auf den Bullet-Punkt (•): Fokussiert tief in diesen Ast hinein (Zoom-in Hoisting). Du kannst über die Breadcrumbs ganz oben jederzeit zurückspringen. +
  • +
  • + Bullet-Punkt ziehen: Verschiebt den Block samt aller eingerückten Unterelemente an eine neue Position. +
  • +
  • + Anpinnen: Wichtige Notizen heften sich ganz oben im Datei-Explorer unter „Angepinnte Notizen" an. +
  • +
+
+
+
+ )} + + {/* SECTION: TRIAGE & QUICK DUMP */} + {(activeCategory === 'all' || activeCategory === 'triage') && ( +
+
+

+ + Triage & Quick Dump: Der Kopf frei in 3 Sekunden +

+

+ Sofortiges Erfassen ohne Reibung. Gedanken werden gesammelt und anschließend im Triage-Deck mit 1 Taste entschieden. +

+
+ +
+
+

1. Blitz-Erfassung

+

+ Drücke überall C oder /, tippe den Gedanken ein und drücke Enter. +

+
+ +
+

2. Triage Entscheidungs-Tasten

+
    +
  • H oder ← : Wegwerfen / Erledigt
  • +
  • L oder → : Als Aufgabe anlegen
  • +
  • T : Aufgabe mit Fälligkeitsdatum
  • +
  • D : In Workspace-Notiz umwandeln
  • +
  • Leertaste : Später (ans Ende schieben)
  • +
+
+ +
+

3. Direkt-Zuordnung

+

+ Über das Dropdown neben der Eingabeleiste kannst du einen Eintrag schon beim Schreiben direkt einer Aufgabenliste, Einkaufsliste oder Notiz zuweisen. +

+
+
+
+ )} + + {/* SECTION: TASKS & LISTS */} + {(activeCategory === 'all' || activeCategory === 'tasks') && ( +
+
+

+ + Aufgaben, Listen & Team-Zusammenarbeit +

+

+ Verwalte Aufgabenlisten, Einkaufslisten und teile sie mit Teams und Räumen in Echtzeit. +

+
+ +
+
+

+ + Aufgabenlisten teilen & Avatar-Stack +

+

+ Jede Aufgabenliste kann über den Button „Teilen" einem gemeinsamen Team-Space zugeordnet werden. + Im Header und auf den Tabs zeigt der Avatar-Stack sofort an, wer Eigentümer ist und welche Teammitglieder Zugriff haben. +

+
+ +
+

+ + Globale To-Dos Rollup +

+

+ Im Workspace-Tab „Globale To-Dos" werden automatisch alle offenen Checkboxen (- [ ]) aus sämtlichen Notizen und Ordnern aggregiert. + Ein Klick auf die Checkbox synchronisiert die Aufgabe in Echtzeit direkt in der physischen Markdown-Datei! +

+
+
+
+ )} + + {/* SECTION: SHORTCUTS CHEAT SHEET */} + {(activeCategory === 'all' || activeCategory === 'shortcuts') && ( +
+
+

+ + Tastatur-Kürzel Gesamtübersicht +

+

+ Schneller arbeiten ohne Maus: Alle Hotkeys auf einen Blick. +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TasteBereichFunktion
C oder /GlobalFokus sofort in die Schnellerfassungsleiste setzen
EnterSchnellerfassungGedanken sofort speichern
EscSchnellerfassungEingabe leeren & Menüs schließen
EnterWorkspace OutlinerNeuen Bullet-Point auf gleicher Ebene anlegen
TabWorkspace OutlinerBullet-Point einrücken (dem vorherigen unterordnen)
Shift + TabWorkspace OutlinerBullet-Point ausrücken (Ebene verringern)
Shift + EnterWorkspace OutlinerWeicher Zeilenumbruch innerhalb des aktuellen Blocks
BackspaceWorkspace OutlinerBei leerem Block: Ebene ausrücken bzw. Bullet löschen
/Workspace OutlinerSlash-Command-Menü öffnen (/todo, /h1, /code, ...)
H oder ←Triage-DeckGedanken verwerfen / erledigen
L oder →Triage-DeckGedanken sofort zur Aufgabe machen
TTriage-DeckAufgabe mit Fälligkeitsdatum anlegen
DTriage-DeckGedanken in Notiz/Dokument umwandeln
LeertasteTriage-DeckKarte überspringen („Später" – ans Ende schieben)
+
+
+ )} +
+ + ); +}; diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index a3633aa..8c7c2b6 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -38,6 +38,7 @@ import { Circle, FolderTree, Plus, + HelpCircle, } from 'lucide-react'; import { BlockType, Document, DocumentBlock } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; @@ -45,9 +46,10 @@ import { api, triggerHaptic } from '../../lib/api'; interface WorkspaceViewProps { initialDocId?: string | null; onStatsChange: () => void; + onOpenHelp?: () => void; } -const ICONS = ['🚀', '💡', '📄', '🏡', '⚡', '🎯', '🍕', '🛠️', '🧠', '🔥', '🏔️', '📦', '📑', '⚙️', '📊']; +const ICONS = ['📄', '📝', '💡', '🚀', '🏡', '⚡', '🎯', '🍕', '🛠️', '🧠', '🔥', '🏔️', '📦', '📑', '⚙️', '📊']; interface FolderTreeItem { path: string; @@ -80,6 +82,7 @@ const SLASH_COMMANDS: SlashCommandItem[] = [ export const WorkspaceView: React.FC = ({ initialDocId, onStatsChange, + onOpenHelp, }) => { // Top Workspace Tab Mode: 'editor' vs 'todos' (Global To-Do Rollup Dashboard) const [workspaceMode, setWorkspaceMode] = useState<'editor' | 'todos'>('editor'); @@ -105,6 +108,10 @@ export const WorkspaceView: React.FC = ({ const [newFolderParent, setNewFolderParent] = useState(null); const [newFolderName, setNewFolderName] = useState(''); + // Sidebar Document Drag and Drop state + const [draggingDocId, setDraggingDocId] = useState(null); + const [dragOverFolder, setDragOverFolder] = useState(null); + // Active focused block ref const [lastFocusedBlockId, setLastFocusedBlockId] = useState(null); @@ -116,7 +123,7 @@ export const WorkspaceView: React.FC = ({ const [slashSearch, setSlashSearch] = useState(''); const [slashSelectedIndex, setSlashSelectedIndex] = useState(0); - // Drag and Drop reordering state + // Drag and Drop reordering state for blocks const [draggingBlockId, setDraggingBlockId] = useState(null); // --- GLOBAL TO-DO ROLLUP STATE --- @@ -207,7 +214,7 @@ export const WorkspaceView: React.FC = ({ } const created = await api.createDocument({ title: 'Neuer Gedanke / Notiz', - icon: '💡', + icon: '📄', folderPath: path, blocks: [ { @@ -258,6 +265,30 @@ export const WorkspaceView: React.FC = ({ await persistDocument(updatedDoc); }; + const handleDropDocToFolder = async (folderPath: string, e: React.DragEvent) => { + const docId = e.dataTransfer.getData('application/mindsift-doc-id') || draggingDocId; + if (!docId) return; + const doc = docs.find((d) => d.id === docId); + if (!doc) return; + const cleanPath = folderPath.trim(); + if (getDocFolder(doc) === cleanPath) return; + + triggerHaptic(15); + const updatedTags = (doc.tags || []).filter((t) => !t.startsWith('folder:')); + if (cleanPath) { + updatedTags.push(`folder:${cleanPath}`); + } + const updatedDoc: Document = { + ...doc, + folderPath: cleanPath, + tags: updatedTags, + }; + await persistDocument(updatedDoc); + if (cleanPath) { + setExpandedFolders((prev) => new Set([...prev, cleanPath])); + } + }; + const handleStartCreateFolder = (parentPath?: string | null) => { setNewFolderParent(parentPath || null); setNewFolderName(''); @@ -801,25 +832,41 @@ export const WorkspaceView: React.FC = ({ return { rootTree: rootFolders, rootDocs: rootOnlyDocs }; }, [allFolderPaths, filteredDocs]); + const pinnedDocs = useMemo(() => { + return docs.filter((d) => d.isPinned); + }, [docs]); + const renderDocItem = (doc: Document, depth: number) => { const isSelected = selectedDoc?.id === doc.id && workspaceMode === 'editor'; + const isDragging = draggingDocId === doc.id; return (
{ + e.stopPropagation(); + e.dataTransfer.setData('application/mindsift-doc-id', doc.id); + e.dataTransfer.setData('text/plain', doc.id); + setDraggingDocId(doc.id); + }} + onDragEnd={() => setDraggingDocId(null)} onClick={() => { setSelectedDoc(doc); setZoomedBlockId(null); setWorkspaceMode('editor'); }} style={{ paddingLeft: `${depth * 14 + 16}px` }} - className={`group flex items-center justify-between py-1.5 pr-2 border-l-2 transition-colors cursor-pointer text-xs ${ + className={`group flex items-center justify-between py-1.5 pr-2 border-l-2 transition-all cursor-pointer text-xs ${ + isDragging ? 'opacity-40 scale-[0.98]' : '' + } ${ isSelected ? 'bg-panel-elevated border-accent text-main font-medium' : 'border-transparent text-muted hover:text-main hover:bg-canvas/50' }`} + title={`${doc.title || 'Notiz'} (Ziehen zum Verschieben in Ordner)`} >
- {doc.icon} + {doc.icon || '📄'} {doc.title || 'Unbenannt'} {doc.isPinned && }
@@ -830,14 +877,41 @@ export const WorkspaceView: React.FC = ({ const renderFolder = (node: FolderTreeItem): React.ReactNode => { const isExpanded = expandedFolders.has(node.path); const isCreatingSubfolder = isCreatingFolder && newFolderParent === node.path; + const isDragOver = dragOverFolder === node.path; const totalItems = node.docs.length + node.subfolders.length; return ( -
+
{ + if (draggingDocId) { + e.preventDefault(); + e.stopPropagation(); + setDragOverFolder(node.path); + } + }} + onDragLeave={(e) => { + e.preventDefault(); + if (dragOverFolder === node.path) { + setDragOverFolder(null); + } + }} + onDrop={(e) => { + e.preventDefault(); + e.stopPropagation(); + setDragOverFolder(null); + handleDropDocToFolder(node.path, e); + }} + >
toggleFolder(node.path)} style={{ paddingLeft: `${node.depth * 14 + 8}px` }} - className="group flex items-center justify-between py-1.5 pr-2 hover:bg-panel-elevated cursor-pointer transition-colors text-xs text-muted" + className={`group flex items-center justify-between py-1.5 pr-2 cursor-pointer transition-colors text-xs ${ + isDragOver + ? 'bg-accent/20 border-l-2 border-accent text-main font-medium' + : 'hover:bg-panel-elevated text-muted' + }`} >
@@ -954,7 +1028,7 @@ export const WorkspaceView: React.FC = ({ }`} > - Dokumente & Outliner + Notizen
- {workspaceMode === 'editor' && selectedDoc && ( -
- - - {saveState === 'saving' ? 'Speichert…' : 'Gespeichert'} - +
+ {workspaceMode === 'editor' && selectedDoc && ( + <> + + + {saveState === 'saving' ? 'Speichert…' : 'Gespeichert'} + + + + )} + + {onOpenHelp && ( -
- )} + )} +
{/* ===================================================================== */} @@ -1097,6 +1185,89 @@ export const WorkspaceView: React.FC = ({
) : (
+ {/* Drag target for Root when dragging a note */} + {draggingDocId && ( +
{ + e.preventDefault(); + e.stopPropagation(); + setDragOverFolder('__root__'); + }} + onDragLeave={(e) => { + e.preventDefault(); + if (dragOverFolder === '__root__') setDragOverFolder(null); + }} + onDrop={(e) => { + e.preventDefault(); + e.stopPropagation(); + setDragOverFolder(null); + handleDropDocToFolder('', e); + }} + className={`mx-2 my-1.5 p-2 border-2 border-dashed rounded-btn text-center text-xs transition-colors ${ + dragOverFolder === '__root__' + ? 'border-accent bg-accent/20 text-main font-semibold' + : 'border-border text-muted bg-canvas' + }`} + > + 📁 In Hauptverzeichnis verschieben (ohne Ordner) +
+ )} + + {/* Angepinnte Notizen (Favorites) */} + {pinnedDocs.length > 0 && !searchQuery && ( +
+
+ + Angepinnte Notizen ({pinnedDocs.length}) +
+
+ {pinnedDocs.map((doc) => { + const isSelected = selectedDoc?.id === doc.id && workspaceMode === 'editor'; + return ( +
{ + e.stopPropagation(); + e.dataTransfer.setData('application/mindsift-doc-id', doc.id); + e.dataTransfer.setData('text/plain', doc.id); + setDraggingDocId(doc.id); + }} + onDragEnd={() => setDraggingDocId(null)} + onClick={() => { + setSelectedDoc(doc); + setZoomedBlockId(null); + setWorkspaceMode('editor'); + }} + className={`group flex items-center justify-between px-2 py-1.5 rounded-btn transition-colors cursor-pointer text-xs ${ + isSelected + ? 'bg-panel-elevated border-l-2 border-accent text-main font-medium' + : 'text-muted hover:text-main hover:bg-canvas/50' + }`} + title="Angepinnte Notiz öffnen" + > +
+ {doc.icon || '📄'} + {doc.title || 'Unbenannt'} +
+ +
+ ); + })} +
+
+ )} + {rootTree.map(renderFolder)} {rootDocs.map((doc) => renderDocItem(doc, 0))} {filteredDocs.length === 0 && searchQuery && ( @@ -1117,18 +1288,7 @@ export const WorkspaceView: React.FC = ({
- + {selectedDoc.folderPath || 'Hauptverzeichnis'}
• {selectedDoc.blocks.length} Bullets @@ -1159,14 +1319,14 @@ export const WorkspaceView: React.FC = ({ {/* ZOOM BREADCRUMB BAR (Hoisting Focus Mode) */} {zoomedBlock && ( -
+
@@ -1176,7 +1336,7 @@ export const WorkspaceView: React.FC = ({ @@ -1184,18 +1344,10 @@ export const WorkspaceView: React.FC = ({ ))} - + {zoomedBlock.content || 'Aktiver Ast'}
- -
)} @@ -1205,14 +1357,14 @@ export const WorkspaceView: React.FC = ({ {showIconPicker && ( -
+
{ICONS.map((ic) => (
)} - {/* Quick Hint Bar for Logseq Outliner Controls */} -
-
- Enter Neuer Bullet - Tab Einrücken - Shift+Tab Ausrücken - / Slash-Befehle -
- Dot ziehen = Verschieben · Klick = Reinhoomen -
- {/* =========================================================== */} {/* LOGSEQ HIERARCHICAL BULLET LIST CANVAS */} {/* =========================================================== */} @@ -1574,11 +1715,8 @@ export const WorkspaceView: React.FC = ({

- Globales Notizen-To-Do Rollup + Globale To-Dos

-

- Alle Aufgaben aus sämtlichen Markdown-Dateien und Ordnern auf einen Blick. Abhaken synchronisiert direkt zurück in die jeweilige Datei. -

diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index ae9f48b..2e1da3e 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -810,7 +810,7 @@ func (s *Store) UpdateThoughtTriage(userID, thoughtID, action, convertTo, target {ID: domain.NewID("blk"), Type: "h2", Content: "Nächste Schritte"}, {ID: domain.NewID("blk"), Type: "todo", Content: "Ersten Meilenstein definieren", Checked: false}, } - doc, err := s.CreateDocument(userID, "", title, "✨", "", blocks, thought.Tags, false) + doc, err := s.CreateDocument(userID, "", title, "📄", "", blocks, thought.Tags, false) if err == nil && doc != nil { convertedRefID = doc.ID } -- 2.54.0 From 29d202cc4e5b4d172284e3b2b481eb974cf17823 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 01:12:39 +0200 Subject: [PATCH 02/37] docs: update README with outliner features and add curl one-liner installer --- README.md | 139 ++++++++++++++++++++++++++++++--------------- compose.yaml | 23 ++++++++ docker-compose.yml | 3 - install.sh | 50 ++++++++++++++++ 4 files changed, 165 insertions(+), 50 deletions(-) create mode 100644 compose.yaml create mode 100755 install.sh diff --git a/README.md b/README.md index 5317e32..7af3108 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ ### *Der Kopf frei in drei Sekunden.* [![Self-Hosted](https://img.shields.io/badge/100%25-Self--Hosted-2563eb?style=flat-square)](#) -[![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-in-60-sekunden-starten) -[![Markdown](https://img.shields.io/badge/Datenhoheit-Pure%20Markdown%20%26%20JSON-18181b?style=flat-square)](#) +[![Docker](https://img.shields.io/badge/Deployment-Single%20Container-2496ed?style=flat-square&logo=docker&logoColor=white)](#-schnellstart) +[![Markdown](https://img.shields.io/badge/Datenhoheit-Pure%20Markdown%20%26%20Outliner-18181b?style=flat-square)](#) [![License](https://img.shields.io/badge/Lizenz-MIT-22c55e?style=flat-square)](LICENSE) -**MindSift** ist deine zentrale Schaltstelle für spontane Gedanken, strukturierte Aufgaben, vernetztes Wissen und gemeinsame Echtzeit-Listen — kompromisslos schnell, ablenkungsfrei gestaltet und zu 100 % unter deiner eigenen Kontrolle. +**MindSift** ist deine zentrale, kompromisslos schnelle Schaltstelle für spontane Gedanken, strukturierte Aufgaben, hierarchische Outliner-Notizen und gemeinsame Echtzeit-Listen — ablenkungsfrei gestaltet, lokal als Standard-Markdown gespeichert und zu 100 % unter deiner eigenen Kontrolle. -[Philosophie](#-die-philosophie) • [Funktionen](#-was-mindsift-auszeichnet) • [Für wen?](#-für-wen-ist-mindsift-gemacht) • [Schnellstart](#-in-60-sekunden-starten) +[Philosophie](#-die-philosophie) • [Funktionen](#-was-mindsift-auszeichnet) • [Schnellstart](#-schnellstart) • [Für wen?](#-für-wen-ist-mindsift-gemacht) --- @@ -19,66 +19,98 @@ ## 💡 Die Philosophie: *Erst raus aus dem Kopf – später sortieren* Klassische Produktivitäts- und Notiz-Apps scheitern im Alltag oft an derselben Hürde: **Sie verlangen Struktur im falschen Moment.** -Wenn dir mitten im Gespräch, unterwegs oder kurz vor dem Feierabend ein wichtiger Gedanke kommt, willst du keinen Ordner auswählen, keine Datenbank-Eigenschaften pflegen und kein Formular ausfüllen. Du willst den Gedanken in drei Sekunden loswerden – und sicher sein, dass er nicht verloren geht. +Wenn dir mitten im Gespräch, unterwegs oder kurz vor Feierabend ein wichtiger Gedanke kommt, willst du keinen Ordner auswählen, keine Datenbank-Eigenschaften pflegen und kein Formular ausfüllen. Du willst den Gedanken in drei Sekunden loswerden – und sicher sein, dass er nicht verloren geht. **MindSift trennt das Erfassen radikal vom Organisieren:** -1. **Sofort abladen (Quick Dump):** Oben in der Eingabeleiste tippen, `Enter` drücken, fertig. Dein Kopf ist wieder frei. Wer möchte, kann einen Eintrag aber auch direkt bei der Eingabe einer Aufgabenliste, einer Einkaufsliste oder einer Notiz zuordnen. -2. **Spielerisch sichten (Triage):** Wenn du später Ruhe hast, arbeitest du deinen Gedanken-Stapel Karte für Karte durch. Nach links verwerfen, auf „Später“ schieben oder mit einem Klick in eine echte Aufgabe, ein Dokument oder einen Listeneintrag verwandeln. -3. **Nahtlos weiterarbeiten:** Aus flüchtigen Ideen werden strukturierte Aufgaben-Boards, verlinkte Wiki-Dokumente oder geteilte Listen für Familie und Team. +1. **Sofort abladen (Quick Dump):** Oben in der Eingabeleiste tippen, `Enter` drücken, fertig. Dein Kopf ist wieder frei. Alternativ kannst du einen Eintrag schon beim Schreiben direkt einer Aufgabenliste, Einkaufsliste oder Notiz zuweisen. +2. **Spielerisch sichten (Triage):** Wenn du später Ruhe hast, sichtest du deinen Gedanken-Stapel Karte für Karte per Tastatur oder Swipe. Nach links verwerfen, auf „Später“ schieben oder mit einem Tastendruck in eine Aufgabe, ein Dokument oder einen Listeneintrag verwandeln. +3. **Strukturiert vertiefen:** Aus spontanen Notizen werden hierarchische Outliner-Dokumente, vernetzte Notizbäume, Kanban-Boards oder geteilte Listen für Familie und Team. --- ## ✨ Was MindSift auszeichnet -### ⚡ Quick-Dump & Triage-Deck -* **Immer griffbereit:** Die Schnell-Eingabeleiste sitzt prominent ganz oben – jederzeit erreichbar per Klick oder Tastendruck (`C` oder `/`). -* **Entscheiden ohne Stress:** Im Triage-Deck siehst du immer genau eine Karte im Fokus, während darunter deine Warteschlange bereitliegt. Bearbeite den Text direkt auf der Karte, vergib ein Fälligkeitsdatum oder sortiere ihn per Swipe und Tastatur-Kürzel weg. +### ⚡ Quick-Dump & Triage-Deck (Das 3-Sekunden-Prinzip) +* **Blitz-Erfassung:** Die Schnelleingabeleiste sitzt prominent ganz oben im Header – jederzeit erreichbar per Klick oder Tastendruck (`C` oder `/`). +* **Entscheiden mit 1 Taste:** Im Triage-Deck steht immer genau ein Gedanke im Fokus: + * `H` oder `←`: Wegwerfen / Erledigt + * `L` oder `→`: Sofort zur Aufgabe machen + * `T`: Termin-Aufgabe mit Fälligkeitsdatum + * `D`: In Workspace-Notiz umwandeln + * `Leertaste`: Später (ans Ende des Stapels schieben) + +### 📝 Workspace: Der hierarchische Logseq-Outliner +* **Jeder Gedanke ein Bullet-Point:** Schreibe Notizen als flexible, hierarchische Gliederung: + * `Enter`: Neuer Bullet-Point auf derselben Ebene + * `Tab` / `Shift + Tab`: Unterordnen (einrücken) und ausrücken + * `Shift + Enter`: Weicher Zeilenumbruch innerhalb desselben Blocks +* **Zoom-in Hoisting:** Klicke auf den Bullet-Punkt (`•`), um tief in einen Ast hineinzuzoomen (Fokus-Modus). Über die dezenten Breadcrumbs oben springst du jederzeit wieder zurück. +* **Slash-Befehle (`/`):** Tippe einfach `/` für `/todo`, `/h1`, `/h2`, `/h3`, `/code`, `/table`, `/callout` oder `/progress`. +* **Datei-Explorer mit Drag & Drop:** Verschiebe Dokumente mit der Maus direkt per Drag & Drop in Ordner oder zurück ins Hauptverzeichnis. +* **Angepinnte Favoriten:** Hefte wichtige Notizen mit einem Klick an – sie erscheinen prominent ganz oben im Explorer. +* **100 % Standard-Markdown auf der Festplatte:** Alle Notizen werden unter `/data/workspace//...` als echte `.md`-Dateien gespeichert (mit 2-Leerzeichen-Einrückung). Vollständig kompatibel mit Obsidian, Logseq oder einfachen Text-Editoren – kein Vendor-Lock-in! + +### 📋 Globale To-Dos: Das automatische Aufgaben-Rollup +* **Alles auf einen Blick:** MindSift durchsucht all deine Notizen und sammelt sämtliche Checkboxen (`- [ ]`) in einem zentralen Rollup-Dashboard. +* **Ordner-Drilldown:** Isoliere einzelne Projekt-Ordner mit einem Klick. +* **Echtzeit-2-Wege-Sync:** Hakst du eine Aufgabe im Rollup ab, wird sie im selben Augenblick direkt in der physischen `.md`-Datei auf deiner Festplatte als `- [x]` aktualisiert! ### ✅ Aufgabenlisten & Kanban-Board -* **Mehrere Aufgabenlisten:** Trenne Berufliches, Privates, Projekte oder gemeinsame Team-Aufgaben sauber in eigenen Listen. -* **Liste oder Kanban:** Wechsle jederzeit mit einem Klick zwischen einer kompakten Listenansicht und einem übersichtlichen **3-Spalten-Kanban-Board** (*Zu erledigen*, *In Bearbeitung*, *Erledigt*). -* **Detailtiefe bei Bedarf:** Füge Aufgaben Fälligkeiten, Prioritäten und ausführliche Notizen hinzu – oder wandle eine wachsende Aufgabe direkt in ein großes Workspace-Dokument um. +* **Mehrere Listen:** Trenne Berufliches, Privates, Homelab oder Team-Projekte sauber in eigenen Listen. +* **Liste oder Kanban:** Wechsle mit einem Klick zwischen kompakter Listenansicht und dem **3-Spalten-Kanban-Board** (*Zu erledigen*, *In Arbeit*, *Erledigt*). +* **Teilen & Avatar-Stack:** Teile Aufgabenlisten mit Spaces und Teams. Der integrierte Avatar-Stack zeigt mit kleinen Initialen-Bubbles und Tooltips sofort, wer Zugriff hat. -### 📝 Workspace: Komfort wie in Word, gespeichert als echtes Markdown -* **Formatieren auf Knopfdruck:** Nutze die übersichtliche Werkzeugleiste für Überschriften, Fett/Kursiv, Checklisten, Code-Blöcke, Hervorhebungen, Tabellen und Fortschrittsbalken – ganz ohne Markdown-Befehle auswendig lernen zu müssen. -* **Vernetztes Wiki:** Verknüpfe Dokumente untereinander über interne `[[Wiki-Links]]` und organisiere dein Wissen in einer klaren Ordner- und Unterordner-Struktur. -* **Volle Datenhoheit (Kein Lock-in):** Jedes Dokument wird im Hintergrund als saubere, standardisierte `.md`-Datei in deiner Ordnerstruktur auf der Festplatte abgelegt. Du kannst deine Notizen jederzeit direkt aus dem Dateisystem kopieren, sichern oder mit anderen Programmen nutzen. +### 🛒 Geteilte Echtzeit-Listen (Einkauf & Checklisten) +* **Gemeinsam abhaken:** Ob Wocheneinkauf oder Urlaubs-Packliste – teile Listen in Echtzeit über WebSocket. +* **Live-Synchronisation:** Wird ein Artikel im Laden abgehakt, verschwindet er sofort bei allen anderen Teilnehmern. -### 🛒 Geteilte Echtzeit-Listen -* **Gemeinsam abhaken:** Ob Wocheneinkauf, Packliste für den Urlaub oder Checkliste fürs Projekt – teile Spaces per Einladungscode mit Partnern, Familie oder Kollegen. -* **Live-Synchronisation:** Hakst du im Supermarkt einen Artikel ab, verschwindet er im selben Augenblick auf den Bildschirmen aller anderen Mitglieder. +### 🏢 Organisationen, Teams & Administration +* **Geführte Ersteinrichtung:** Beim allerersten Start führt dich MindSift automatisch durch die Einrichtung deines Administrator-Kontos. +* **3-Stufen-Rollensystem:** Server-Admin, Team-Admin und reguläres Mitglied. +* **Einladungscodes & Registrierungs-Schalter:** Schalte die öffentliche Registrierung bequem per Schiebeschalter ab und lade neue Mitglieder gezielt per Einladungscode ein. +* **Server-Isolation:** Bestimme zentral, ob Nutzer strikt privat für sich arbeiten, sich nur innerhalb ihrer Teams sehen oder serverweit kooperieren. -### 🏢 Organisationen, Teams & Berechtigungskonzept -* **Geführte Ersteinrichtung:** Beim ersten Start begrüßt dich ein Einrichtungs-Assistent, mit dem du dein Administrator-Konto und die Sicherheitseinstellungen deines Servers festlegst. -* **Geschlossene Gesellschaft oder offenes Team:** Schalte die öffentliche Registrierung ab und vergib gezielte **Einladungscodes**, oder lege Benutzer direkt über die Administration an. -* **Drei klare Rollen:** Unterscheide zwischen **Server-Admin** (volle Server- und Mail-Steuerung), **Team-Admin** (darf Nutzer einladen sowie Organisationen und Teams verwalten) und **Mitglied**. -* **Sichtbarkeits-Isolation nach Maß:** Bestimme als Administrator zentral, ob Nutzer komplett isoliert für sich arbeiten (*Strikt Privat*), nur Mitglieder ihrer eigenen Teams sehen (*Teams isoliert*) oder serverweit zusammenarbeiten (*Offen*). +### 🔔 Multi-Channel-Alarme +* **Pünktlich erinnert:** Automatische Benachrichtigung vor Fälligkeit wahlweise per **E-Mail (SMTP)**, **Discord-Webhook** oder **eigenem Webhook-Endpunkt**. +* **Eigene API-Schlüssel:** Sende System-Alarme, Server-Logs oder Skripte per einfachem `curl`-Befehl direkt in deine Inbox. -### 🔔 Multi-Channel-Alarme & Eigene Webhooks -* **Keine Frist mehr verpassen:** Lass dich vor Fälligkeit einer Aufgabe automatisch benachrichtigen – frei wählbar per **E-Mail**, **Discord-Nachricht** oder **eigenem Webhook**. -* **Offen für deine Automatisierung:** Erzeuge persönliche API-Schlüssel in deinen Kontoeinstellungen und schicke System-Alarme, Server-Statusmeldungen oder eigene Skripte per einfachem `curl`-Befehl direkt als Aufgabe oder Notiz in dein MindSift. - -### 🖤 Fokussiertes, eckiges Dark-Design -* **Ruhe fürs Auge:** Ein klares, monochromes dunkles Design mit präzisen Kanten und einer einzigen, gezielt eingesetzten Akzentfarbe. Keine Ablenkung, keine überladenen Menüs, volle Konzentration auf deine Inhalte. +### 🎨 Single-File CSS Theming & Hilfe +* **1 Datei = 1 Theme:** Vollständiges Design-System gesteuert über genau eine CSS-Datei (`dark`, `light`, `high-contrast`, `soft`). +* **Integrierte Hilfeseite:** Ein Klick auf das Profil-Menü oder den Hilfe-Button öffnet eine strukturierte Dokumentation inklusive Tastatur-Cheat-Sheet. --- -## 👥 Für wen ist MindSift gemacht? +## 🚀 Schnellstart -* **Für Vielbeschäftigte & Kreative**, die tagsüber hunderte Gedanken, To-Dos und Links im Kopf jonglieren und ein System suchen, das schneller ist als jede klassische Notiz-App. -* **Für Familien, Paare & WGs**, die gemeinsame Einkaufslisten, Erledigungen und Planungen ohne Werbe-Apps und ohne fremde Cloud synchronisieren möchten. -* **Für Teams, Selbstständige & Homelab-Enthusiasten**, die Aufgaben, Projektdokumentation, Server-Webhooks und Benutzerrechte souverän auf der eigenen Infrastruktur betreiben wollen. +MindSift läuft als leichtgewichtiger, einzelner Container und bringt Webserver, API, Datenbank und Frontend direkt mit. + +### Option 1: Der 1-Zeiler (Empfohlen) + +Führe diesen Befehl im Terminal deines Servers aus. Er erstellt das Verzeichnis, lädt die Konfiguration, erzeugt ein sicheres JWT-Geheimnis und startet den Container: + +```bash +curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/install.sh | bash +``` --- -## 🚀 In 60 Sekunden starten +### Option 2: Schneller Start mit zwei `curl`-Befehlen -MindSift läuft als einzelner, ressourcenschonender Container und bringt alles mit, was du brauchst. +Falls du die Dateien direkt ohne Skript herunterladen möchtest: -### Start mit Docker Compose +```bash +mkdir -p mindsift && cd mindsift +curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/docker-compose.yml -o docker-compose.yml +curl -fsSL https://git.dsit.me/sandrodach/MindSift/raw/branch/dev/.env.example -o .env +docker compose up -d +``` -Lege eine `docker-compose.yml` an: +--- + +### Option 3: Manuelle `docker-compose.yml` + +Erstelle eine Datei namens `docker-compose.yml`: ```yaml services: @@ -93,10 +125,9 @@ services: environment: - PORT=8080 - DATA_DIR=/data - - JWT_SECRET=bitte-hier-ein-langes-sicheres-geheimnis-eintragen + - JWT_SECRET=hier-ein-langes-sicheres-zufalls-geheimnis-eintragen - ALLOW_REGISTRATION=false - # Optional: Eigener SMTP-Server für E-Mail-Alarme - # (kann alternativ auch später direkt in der Admin-Oberfläche eingetragen werden) + # Optional: Mailserver für E-Mail-Alarme (auch im Admin-UI einstellbar) - SMTP_HOST= - SMTP_PORT=587 - SMTP_USER= @@ -105,20 +136,34 @@ services: - SMTP_TLS=true ``` -Container starten: +Starte den Container: ```bash docker compose up -d ``` -Öffne anschließend **`http://localhost:8080`** im Browser. Beim allerersten Aufruf führt dich MindSift automatisch durch die **Ersteinrichtung** für dein Administrator-Konto. +--- + +### 🌐 Ersteinrichtung aufrufen + +Öffne deinen Browser unter **`http://localhost:8080`** (bzw. die IP deines Servers). +MindSift erkennt den Erststart automatisch und führt dich durch den Einrichtungs-Assistenten zum Erstellen des Administrator-Kontos. + +--- + +## 👥 Für wen ist MindSift gemacht? + +* **Für Macher & Wissensarbeiter**, die tagsüber dutzende Gedanken, Links und Aufgaben festhalten müssen, ohne aus dem Arbeitsfluss gerissen zu werden. +* **Für Outliner-Fans**, die die Flexibilität von Logseq schätzen, aber ihre Notizen in einer klaren Ordnerstruktur auf dem eigenen Server haben wollen. +* **Für Familien & WGs**, die gemeinsame Einkaufs- und Packlisten ohne Werbe-Apps und fremde Clouds synchronisieren möchten. +* **Für Homelab- & Self-Hosting-Begeisterte**, die volle Datenhoheit, saubere Markdown-Dateien und automatisierte Webhooks auf eigener Hardware schätzen. --- ## 🔒 Deine Daten gehören dir -* **100 % Self-Hosted:** Keine externen Cloud-Dienste, keine versteckte Telemetrie, kein Tracking. -* **Portabel & Transparent:** Alle Workspace-Notizen liegen als lesbare Markdown-Dateien im Datenverzeichnis; zusätzlich kannst du jederzeit ein komplettes Backup deiner Daten als JSON-Datei herunterladen. +* **100 % Self-Hosted:** Keine fremden Cloud-Dienste, keine Telemetrie, kein Tracking. +* **Kein Vendor-Lock-in:** Alle Notizen liegen als lesbare `.md`-Dateien im Ordner `./data/workspace`. Backups können jederzeit als JSON exportiert werden. --- diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..b74ba1a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,23 @@ +services: + mindsift: + image: git.dsit.me/sandrodach/mindsift:dev + container_name: mindsift + restart: unless-stopped + ports: + - "${PORT:-8080}:8080" + volumes: + - ./data:/data + environment: + - PORT=8080 + - DATA_DIR=/data + - JWT_SECRET=${JWT_SECRET:-change_this_to_a_secure_random_key_min_32_chars} + - BASE_URL=${BASE_URL:-http://localhost:8080} + - ALLOW_REGISTRATION=${ALLOW_REGISTRATION:-false} + - LOG_LEVEL=${LOG_LEVEL:-info} + # Optional SMTP / Mail Configuration (can also be configured in Admin UI) + - SMTP_HOST=${SMTP_HOST:-} + - SMTP_PORT=${SMTP_PORT:-587} + - SMTP_USER=${SMTP_USER:-} + - SMTP_PASS=${SMTP_PASS:-} + - SMTP_FROM=${SMTP_FROM:-} + - SMTP_TLS=${SMTP_TLS:-true} diff --git a/docker-compose.yml b/docker-compose.yml index d06a86f..b74ba1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,5 @@ services: mindsift: - build: - context: . - dockerfile: Dockerfile image: git.dsit.me/sandrodach/mindsift:dev container_name: mindsift restart: unless-stopped diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..7d26dd2 --- /dev/null +++ b/install.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "=============================================" +echo "🧠 MindSift — Schnell-Installation" +echo "=============================================" + +# 1. Prüfen ob Docker / Compose verfügbar ist +COMPOSE_CMD="" +if command -v docker &>/dev/null && docker compose version &>/dev/null; then + COMPOSE_CMD="docker compose" +elif command -v docker-compose &>/dev/null; then + COMPOSE_CMD="docker-compose" +elif command -v podman &>/dev/null && podman compose version &>/dev/null; then + COMPOSE_CMD="podman compose" +else + echo "❌ Fehler: Docker Compose (oder Podman Compose) wurde nicht gefunden." + echo "Bitte installiere Docker (https://docs.docker.com/get-docker/) und versuche es erneut." + exit 1 +fi + +INSTALL_DIR="${MINDSIFT_DIR:-mindsift}" +mkdir -p "${INSTALL_DIR}" +cd "${INSTALL_DIR}" + +echo "📥 Lade Konfigurationsdateien herunter..." +REPO_RAW_URL="https://git.dsit.me/sandrodach/MindSift/raw/branch/dev" + +curl -fsSL "${REPO_RAW_URL}/docker-compose.yml" -o docker-compose.yml +curl -fsSL "${REPO_RAW_URL}/.env.example" -o .env + +# Zufälliges JWT-Secret generieren falls Standardwert vorhanden +if grep -q "change_this_to_a_secure_random_key_min_32_chars" .env; then + RANDOM_SECRET=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 32 || openssl rand -hex 16 2>/dev/null || date +%s%N) + sed -i "s/change_this_to_a_secure_random_key_min_32_chars/${RANDOM_SECRET}/g" .env +fi + +echo "🚀 Starte MindSift Container..." +${COMPOSE_CMD} up -d + +echo "" +echo "=============================================" +echo "✅ MindSift wurde erfolgreich gestartet!" +echo "" +echo "🌐 Öffne deinen Browser unter:" +echo " http://localhost:8080" +echo "" +echo "💡 Beim ersten Aufruf wirst du automatisch durch" +echo " die Ersteinrichtung deines Admin-Kontos geführt." +echo "=============================================" -- 2.54.0 From 5b5b7b1810f198b12c8203cc13a2ab1d75732352 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 01:15:19 +0200 Subject: [PATCH 03/37] feat(workspace): make hoisted bullet point header editable in zoom mode --- .../src/features/workspace/WorkspaceView.tsx | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 8c7c2b6..2cf88b3 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -1391,13 +1391,26 @@ export const WorkspaceView: React.FC = ({
)} - {/* If zoomed, show zoomed block as prominent header */} + {/* If zoomed, show zoomed block as editable prominent header */} {zoomedBlock && ( -
-

- - {zoomedBlock.content || 'Unbenannter Ast'} -

+
+ + updateBlock(zoomedBlock.id, { content: e.target.value })} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + const zoomIdx = selectedDoc.blocks.findIndex((b) => b.id === zoomedBlock.id); + if (zoomIdx !== -1) { + insertNewBulletAfter(zoomIdx, (zoomedBlock.level || 0) + 1); + } + } + }} + placeholder="Bullet-Titel bearbeiten… (Enter für Unterpunkt)" + className="w-full bg-transparent text-xl font-bold text-main focus:outline-none placeholder:text-faint" + />
)} @@ -1683,11 +1696,26 @@ export const WorkspaceView: React.FC = ({
)}
+ + {/* Circled Question Mark Help Button (Rechtsbündig) */} +
{/* Navigation tabs attached to header */} @@ -533,12 +547,11 @@ export const App: React.FC = () => { setActiveTab('help')} /> )} {activeTab === 'help' && ( - setActiveTab('workspace')} /> + setActiveTab(previousTab || 'triage')} /> )} {activeTab === 'lists' && ( diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 2cf88b3..23e7cb4 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -46,7 +46,6 @@ import { api, triggerHaptic } from '../../lib/api'; interface WorkspaceViewProps { initialDocId?: string | null; onStatsChange: () => void; - onOpenHelp?: () => void; } const ICONS = ['📄', '📝', '💡', '🚀', '🏡', '⚡', '🎯', '🍕', '🛠️', '🧠', '🔥', '🏔️', '📦', '📑', '⚙️', '📊']; @@ -82,7 +81,6 @@ const SLASH_COMMANDS: SlashCommandItem[] = [ export const WorkspaceView: React.FC = ({ initialDocId, onStatsChange, - onOpenHelp, }) => { // Top Workspace Tab Mode: 'editor' vs 'todos' (Global To-Do Rollup Dashboard) const [workspaceMode, setWorkspaceMode] = useState<'editor' | 'todos'>('editor'); @@ -1066,18 +1064,6 @@ export const WorkspaceView: React.FC = ({ )} - - {onOpenHelp && ( - - )}
-- 2.54.0 From 14e3176e554b79acb93378beb4b53f20456e8ee1 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 01:20:38 +0200 Subject: [PATCH 05/37] style(workspace): make pin button icon-only matching trash button --- frontend/src/features/workspace/WorkspaceView.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 23e7cb4..2391d40 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -1286,10 +1286,14 @@ export const WorkspaceView: React.FC = ({ onClick={() => persistDocument({ ...selectedDoc, isPinned: !selectedDoc.isPinned }) } - className={selectedDoc.isPinned ? 'btn-action text-accent border-accent/40' : 'btn-action'} + className={ + selectedDoc.isPinned + ? 'btn-icon text-accent border-accent/40 bg-accent/10' + : 'btn-icon' + } + title={selectedDoc.isPinned ? 'Von Favoriten / Angepinnt entfernen' : 'Oben in Favoriten anpinnen'} > - {selectedDoc.isPinned ? 'Angepinnt' : 'Anpinnen'} {showProfileMenu && ( diff --git a/frontend/src/features/capture/InstantCaptureBar.tsx b/frontend/src/features/capture/InstantCaptureBar.tsx index 832505c..8f0d0a1 100644 --- a/frontend/src/features/capture/InstantCaptureBar.tsx +++ b/frontend/src/features/capture/InstantCaptureBar.tsx @@ -212,13 +212,13 @@ export const InstantCaptureBar: React.FC = ({
{/* Quick-Capture Input Bar (Radius controlled by --radius-pill) */}
-
+
@@ -254,7 +254,7 @@ export const InstantCaptureBar: React.FC = ({ type="button" onClick={() => setShowOptions((prev) => !prev)} title="Ziel wählen (Quick Dump, direkt als Aufgabe, auf Liste oder als Notiz) & Optionen" - className={`h-6 px-2.5 capture-pill text-[11px] flex items-center gap-1 transition-colors ${ + className={`h-6 px-2.5 capture-pill rounded-full text-[11px] flex items-center gap-1 transition-colors ${ showOptions || target !== 'inbox' || priority !== 'normal' || dueDate ? 'bg-accent-dim text-accent border border-accent-border font-medium' : 'text-muted hover:text-main hover:bg-panel-elevated border border-transparent' @@ -281,7 +281,7 @@ export const InstantCaptureBar: React.FC = ({ type="button" onClick={toggleVoiceDictation} title="Spracheingabe" - className={`w-6 h-6 capture-pill flex items-center justify-center transition-colors ${ + className={`w-6 h-6 capture-pill rounded-full flex items-center justify-center transition-colors ${ isListening ? 'bg-accent-dim text-accent' : 'text-muted hover:text-main hover:bg-panel-elevated' @@ -294,7 +294,7 @@ export const InstantCaptureBar: React.FC = ({
) : ( -

+

{activeCard.content}

)} diff --git a/frontend/src/index.css b/frontend/src/index.css index e8ee468..261721a 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -84,6 +84,12 @@ body { border-radius: 9999px !important; } + /* Quick-Capture-Blase bei mehrzeiliger Eingabe (Shift+Enter) */ + .capture-bar-multiline, + .capture-bar-multiline.bg-panel.border { + border-radius: 18px !important; + } + /* Aufgaben-Checkboxen */ .checkbox-square { border-radius: var(--radius-checkbox); -- 2.54.0 From 63db1a0f903b284259aafa06526017e2a6d54620 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 01:30:10 +0200 Subject: [PATCH 08/37] fix(nav): move borderless help button to tab navigation bar --- frontend/src/App.tsx | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 90b65d7..4e937f1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -428,21 +428,11 @@ export const App: React.FC = () => {
)}
- - {/* Circled Question Mark Help Button (Rechtsbündig) */} - {/* Navigation tabs attached to header */} -