Trilium als Archiv: verwaltete Instanz pro Projekt, MD-Archiv und Dokument-Tab entfernt

- domain/trilium.rs: Runtime-Download (gepinnt 0.104.0), Instanz pro Projekt auf 127.0.0.1
  mit FNV-Port, ETAPI (archive_note, search_notes), Token im Keyring
- Setup bis aufs Passwort automatisiert: trilium_connect macht new-document (skipDemoDb),
  set-password und ETAPI-Login; Modul-Flag erst nach Token-Erfolg
- write_panel legt direkt eine Trilium-Notiz an (text|path, folder/description/tags);
  noteopen-Kanal, das Terminal-Fenster öffnet die Notiz (ersetzt archive_panel)
- MD-Altbestand raus: archive*/wiki*, archiveHome samt Permissions, panel_set/panel_load/
  wiki_open, panel_archive*/archive_docs/*_archive_home, reveal_path, spellcheck
- Dokument-Tab raus: Panel = ToDo/Befehle/Suche + Archiv-Knopf (trilium_open);
  panel-view/archive-form gelöscht, Markup/CSS/Messages bereinigt
- Lösch-Stufe Archiv: Instanz stoppen, Trilium-Datadir und Keyring-Token löschen
This commit is contained in:
marcus hinz
2026-07-24 21:42:03 +02:00
parent f58b9887c7
commit 47951a615b
64 changed files with 1454 additions and 4679 deletions
+104 -72
View File
@@ -3,9 +3,6 @@
use std::time::{Duration, Instant};
use crate::domain::archive::{
archive_panel_content, project_archive_home, require_archive_home, ArchiveMeta,
};
use crate::domain::credentials::keychain_service;
use crate::domain::paths::Paths;
use crate::domain::pool::{
@@ -350,11 +347,6 @@ pub(crate) fn set_terminal_font_size(size: u32) -> Result<(), String> {
settings::set_terminal_font_size_in(&Paths::real(), size)
}
#[tauri::command]
pub(crate) fn spellcheck_lang() -> String {
settings::spellcheck_lang(&Paths::real())
}
// ---------- Verbrauch ----------
#[tauri::command]
@@ -362,15 +354,6 @@ pub(crate) fn usage_stats(days: u32) -> Result<Vec<UsageRow>, String> {
usage_stats_in(&Paths::real(), days)
}
// ---------- Panel-Archiv ----------
/// Konfiguriertes Archiv-Home des Projekts (für den Panel-Button: entscheidet,
/// ob der Ordner-Dialog nötig ist).
#[tauri::command]
pub(crate) fn panel_archive_dir_cmd(project: String) -> Option<String> {
project_archive_home(&project).map(|p| p.display().to_string())
}
/// Modul-Registry für den Settings-Dialog (Checkbox-Gruppe „Module").
#[tauri::command]
pub(crate) fn module_registry(project: String) -> Result<Vec<crate::domain::modules::ModuleInfo>, String> {
@@ -383,78 +366,127 @@ pub(crate) fn set_module(project: String, module: String, enabled: bool) -> Resu
crate::domain::modules::set_module_in(&Paths::real(), &project, &module, enabled)
}
/// Aktive Module fürs Frontend: Registry-Enablement der Projekt-Config plus
/// `requires_archive` — ohne konfiguriertes Archiv-Home fallen Archiv-Module
/// weg, ihre Tabs erscheinen nicht.
/// Aktive Module fürs Frontend (Registry-Enablement der Projekt-Config).
#[tauri::command]
pub(crate) fn enabled_modules(project: String) -> Result<Vec<String>, String> {
let paths = Paths::real();
let has_archive = read_project_config_in(&paths, &project)?.archive_home.is_some();
Ok(
crate::domain::modules::active_in(&paths, &project)?
crate::domain::modules::active_in(&Paths::real(), &project)?
.iter()
.filter(|m| !m.requires_archive || has_archive)
.map(|m| m.id.to_string())
.collect(),
)
}
/// Setzt das Archiv-Home eines Projekts (Einstellungsdialog) — inklusive
/// Permissions-Eintrag in der Projekt-settings.json.
#[tauri::command]
pub(crate) fn set_archive_home_cmd(project: String, dir: String) -> Result<(), String> {
crate::domain::archive::set_project_archive_home(&project, &dir)
// ---------- Trilium ----------
/// Wizard-Status: Runtime da, Instanz erreichbar, Token gültig. Reine
/// Checks, keine Schreiber.
#[derive(serde::Serialize)]
pub(crate) struct TriliumStatus {
version: &'static str,
port: u16,
data_dir: String,
runtime_ready: bool,
instance_running: bool,
token_ok: bool,
}
fn trilium_status_for(project: &str) -> TriliumStatus {
let paths = Paths::real();
let instance_running = crate::domain::trilium::instance_running(project);
TriliumStatus {
version: crate::domain::trilium::VERSION,
port: crate::domain::trilium::port_for(project),
data_dir: crate::domain::paths::contract_home(
&paths,
&crate::domain::trilium::data_dir(&paths, project),
),
runtime_ready: crate::domain::trilium::runtime_ready(&paths),
instance_running,
token_ok: instance_running && crate::domain::trilium::token_ok(project),
}
}
/// Wechselt das Archiv-Home (Einstellungsdialog, wenn schon eins gesetzt
/// ist): optional ziehen die Dokumente mit um; die Rechte des alten Ordners
/// werden zurückgenommen.
#[tauri::command]
pub(crate) fn change_archive_home_cmd(
pub(crate) async fn trilium_status(project: String) -> TriliumStatus {
trilium_status_for(&project)
}
/// Richtet die Projekt-Instanz ein: Runtime laden (gepinnt, Checksum),
/// Instanz starten. Schreibt KEINE Config — das Modul-Flag setzt erst
/// trilium_connect nach erfolgreichem Token-Login.
#[tauri::command]
pub(crate) async fn trilium_setup(project: String) -> Result<TriliumStatus, String> {
crate::domain::trilium::ensure_instance(&Paths::real(), &project)?;
Ok(trilium_status_for(&project))
}
/// Richtet die Instanz mit dem Passwort aus dem Wizard vollständig ein:
/// Erstlauf legt das Dokument an und setzt das Passwort, dann ETAPI-Token
/// holen, im Keyring ablegen und ERST DANN das Modul in der Projekt-Config
/// freischalten. Das Passwort wird nicht gespeichert.
#[tauri::command]
pub(crate) async fn trilium_connect(
project: String,
dir: String,
migrate: bool,
password: String,
) -> Result<TriliumStatus, String> {
let paths = Paths::real();
crate::domain::trilium::ensure_instance(&paths, &project)?;
if !crate::domain::trilium::initialized(&project)? {
crate::domain::trilium::setup_new_document(&project)?;
crate::domain::trilium::set_password(&project, &password)?;
}
let token = crate::domain::trilium::login(&project, &password)?;
crate::domain::trilium::store_token(&project, &token)?;
if !crate::domain::trilium::token_ok(&project) {
crate::domain::trilium::delete_token(&project);
return Err("Token angenommen, aber ETAPI-Zugriff scheitert".into());
}
crate::domain::modules::set_module_in(&paths, &project, "trilium", true)?;
Ok(trilium_status_for(&project))
}
/// Öffnet das Trilium-Fenster des Projekts (startet die Instanz bei Bedarf);
/// `note_id` springt zur Notiz. Async wie open_panel_window (Fenster-Erzeugung
/// aus synchronen Commands klemmt auf dem GTK-Mainloop).
#[tauri::command]
pub(crate) async fn trilium_open(
app: tauri::AppHandle,
project: String,
note_id: Option<String>,
) -> Result<(), String> {
crate::domain::archive::change_project_archive_home(&project, &dir, migrate)
use tauri::Manager;
crate::domain::trilium::ensure_instance(&Paths::real(), &project)?;
let mut url = crate::domain::trilium::base_url(&project);
if let Some(id) = &note_id {
url.push_str(&format!("/#root/{id}"));
}
let label = format!("trilium-{project}");
if let Some(win) = app.get_webview_window(&label) {
if note_id.is_some() {
win
.eval(&format!("window.location.href = {}", serde_json::json!(url)))
.map_err(|e| e.to_string())?;
}
win.set_focus().map_err(|e| e.to_string())?;
return Ok(());
}
let parsed: tauri::Url = url.parse().map_err(|e| format!("{url}: {e}"))?;
tauri::WebviewWindowBuilder::new(&app, &label, tauri::WebviewUrl::External(parsed))
.title(format!("Trilium — {}", crate::domain::project::display_name_in(&Paths::real(), &project)?))
.inner_size(1100.0, 780.0)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
/// Wählt das Archiv ab (Einstellungsdialog): Config-Eintrag und Permissions
/// weg, der Ordner bleibt liegen.
/// Wählt das Modul ab und stoppt die Projekt-Instanz. Daten und Token
/// bleiben — erneutes Aktivieren verbindet sich wieder.
#[tauri::command]
pub(crate) fn clear_archive_home_cmd(project: String) -> Result<(), String> {
crate::domain::archive::clear_project_archive_home(&project)
pub(crate) async fn trilium_disable(project: String) -> Result<(), String> {
crate::domain::modules::set_module_in(&Paths::real(), &project, "trilium", false)?;
crate::domain::trilium::stop_instance(&project);
Ok(())
}
/// Archiviert den aktuellen Panel-Entwurf. `dir` (aus dem Ordner-Dialog) setzt
/// das Archiv-Home zugleich; ohne `dir` muss es konfiguriert sein. Ordner,
/// Beschreibung und Schlagwörter kommen aus dem Archiv-Formular im Panel.
#[tauri::command]
pub(crate) fn panel_archive_cmd(
project: String,
dir: Option<String>,
folder: Option<String>,
description: Option<String>,
tags: Option<Vec<String>>,
) -> Result<String, String> {
let meta = ArchiveMeta {
folder: folder.filter(|f| !f.trim().is_empty()),
description: description.filter(|d| !d.trim().is_empty()),
tags: tags.unwrap_or_default(),
};
archive_panel_content(&project, dir.as_deref(), &meta).map(|p| p.display().to_string())
}
/// Dokumentliste des Archiv-Index (frisch gescannt) fürs Panel.
#[tauri::command]
pub(crate) fn archive_docs_cmd(
project: String,
) -> Result<Vec<crate::domain::archive_index::Doc>, String> {
let home = require_archive_home(&project)?;
crate::domain::archive_index::scan_archive(&home)
}
/// Zeigt einen Pfad im Dateimanager (nach dem Archivieren „im Finder zeigen").
#[tauri::command]
pub(crate) fn reveal_path_cmd(path: String) {
crate::platform::reveal_path(std::path::Path::new(&path));
}