Deleting a note (or its folder) moves it to the trash instead of erasing it. Trashed notes leave the tree, search, export and stats and are hidden from everyone they were shared with; the owner sees them read-only with a restore banner. New endpoints list, restore, purge and empty the trash; restoring recreates a deleted folder. Sidebar entry and mobile menu item with counter, list view with two-tap permanent delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
Modul: Workspace (Outliner-Notizen)
Code: WorkspaceView.tsx (Explorer, Tags, To-Do-Rollup), DocumentEditor.tsx, OutlinerTree.tsx, OutlinerBlock.tsx, CodeBlockItem.tsx, LinkModal.tsx; Backend: Dokument-, Ordner- und Tag-Handler, Sync-Handler in internal/api/docsync.go, internal/collab, internal/storage/docsync.go.
Datenhaltung
- Eine Notiz ist ein Datensatz in
documentsplus ein Baum von Blöcken inblocks(parent_id, Sortierschlüsselorder_key). Jede Änderung läuft überinternal/collabund wird im Logdoc_opsfestgehalten. Ebene und Position für die Oberfläche berechnet der Server beim Lesen. Das Log und gelöschte Blöcke werden regelmäßig verdichtet (database.md). Alles liegt in SQLite; es gibt keine Dateien auf der Platte. - Bei jeder Änderung wird
documents.markdown_cacheneu erzeugt;block_tagswird aus#Hashtagsder Blöcke neu aufgebaut. - Hashtag-Regel:
#wortzählt nur am Textanfang oder nach Leerzeichen bzw.(. Anker in Links (example.com/blog#ueberschrift) und Wörter wieC#sind keine Tags. Gleiche Regel im Server (domain.ExtractTags) und im Browser (lib/hashtags.ts). Beim ersten Start nach der Umstellung entferntmigrateAnchorTagseinmalig gespeicherte Tags, die nur aus Ankern stammten. - Der Export als
.mderfolgt im Browser (Button im Editor, Blob-Download). - Blocktypen: Überschriften
h1–h3,paragraph/text,bullet,todo,code,callout,divider,table,progress.
Editor
-
Outliner:
Enterneuer Block,Tab/Shift+Tabein-/ausrücken,Shift+Enterweicher Umbruch, Blöcke ein-/ausklappen. -
Zoom (Hoisting): Klick auf den Bullet fokussiert den Ast; die Breadcrumb-Leiste führt zurück.
-
Drag & Drop verschiebt Blöcke samt Unterästen; auf einen anderen Block gezogen, wird der Block zum Unterpunkt.
-
Slash-Befehle:
/todo,/link,/h1,/h2,/h3,/code,/callout,/divider(Liste inOutlinerBlock.tsx). -
Links:
[[Notiztitel]], Markdown-Links[Titel](url)und reine URLs werden als Token dargestellt;LinkModalhilft beim Verknüpfen von Notizen. -
Einfügen: Mehrzeiliger Text wird anhand von Markdown-Präfixen und Einrückung in Blöcke zerlegt; Überschriften gruppieren darunterliegenden Text und Code.
-
Speichern: Jede Notiz hat im Browser eine
DocSyncEngine(lib/docSync/). Der Editor meldet jede Änderung der Block-Liste; die Engine übersetzt sie in Operationen, speichert sie zuerst in IndexedDB und sendet sie dann gesammelt (300 ms) anPOST /docs/{id}/sync. Beim Öffnen zeigt sie sofort den gespeicherten Stand, auch offline. -
Abgleich bei geteilten oder mehrfach geöffneten Notizen, nach Verfügbarkeit:
- Push über WebSocket oder SSE: Der Server meldet
DOC_SEQ, die Engine gleicht sofort ab (im Test 0,2–0,3 s). - Long-Polling (
/docs/{id}/changes), wenn ein Proxy WebSocket und SSE blockiert oder SSE puffert: ebenso schnell. - Abfrage alle 3 s, wenn auch Long-Polling dreimal scheitert (bei
onlinewird Long-Polling erneut versucht).
Solange 1. oder 2. läuft, gleicht die Engine zusätzlich alle 7 s ab, wenn sie Sperren hält (Verlängerung), sonst alle 30 s zur Sicherheit. Ein Fokuswechsel meldet die Sperre sofort an. Der Editor zeigt den Weg an („Live“, „Live (Long-Polling)“, „Abgleich alle 3 s“).
- Push über WebSocket oder SSE: Der Server meldet
-
Private Notizen, die nur einmal geöffnet sind, gleichen alle 30 s ab, außerdem sofort bei Rückkehr ins Netz oder in den Tab. Beim Verlassen sendet die Engine Ausstehendes mit
keepaliveund gibt ihre Sperren frei. -
Textkonflikte: Jede Textänderung trägt die Version, die der Nutzer beim Tippen vor sich hatte. Hat jemand anderes den Bullet inzwischen geändert oder gesperrt, legt der Server eine Konfliktkopie an; es wird nie still überschrieben.
-
Sperren: Der Bullet mit dem Cursor, verlassene Bullets für 20 s und neu angelegte Bullets werden gehalten. Bullets, die jemand anderes hält, zeigen ein Schloss mit Namen und sind schreibgeschützt; Enter darin legt einen neuen Bullet darunter an. Löschen, Zusammenführen, Ein-/Ausrücken und Ziehen werden bei gesperrten Bullets nicht ausgeführt (Hinweis unten im Editor).
-
Präsenz: Im Kopf der Notiz steht, wer sie gerade sonst noch geöffnet hat.
-
Konfliktkopien sind farbig markiert („Konflikt – Version von …“) mit „Übernehmen“ (Text ersetzt den ursprünglichen Punkt bzw. den Titel, die Kopie verschwindet) und „Verwerfen“.
-
Rückgängig:
Strg+Z/Cmd+Znimmt die eigene letzte Änderung zurück,Strg+Umschalt+ZoderStrg+Ywiederholt (auch im Titel und über das Kontextmenü/mobile Tastaturen). Tippen im selben Punkt bis 1 s Pause ist ein Schritt; bis zu 200 Schritte je Tab. Hat jemand anderes den Punkt inzwischen geändert oder hält ihn gesperrt, wird der Schritt mit Hinweis übersprungen. -
PUT /documents/{id}ändert nur noch Symbol, Anheften und Ordner (Titel wird ins Log übernommen); Inhalte laufen ausschließlich über die Engine. Das To-Do-Rollup hakt per einzelner Operation über denselben Sync-Endpunkt ab. -
Notiz-Icon (Emoji-Picker): Vollständiger Emoji-Katalog mit Kategoriereitern, Suche, Verlauf zuletzt verwendeter Symbole (
localStorage) und dynamischer Theme-Anpassung. Auf Mobilgeräten (< 640 px) als Bottom-Sheet mit touch-freundlichen Zielen (≥ 44 px) undtext-base-Suchfeld (verhindert iOS-Zoom).
Explorer, Tags, To-Dos
- Ordnerbaum mit Favoriten (
doc_pins), Verschieben per Drag & Drop (/documents/{id}/move,/folders/move). - Tags: Alle
#Hashtagswerden aggregiert (/workspace/tags). Eine Tag-Seite zeigt alle Fundstellen gruppiert nach Notiz und kann eine eigene Freitext-Notiz tragen (tag_pages). - To-Do-Rollup: Die Ansicht „To-Dos“ sammelt alle
todo-Blöcke aller Notizen, filterbar nach Ordner. Abhaken im Rollup ändert den Block in der Notiz. - Enhanced Tasks: Ein
todo-Block lässt sich als verknüpfte Aufgabe führen (/documents/{id}/blocks/{blockId}/enhanced-task); Erledigt-Status bleibt zwischen Notiz und Aufgabenansicht synchron.
Papierkorb
- Löschen (einzeln oder mit dem Ordner) verschiebt eigene Notizen in den Papierkorb (
is_archived = 1). Wer eine geteilte Notiz löscht, die ihm nicht gehört, verlässt nur die Freigabe. - Notizen im Papierkorb fehlen im Baum, in Suche, Export und Statistik und sind für alle Empfänger unsichtbar (
DocRole= keine Rolle). Eigentümer und Löschende sehen sie schreibgeschützt mit Hinweisleiste und „Wiederherstellen“; Umbenennen, Verschieben und Teilen werden abgelehnt. - Eintrag „Papierkorb“ mit Zähler unten in der Ordnerleiste und im mobilen Menü. Die Liste zeigt Ordner und Löschzeitpunkt; „Endgültig löschen“ und „Papierkorb leeren“ brauchen einen zweiten Tipp.
- Wiederherstellen legt einen inzwischen gelöschten Ordner neu an; existiert ein geteilter Ordner nicht mehr, landet die Notiz auf der Hauptebene. Freigaben bleiben erhalten. Der Papierkorb leert sich nicht von selbst.
Freigabe
- Einzelne Notizen und ganze geteilte Ordner lassen sich an Benutzer oder Gruppen geben. Empfänger haben nur Leserechte; nur der Eigentümer schreibt (sharing-and-permissions.md, FLAW-17).
- Empfänger ordnen geteilte Notizen und Ordner in ihrem eigenen Baum ein (
doc_user_folders,workspace_folder_mounts), ohne dass der Eigentümer etwas davon merkt. - Änderungen erreichen offene Fenster über den Weckruf
DOC_SEQund anschließenden Sync; Notizlisten überDOC_UPDATED(websocket.md).
Grenzen
- Keine gleichzeitige Mehrbenutzerbearbeitung, keine Cursor-Anzeige, keine Versionshistorie.
- Keine Virtualisierung langer Dokumente; sehr große Notizen können auf Mobilgeräten träge werden (nicht gemessen).
Suche (: Mit der globalen FTS5-Suche (/search) erfasst nur Titel und Inhalt eigener NotizenCmd+K/Ctrl+K//) werden sowohl Titel als auch Block-Inhalte aller zugänglichen Notizen (eigene und freigegebene) durchsucht (FLAW-18).
Export als Markdown-Archiv
Kontoeinstellungen → Private Webhooks & Export → „Markdown-Archiv herunterladen“ (GET /export/markdown, Code: export_markdown.go).
- Ordner und Notizen so, wie der Nutzer sie sieht (eigene Ordner, eingehängte geteilte Ordner, mit ihm geteilte Notizen); leere Ordner bleiben erhalten.
- Jede Notiz: YAML-Frontmatter (
title,icon,tags,created_at,updated_at) und Markdown, das Obsidian direkt darstellt: Überschriften, Trenner, Hinweisboxen (> [!note]) und Codeblöcke der obersten Ebene als echte Blöcke, ihre Kinder rücken eine Ebene nach vorn; alles andere als Liste mit einem Tab pro Ebene, To-dos als- [ ]. - Dateinamen werden für alle Betriebssysteme bereinigt (keine
/ \ : * ? " < > |, nie./..); doppelte Titel bekommen „ (2)“. - Das ZIP wird beim Lesen der Daten direkt in die Antwort geschrieben, ohne temporäre Dateien.
Import
Kontoeinstellungen → Private Webhooks & Export → „Daten importieren“, mit zwei getrennten Wegen (Code: import_markdown.go, ImportCard.tsx):
| Weg | Endpunkt | Dateien | Ergebnis |
|---|---|---|---|
| MindSift-Daten | POST /import/mindsift |
Backup-ZIP oder JSON (/export) oder Markdown-Archiv (/export/markdown), das Format wird erkannt |
Notizen in ihre Ordner, Aufgabenlisten mit Unteraufgaben, Fälligkeit, Priorität und Notizen, Listen mit Menge, Kategorie und Erledigt-Status, beim JSON-Backup auch die Gedanken der Triage. Fremde, nur mit dem Exporteur geteilte Inhalte im JSON werden übersprungen. |
| Obsidian-Vault | POST /import/obsidian |
ZIP eines Vaults | Alles in einen neuen Ordner mit dem Namen des Vaults, Unterordner bleiben. Checkboxen bleiben Checkboxen, Frontmatter-Tags werden zu #Hashtags, Callouts zu Hinweisboxen, Wikilinks bleiben [[…]]. Versteckte Ordner (.obsidian, .trash) werden ignoriert, Anhänge übersprungen. |
- Vorschau: Die Oberfläche lädt die Datei zuerst mit
?preview=1hoch. Der Server liest und prüft alles und meldet, was angelegt würde (inkl. übersprungener Dateien mit Grund), schreibt aber nichts. Erst „Jetzt importieren“ legt an. - Hinzufügen oder Wiederherstellen (nur MindSift-Daten): „Hinzufügen“ legt nur neu an, Bestehendes bleibt unverändert. „Wiederherstellen“ (
?mode=restore, in der Oberfläche nur nach bestätigtem Warnhinweis) löscht vorher alle eigenen Notizen, Ordner, Aufgaben, Gedanken und Listen – erst nachdem die Datei vollständig gelesen und geprüft ist, eine kaputte Datei löscht also nichts. Mit dem Nutzer geteilte Inhalte anderer bleiben. Ein Obsidian-Import fügt immer hinzu. - Markdown → Notiz: Listen behalten ihre Verschachtelung (Tabs oder Leerzeichen), Inhalte unter einer Überschrift werden darunter eingerückt (Gegenstück zum Export), Absätze werden zu Punkten, Codeblöcke zu Codeblöcken.
- Grenzen: Upload max. 100 MB, eine Notiz max. 10 MB, entpackt zusammen max. 500 MB, höchstens 20 000 Dateien. Pfade mit
..oder absolute Pfade werden entschärft und bleiben im Zielordner. - Bricht ein Import wegen eines Fehlers ab, bleibt das bis dahin Angelegte erhalten; die Meldung nennt das Element, an dem es scheiterte.