Archiv-Wiki im Panel: vier Tabs, FTS5-Suche, strukturierbares Archiv

- Archiv-Home pro Projekt (archiveHome in ai-control.json); archive_panel
  mit Ordner, Beschreibung und Schlagwörtern (Frontmatter + Zeitstempel-Stem)
- Archiv-Index (archive_index.rs): Scan mit Frontmatter, Wikilinks,
  Backlinks; Wiki-Seiten als strukturierte Daten (Übersicht/Tag-Seiten)
- FTS5-Suche (archive_search.rs, rusqlite bundled): in-memory-Index pro
  Anfrage; MCP search_archive und Panel-Suchfeld (search_run) teilen die Engine
- Panel: vier Puffer/Ansichten (Dokument, Befehle, Suche, Wiki) mit Tabs im
  Fenster-Header; gemeinsame Verdrahtung panel-wiring.ts, Kachel-CSS,
  Wiki-View mit Tag-Chips, Ordner-Sektionen und Backlinks
- Archivieren speichert offene Bearbeitung (flush); Tab-Wechsel speichert
  statt zu blockieren; Fehler sichtbar als Panel-Toast
- Abgelöstes Fenster: rahmenlos (Linux), eigene Kopfleiste mit Tabs und
  Fensterknöpfen, Theme-Kopplung über gemeinsames themes.ts
- MCP-Tools show_commands, show_archive; Befehls-History mit Kachel-Löschen
This commit is contained in:
marcus hinz
2026-07-19 15:25:40 +02:00
parent d4d455fa08
commit 994d5070f0
29 changed files with 3471 additions and 571 deletions
+117
View File
@@ -0,0 +1,117 @@
/// Gemeinsame Panel-Verdrahtung für das angedockte Panel (terminal.ts) und
/// das abgelöste Fenster (panel.ts): Entwurfs-Ansicht, Befehls- und
/// Such-Kacheln, Modus-Umschalter, Wikilink-Klick und die drei Update-Events.
/// Die Element-IDs sind in beiden HTML-Dateien identisch; die Draft-Controls
/// trägt das Markup als `.draft-only`.
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { initPanelView, type PanelView } from "./panel-view";
import {
initCommandsView,
initPanelMode,
panelToast,
type CommandsView,
type PanelMode,
} from "./commands-view";
import { initSearchView } from "./search-view";
import { initWikiView } from "./wiki-view";
import "./panel-tiles.css";
export interface PanelWiring {
view: PanelView;
cmdView: CommandsView;
mode: { to(m: PanelMode): void; current(): PanelMode };
/// Entwurfstext beim Start (für die Anfangs-Modus-Entscheidung).
draft: string;
}
export async function wirePanel(
project: string,
onIncoming?: () => void,
): Promise<PanelWiring> {
const titleEl = document.querySelector(".panel-title") as HTMLElement;
const [defaultLang, draft, cmds, search, wiki] = await Promise.all([
invoke<string>("spellcheck_lang"),
invoke<string>("panel_read", { project }),
invoke<string>("commands_read", { project }),
invoke<string>("search_read", { project }),
invoke<string>("wiki_read", { project }),
]);
// Jeder Wiki-Sprung (Wikilink, Chip, Suchtreffer) geht als ein Invoke an den
// Kern; das Ergebnis kommt über den Wiki-Puffer und `wiki-update` zurück.
const openWiki = (name: string) => invoke("wiki_open", { project, name });
const view = initPanelView({
content: document.getElementById("panel-content")!,
copyBtn: document.getElementById("panel-copy")!,
modeBtn: document.getElementById("panel-mode")!,
titleEl,
editBtn: document.getElementById("panel-title-edit")!,
editContentBtn: document.getElementById("panel-content-edit")!,
langSelect: document.getElementById("panel-lang") as HTMLSelectElement,
defaultLang,
onCommit: (text) => invoke("panel_set", { project, text }),
onWikiLink: openWiki,
});
const cmdView = initCommandsView(document.getElementById("commands-content")!, (line, entry) =>
invoke("commands_delete", { project, line, entry }),
);
const searchView = initSearchView(
document.getElementById("search-content")!,
(relpath) => openWiki(relpath.split("/").pop()!.replace(/\.md$/, "")),
(query) =>
void invoke("search_run", { project, query }).catch((e) =>
panelToast(`Suche fehlgeschlagen: ${e}`),
),
);
const wikiView = initWikiView(document.getElementById("wiki-content")!, openWiki);
const mode = initPanelMode({
tabsEl: document.getElementById("panel-tabs")!,
draftEls: [
document.getElementById("panel-content")!,
...document.querySelectorAll<HTMLElement>(".draft-only"),
],
commandsContent: document.getElementById("commands-content")!,
searchContent: document.getElementById("search-content")!,
wikiContent: document.getElementById("wiki-content")!,
titleEl,
flush: () => void view.flush(),
});
view.set(draft);
cmdView.set(cmds);
searchView.set(search);
wikiView.set(wiki);
// Wiki-Tab bei leerem Puffer (Session-Start): Übersicht direkt laden.
document
.querySelector<HTMLElement>('#panel-tabs [data-mode="wiki"]')!
.addEventListener("click", () => {
if (wikiView.empty()) void openWiki("tag:");
});
await listen<string>("panel-update", (e) => {
view.set(e.payload);
mode.to("draft");
onIncoming?.();
});
await listen<string>("commands-update", (e) => {
cmdView.set(e.payload);
mode.to("commands");
onIncoming?.();
});
await listen<string>("search-update", (e) => {
searchView.set(e.payload);
mode.to("search");
onIncoming?.();
});
await listen<string>("wiki-update", (e) => {
wikiView.set(e.payload);
mode.to("wiki");
onIncoming?.();
});
return { view, cmdView, mode, draft };
}