Technische ID als Identität der Notizen; Pfad wird zur Eigenschaft
Bisher war der Dateipfad die Identität einer Notiz: Umbenennen brach Verweise, und der Pfad tauchte in der Oberfläche auf (Tooltips, Menüpunkte "Dateiname ändern"/"Ordnerpfad ändern"). Jede Notiz trägt jetzt eine technische ID im Frontmatter (id: <uuid>), die nie wechselt; der Systempfad ist nur noch eine Eigenschaft. - ensure_ids ergänzt fehlende IDs beim Seitenaufbau (wie ensure_node_texts); frontmatter() schreibt sie bei jedem Anlegen als erste Zeile. - Doc, WikiDocEntry, FolderNode und Suchtreffer führen die ID mit; resolve_id löst sie auf den aktuellen relpath auf. - Commands sprechen IDs: archive_read/write/set_title/delete nehmen `id`, archive_create_doc/create_folder die ID des Elternknotens plus Namen, panel_archive_cmd den Zielknoten als ID. - Frontend adressiert durchgehend über IDs: Auswahl, Klapp-Zustand, Verlauf des Zurück-Knopfs, Wikilink-Sprünge, Suchtreffer-Vormerkung und der Zielordner-Baum des Archiv-Dialogs. - Der Pfad ist aus der Oberfläche verschwunden: die Menüpunkte zum Umbenennen von Datei und Ordner samt Commands (archive_rename, archive_move, archive_move_folder) sind entfallen — es gibt nur noch den Titel, Name und Titel können nicht mehr auseinanderlaufen. Offen dadurch: Verschieben im Baum fehlt (ToDo: Drag & Drop). Archiv-Dialog nebenbei überarbeitet: modales Popup im Design der Archiv-Ansicht mit Abbrechen und zuverlässigem Escape, Zielordner über einen einklappbaren Baum statt Pfadeingabe, zusätzlicher Knopf "Auf Platte legen" (panel_save_as); Archivieren öffnet keinen Dateimanager mehr.
This commit is contained in:
+86
-32
@@ -527,6 +527,7 @@ pub fn wiki_open(project: String, name: String) -> Result<(), String> {
|
||||
Some(tag) => {
|
||||
let display = crate::domain::project::display_name_in(&Paths::real(), &project)?;
|
||||
crate::domain::archive_ops::ensure_node_texts(&home, &display)?;
|
||||
crate::domain::archive_ops::ensure_ids(&home)?;
|
||||
let json = serde_json::to_string(&crate::domain::archive_index::archive_page(
|
||||
&home,
|
||||
(!tag.is_empty()).then_some(tag),
|
||||
@@ -549,8 +550,9 @@ pub fn wiki_open(project: String, name: String) -> Result<(), String> {
|
||||
/// Liest den Body eines Archiv-Dokuments (ohne Frontmatter) — Inhalt der
|
||||
/// Notiz-Ansicht im Archiv-Tab.
|
||||
#[tauri::command]
|
||||
pub fn archive_read(project: String, relpath: String) -> Result<String, String> {
|
||||
pub fn archive_read(project: String, id: String) -> Result<String, String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let relpath = crate::domain::archive_index::resolve_id(&home, &id)?;
|
||||
let path = crate::domain::archive_ops::doc_path(&home, &relpath)?;
|
||||
let text = std::fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
Ok(crate::domain::archive::strip_frontmatter(&text).to_string())
|
||||
@@ -559,19 +561,60 @@ pub fn archive_read(project: String, relpath: String) -> Result<String, String>
|
||||
/// Schreibt den Body einer Archiv-Notiz zurück (Bearbeiten im
|
||||
/// Archiv-Fenster); die Frontmatter der Datei bleibt.
|
||||
#[tauri::command]
|
||||
pub fn archive_write(project: String, relpath: String, text: String) -> Result<(), String> {
|
||||
pub fn archive_write(project: String, id: String, text: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let relpath = crate::domain::archive_index::resolve_id(&home, &id)?;
|
||||
let path = crate::domain::archive_ops::doc_path(&home, &relpath)?;
|
||||
crate::domain::archive_ops::write_body(&path, &text)
|
||||
}
|
||||
|
||||
/// Ordner-Knoten des Archivs (Pfad + Titel, sortiert) — Zielordner-Baum des
|
||||
/// Archiv-Dialogs.
|
||||
#[tauri::command]
|
||||
pub fn archive_folders(
|
||||
project: String,
|
||||
) -> Result<Vec<crate::domain::archive_index::FolderNode>, String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
crate::domain::archive_index::folder_nodes(&home)
|
||||
}
|
||||
|
||||
/// Legt den Entwurf als Datei an einem frei gewählten Pfad ab (Speichern-
|
||||
/// Dialog) — ohne Archiv, ohne Frontmatter: der Panel-Inhalt, wie er ist.
|
||||
#[tauri::command]
|
||||
pub fn panel_save_as(project: String, path: String) -> Result<(), String> {
|
||||
let text = std::fs::read_to_string(panel_file(&project)).map_err(|e| e.to_string())?;
|
||||
std::fs::write(&path, text).map_err(|e| format!("{path}: {e}"))
|
||||
}
|
||||
|
||||
/// Setzt den Anzeige-Titel einer Notiz (Klick auf den Titel im Archiv);
|
||||
/// danach die frische Übersicht, damit Baum und Karten den neuen Titel zeigen.
|
||||
#[tauri::command]
|
||||
pub fn archive_set_title(project: String, relpath: String, title: String) -> Result<(), String> {
|
||||
pub fn archive_set_title(project: String, id: String, title: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let relpath = crate::domain::archive_index::resolve_id(&home, &id)?;
|
||||
let path = crate::domain::archive_ops::doc_path(&home, &relpath)?;
|
||||
crate::domain::archive_ops::set_title(&path, &title)?;
|
||||
// Der technische Name folgt dem Titel — beide dürfen nicht auseinander
|
||||
// laufen. Bei einem Knotentext wandert der gleichnamige Ordner mit; die
|
||||
// Archiv-Wurzel (index.md) behält ihren Namen, sie ist die Konvention.
|
||||
let stem = path.file_stem().unwrap_or_default().to_string_lossy().to_string();
|
||||
let name = crate::domain::archive::strip_stamp(&stem).to_string();
|
||||
let slug = crate::domain::archive::slugify(&title);
|
||||
if name != slug && name != "index" {
|
||||
let dir = path.with_file_name(&name);
|
||||
if dir.is_dir() {
|
||||
let old_rel = dir.strip_prefix(&home).unwrap().display().to_string();
|
||||
let new_rel = match old_rel.rsplit_once('/') {
|
||||
Some((parent, _)) => format!("{parent}/{slug}"),
|
||||
None => slug.clone(),
|
||||
};
|
||||
crate::domain::archive_ops::move_folder(&home, &old_rel, &new_rel)?;
|
||||
relink_folder(&project, &home.join(&old_rel), &home.join(&new_rel))?;
|
||||
} else {
|
||||
let new_rel = crate::domain::archive_ops::rename_doc(&home, &relpath, &title)?;
|
||||
relink(&project, &path, Some(&home.join(new_rel)))?;
|
||||
}
|
||||
}
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
@@ -580,57 +623,66 @@ pub fn archive_set_title(project: String, relpath: String, title: String) -> Res
|
||||
fn wiki_refresh_page(project: &str, home: &std::path::Path) -> Result<(), String> {
|
||||
let display = crate::domain::project::display_name_in(&Paths::real(), project)?;
|
||||
crate::domain::archive_ops::ensure_node_texts(home, &display)?;
|
||||
crate::domain::archive_ops::ensure_ids(home)?;
|
||||
let json = serde_json::to_string(&crate::domain::archive_index::archive_page(home, None)?)
|
||||
.map_err(|e| e.to_string())?;
|
||||
std::fs::write(wiki_file(project), json).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Verschiebt ein Archiv-Dokument in einen anderen Ordner (leer = Wurzel).
|
||||
#[tauri::command]
|
||||
pub fn archive_move(project: String, relpath: String, folder: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let old = home.join(&relpath);
|
||||
let new_rel = crate::domain::archive_ops::move_doc(&home, &relpath, &folder)?;
|
||||
relink(&project, &old, Some(&home.join(new_rel)))?;
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
/// Benennt ein Archiv-Dokument um (Slug hinter erhaltenem Zeitstempel).
|
||||
#[tauri::command]
|
||||
pub fn archive_rename(project: String, relpath: String, name: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let old = home.join(&relpath);
|
||||
let new_rel = crate::domain::archive_ops::rename_doc(&home, &relpath, &name)?;
|
||||
relink(&project, &old, Some(&home.join(new_rel)))?;
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
/// Löscht ein Archiv-Dokument; danach zeigt das Wiki die Übersicht.
|
||||
#[tauri::command]
|
||||
pub fn archive_delete(project: String, relpath: String) -> Result<(), String> {
|
||||
pub fn archive_delete(project: String, id: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let relpath = crate::domain::archive_index::resolve_id(&home, &id)?;
|
||||
crate::domain::archive_ops::delete_doc(&home, &relpath)?;
|
||||
relink(&project, &home.join(&relpath), None)?;
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
/// Verschiebt/benennt einen Archiv-Ordner um; danach Übersicht.
|
||||
#[tauri::command]
|
||||
pub fn archive_move_folder(project: String, folder: String, to: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
crate::domain::archive_ops::move_folder(&home, &folder, &to)?;
|
||||
relink_folder(&project, &home.join(&folder), &home.join(&to))?;
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
/// Legt einen Ordner im Archiv an (Plus im Baum); danach Übersicht.
|
||||
#[tauri::command]
|
||||
pub fn archive_create_folder(project: String, folder: String) -> Result<(), String> {
|
||||
pub fn archive_create_folder(project: String, parent: String, name: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let folder = join_under(&home, &parent, &name)?;
|
||||
crate::domain::archive_ops::create_folder(&home, &folder)?;
|
||||
let display = crate::domain::project::display_name_in(&Paths::real(), &project)?;
|
||||
crate::domain::archive_ops::ensure_node_texts(&home, &display)?;
|
||||
crate::domain::archive_ops::ensure_ids(&home)?;
|
||||
wiki_refresh_page(&project, &home)
|
||||
}
|
||||
|
||||
/// Pfad eines neuen Kindes unterhalb des Knotens `parent` (ID; leer =
|
||||
/// Wurzel) — der Name wird als Slug angehängt.
|
||||
fn join_under(
|
||||
home: &std::path::Path,
|
||||
parent: &str,
|
||||
name: &str,
|
||||
) -> Result<String, String> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err("Name fehlt".into());
|
||||
}
|
||||
let slug = crate::domain::archive::slugify(name);
|
||||
if parent.is_empty() {
|
||||
return Ok(slug);
|
||||
}
|
||||
// Der Zielordner ist der Ordner neben dem Knotentext des Elternteils.
|
||||
let rel = crate::domain::archive_index::resolve_id(home, parent)?;
|
||||
let stem = std::path::Path::new(&rel)
|
||||
.file_stem()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let dir = match rel.rsplit_once('/') {
|
||||
Some((head, _)) => format!("{head}/{stem}"),
|
||||
None => stem,
|
||||
};
|
||||
Ok(format!("{dir}/{slug}"))
|
||||
}
|
||||
|
||||
/// Legt ein leeres Dokument an (Plus im Listenkopf) und öffnet es im
|
||||
/// Dokument-Tab: leerer Dokument-Puffer plus Quell-Verknüpfung — das
|
||||
/// Getippte landet über `panel_set` in der Archiv-Datei. Die Übersicht
|
||||
@@ -638,9 +690,11 @@ pub fn archive_create_folder(project: String, folder: String) -> Result<(), Stri
|
||||
/// frisch (zwei konkurrierende Puffer-Events würden sonst um den aktiven Tab
|
||||
/// rennen).
|
||||
#[tauri::command]
|
||||
pub fn archive_create_doc(project: String, folder: String, name: String) -> Result<(), String> {
|
||||
pub fn archive_create_doc(project: String, parent: String, name: String) -> Result<(), String> {
|
||||
let home = crate::domain::archive::require_archive_home(&project)?;
|
||||
let display = crate::domain::project::display_name_in(&Paths::real(), &project)?;
|
||||
let rel_new = join_under(&home, &parent, &name)?;
|
||||
let folder = rel_new.rsplit_once('/').map(|(h, _)| h.to_string()).unwrap_or_default();
|
||||
let rel = crate::domain::archive_ops::create_doc(&home, &folder, &name, &display)?;
|
||||
let path = home.join(rel);
|
||||
std::fs::write(panel_file(&project), "").map_err(|e| e.to_string())?;
|
||||
|
||||
Reference in New Issue
Block a user