Archiv als eigenes Fenster: Trilium raus, Notizbaum mit In-Place-Bearbeitung

Trilium-Integration komplett zurückgenommen (Revert von 47951a6): keine
verwaltete Instanz, kein Runtime-Download, kein ETAPI, kein Keyring-Token,
kein Setup-Wizard. Das Archiv sind wieder Markdown-Dateien unter archiveHome
samt FTS5-Suche und Wikilinks.

Notizmodell (an Trilium orientiert, ohne dessen Überbau):
- Jeder Knoten besitzt eine Textdatei — ensure_node_texts legt fehlende beim
  Seitenaufbau an (Ordner: gleichnamiges .md daneben, Wurzel: index.md);
  Ordner und Zwillingsdatei sind EINE Notiz mit Inhalt und Kindern.
- Der angezeigte Titel steht im Frontmatter der Datei, der Datei-/Ordnername
  ist technischer Verwaltungsname; move_folder nimmt den Knotentext mit.

Archiv-Ansicht (wiki-view):
- Baum links: Knoten- und Blatt-Symbole, Führungslinien pro Ebene, Dokumente
  näher an der Linie als Ordner; ganze Zeile wählt aus, nur der Pfeil klappt.
- Notiz-Ansicht rechts: großer Titel (Klick bearbeitet ihn → archive_set_title),
  Zurück-Knopf mit Auswahl-Verlauf, Details als Popup am Info-Knopf, Kindliste
  als abgesetzter Karten-Block mit Anlage-/Änderungsdatum.
- Bearbeiten in place: Split-View aus Rohtext und Live-Vorschau, Speichern per
  Knopf oder Ctrl+S (archive_read/archive_write; Frontmatter bleibt).
- Rechte Maustaste im Baum als einziger Aktionsweg (eigenes Kontextmenü,
  Standard-Menü des Webviews aus); Anlegen/Umbenennen über modale Dialoge.

Feste Flächenaufteilung statt Ablösen/Andocken:
- Angedocktes Panel = Session-Begleiter (ToDo, Befehle, flüchtiger Entwurf);
  der Dokument-Tab ist aus der Leiste raus.
- Archiv-Fenster = Archiv + Suche, Größe und Position einmalig beim Öffnen
  aus dem Monitor des rufenden Terminal-Fensters (85 % / 92 %); Öffner-Klick
  bei stehendem Fenster fokussiert und schaltet den Tab (panel-mode).
- Terminal-Fenster zu → Archiv-Fenster geht mit.

Neue Commands: archive_read, archive_write, archive_set_title.
This commit is contained in:
marcus hinz
2026-07-26 14:44:06 +02:00
parent 47951a615b
commit 9de58d43e8
68 changed files with 5943 additions and 1466 deletions
+59 -22
View File
@@ -15,7 +15,8 @@ import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { listen } from "@tauri-apps/api/event";
import { wirePanel } from "./panel-wiring";
import { applyTheme, THEMES } from "./themes";
import { panelToast } from "./tiles";
import { flash, panelToast } from "./tiles";
import { initArchiveForm } from "./archive-form";
import { applyI18n, t } from "./messages";
// Debug-Instrumentierung: Fehler auf der Seite anzeigen und ins Dev-Log spiegeln.
@@ -188,10 +189,10 @@ window.addEventListener("resize", () => fit.fit());
await invoke("term_start", { project, rows: term.rows, cols: term.cols });
term.focus();
// --- Andockbares Panel ----------------------------------------------------
// Die MCP-Tools schreiben in die Puffer-Dateien; terminal.rs meldet neuen
// Inhalt als `<buffer>-update`. Das Panel blendet sich dann ein — außer es
// ist gerade in ein eigenes Fenster abgelöst (`panel-detached`).
// --- Andockbares Entwurfs-Panel -------------------------------------------
// Der Skill schreibt Entwürfe in eine Datei; terminal.rs meldet neuen Inhalt
// per `panel-update`. Das Panel blendet sich dann ein — außer es ist gerade in
// ein eigenes Fenster abgelöst (`panel-detached`).
const panel = document.getElementById("panel")!;
const splitter = document.getElementById("splitter")!;
@@ -211,20 +212,10 @@ function hidePanel() {
// Gemeinsame Verdrahtung (Views, Modi, Update-Events); jedes eingehende
// Update blendet das angedockte Panel ein, solange es nicht abgelöst ist.
const { views, mode } = await wirePanel(project, () => {
const { view, views, mode } = await wirePanel(project, () => {
hasContent = true;
if (!detached) showPanel();
});
// write_panel hat eine Notiz angelegt: das Trilium-Fenster des Projekts
// zeigt sie. Leerer Puffer = Session-Start, keine Notiz.
await listen<string>("noteopen-update", (e) => {
if (!e.payload.trim()) return;
const { noteId } = JSON.parse(e.payload);
void invoke("trilium_open", { project, noteId }).catch((err) =>
panelToast(String(err)),
);
});
// Panel startet zugeklappt — außer das ToDo-Modul ist gewählt und die Liste
// nicht leer: dann öffnet das Panel beim Start mit der ToDo-Ansicht.
const todos = views.get("todo");
@@ -238,6 +229,22 @@ if (todos && !todos.empty()) {
// Tab-Klick im Header öffnet das (angedockte) Panel, falls es zu ist.
const headerTabs = document.getElementById("panel-tabs")!;
// Ausnahme Archiv & Suche: öffnen standardmäßig im eigenen Panel-Fenster
// (Popup) statt in der schmalen angedockten Spalte — Capture-Phase, damit
// weder der Modus-Wechsel des Tabs noch das Einblenden des Panels feuern.
headerTabs.addEventListener(
"click",
(e) => {
const btn = (e.target as HTMLElement).closest<HTMLElement>(
'button[data-mode="wiki"], button[data-mode="search"]',
);
if (!btn) return;
e.stopPropagation();
e.preventDefault();
void invoke("open_panel_window", { project, mode: btn.dataset.mode });
},
{ capture: true },
);
headerTabs.addEventListener("click", () => {
hasContent = true;
if (!detached) showPanel();
@@ -263,17 +270,47 @@ await listen("panel-window-closed", () => {
headerTabs.hidden = false;
});
// Der aktive Tab wandert mit ins abgelöste Fenster.
document
.getElementById("panel-detach")!
.addEventListener("click", () =>
invoke("open_panel_window", { project, mode: mode.current() }),
);
// Ablösen gibt es nicht mehr — die Flächen sind fest verteilt; der Knopf
// bleibt versteckt.
document.getElementById("panel-detach")!.hidden = true;
document.getElementById("panel-hide")!.addEventListener("click", () => {
hidePanel();
mode.clear();
});
// Archivieren: Button klappt das Formular (Ordner/Beschreibung/Schlagwörter)
// auf; Abschicken wählt notfalls erst das Archiv-Home per Dialog.
const archiveBtn = document.getElementById("panel-archive")!;
const archiveForm = initArchiveForm(archiveBtn, async (meta) => {
try {
const configured = await invoke<string | null>("panel_archive_dir_cmd", {
project,
});
let dir: string | undefined;
if (!configured) {
const { open } = await import("@tauri-apps/plugin-dialog");
const chosen = await open({ directory: true, title: t("panel.chooseArchiveDir") });
if (!chosen) return;
dir = chosen as string;
}
await view.flush(); // offene Bearbeitung erst speichern — archiviert, was zu sehen ist
const path = await invoke<string>("panel_archive_cmd", {
project,
dir,
folder: meta.folder ?? null,
description: meta.description ?? null,
tags: meta.tags,
});
flash(archiveBtn, "copied", 1400);
invoke("reveal_path_cmd", { path });
} catch (e) {
flash(archiveBtn, "error", 1400);
panelToast(`Archivieren fehlgeschlagen: ${e}`);
invoke("term_log", { msg: `archive: ${e}` });
}
});
archiveBtn.addEventListener("click", () => archiveForm.toggle());
// Splitter: Panelbreite ziehen.
splitter.addEventListener("mousedown", (e) => {
e.preventDefault();