feat: gestion desktop des vaults & dossiers — retrait par menu contextuel et ajout en configuration #159
This commit is contained in:
@@ -1587,6 +1587,7 @@
|
||||
<li><a href="#cfg-recent" class="help-nav-link" data-i18n="config.section_recent"></a></li>
|
||||
<li><a href="#cfg-tags" class="help-nav-link" data-i18n="config.section_tags"></a></li>
|
||||
<li><a href="#cfg-hidden-files" class="help-nav-link" data-i18n="config.section_hidden"></a></li>
|
||||
<li><a href="#cfg-desktop-roots" class="help-nav-link" data-i18n="config.section_desktop"></a></li>
|
||||
<li><a href="#cfg-sync" class="help-nav-link" data-i18n="settings.sync"></a></li>
|
||||
<li><a href="#cfg-backend-settings" class="help-nav-link" data-i18n="config.section_backend"></a></li>
|
||||
<li><a href="#cfg-diags" class="help-nav-link" data-i18n="settings.diagnostics"></a></li>
|
||||
@@ -1950,6 +1951,45 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Desktop vaults & root directories (#159) -->
|
||||
<section
|
||||
id="cfg-desktop-roots"
|
||||
class="config-section help-section"
|
||||
>
|
||||
<h2 data-i18n="config.section_desktop">🖥️ Vaults & dossiers (Desktop)</h2>
|
||||
<p
|
||||
class="config-description"
|
||||
data-i18n="config.desktop_roots_desc"
|
||||
>
|
||||
Ajoutez ou retirez les vaults et dossiers
|
||||
racine chargés par l'application desktop.
|
||||
</p>
|
||||
|
||||
<div id="desktop-roots-list">
|
||||
<!-- Roots injected by the Tauri shell are injected here -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="config-actions-row"
|
||||
style="margin-top: 16px"
|
||||
>
|
||||
<button
|
||||
class="config-btn-save"
|
||||
id="cfg-desktop-add-vault"
|
||||
data-i18n="desktop.add_vault"
|
||||
>
|
||||
📁 Ajouter un vault
|
||||
</button>
|
||||
<button
|
||||
class="config-btn-save"
|
||||
id="cfg-desktop-add-dir"
|
||||
data-i18n="desktop.add_dir"
|
||||
>
|
||||
📂 Ajouter un dossier
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Watcher / Synchronisation -->
|
||||
<section
|
||||
class="config-section help-section"
|
||||
|
||||
@@ -7,6 +7,7 @@ import { escapeHtml, safeCreateIcons } from './utils.js';
|
||||
import { showToast, closeHeaderMenu, closeMobileSidebar } from './ui.js';
|
||||
import { t, setLocale, getLocale } from './i18n.js';
|
||||
import { getModelCapabilities, renderCapabilityBadges, refreshAIPickers } from './ai.js';
|
||||
import { isTauriEnv, invoke, pickAndAddVault, pickAndAddDir, removeRoot } from './desktop.js';
|
||||
|
||||
let _recentTimestampTimer = null;
|
||||
let _recentFilesCache = [];
|
||||
@@ -776,6 +777,49 @@ function closeHelpModal() {
|
||||
if (modal) modal.classList.remove("active");
|
||||
}
|
||||
|
||||
// ── Vaults & root directories — desktop only (#159) ──────────────────────
|
||||
// Lists the roots the Tauri shell injects into the backend (vaults +
|
||||
// directories) and offers add/remove. The whole section — including its TOC
|
||||
// entry — is hidden outside the desktop app.
|
||||
async function renderDesktopRoots() {
|
||||
const section = document.getElementById("cfg-desktop-roots");
|
||||
const navLink = document.querySelector('#config-nav a[href="#cfg-desktop-roots"]');
|
||||
if (!section) return;
|
||||
if (!isTauriEnv()) {
|
||||
section.style.display = "none";
|
||||
if (navLink && navLink.parentElement) navLink.parentElement.style.display = "none";
|
||||
return;
|
||||
}
|
||||
section.style.display = "";
|
||||
if (navLink && navLink.parentElement) navLink.parentElement.style.display = "";
|
||||
const vaults = (await invoke("list_vaults")) || [];
|
||||
const dirs = (await invoke("list_dirs")) || [];
|
||||
const list = document.getElementById("desktop-roots-list");
|
||||
if (!list) return;
|
||||
const row = (name, path, kind) =>
|
||||
'<div class="hidden-files-vault-card" style="display:flex;align-items:center;gap:10px;margin-bottom:8px">' +
|
||||
'<i data-lucide="' + (kind === "vault" ? "vault" : "folder") + '" style="width:16px;flex-shrink:0"></i>' +
|
||||
'<div style="flex:1;min-width:0"><div style="font-weight:600">' + escapeHtml(name) + "</div>" +
|
||||
'<div style="font-size:12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis">' + escapeHtml(path) + "</div></div>" +
|
||||
'<button class="config-btn-save" data-root="' + escapeHtml(name) + '" style="padding:4px 10px;font-size:12px;white-space:nowrap">' +
|
||||
'<i data-lucide="folder-minus" style="width:14px;vertical-align:-2px"></i> ' + escapeHtml(t("desktop.remove_root")) + "</button></div>";
|
||||
if (!vaults.length && !dirs.length) {
|
||||
list.innerHTML = '<div style="padding:12px;color:var(--text-muted)">' + escapeHtml(t("desktop.roots_empty")) + "</div>";
|
||||
} else {
|
||||
list.innerHTML = vaults.map((v) => row(v.name, v.path, "vault")).join("") +
|
||||
dirs.map((d) => row(d.name, d.path, "dir")).join("");
|
||||
for (const btn of list.querySelectorAll("button[data-root]")) {
|
||||
// onclick (not addEventListener): the modal re-renders on every open.
|
||||
btn.onclick = () => removeRoot(btn.dataset.root, t("desktop.remove_root_confirm", { name: btn.dataset.root }));
|
||||
}
|
||||
}
|
||||
const addVaultBtn = document.getElementById("cfg-desktop-add-vault");
|
||||
const addDirBtn = document.getElementById("cfg-desktop-add-dir");
|
||||
if (addVaultBtn) addVaultBtn.onclick = () => pickAndAddVault();
|
||||
if (addDirBtn) addDirBtn.onclick = () => pickAndAddDir();
|
||||
safeCreateIcons();
|
||||
}
|
||||
|
||||
function initConfigModal() {
|
||||
const openBtn = document.getElementById("config-open-btn");
|
||||
const closeBtn = document.getElementById("config-close");
|
||||
@@ -806,6 +850,7 @@ function initConfigModal() {
|
||||
loadTokensUI();
|
||||
loadSharesUI();
|
||||
loadToolKeys();
|
||||
renderDesktopRoots();
|
||||
safeCreateIcons();
|
||||
});
|
||||
|
||||
|
||||
+52
-2
@@ -163,14 +163,64 @@ export async function pickAndAddVault() {
|
||||
if (!path) return undefined;
|
||||
|
||||
// Derive a display name from the final path segment.
|
||||
const segments = path.replace(/[\\/]+$/, '').split(/[\\/]/);
|
||||
const name = segments[segments.length - 1] || 'Vault';
|
||||
const name = lastPathSegment(path) || 'Vault';
|
||||
|
||||
await invoke('add_vault', { name: name, path: path });
|
||||
await invoke('restart_backend');
|
||||
return path;
|
||||
}
|
||||
|
||||
// ── Vault & root-directory management (#159) ────────────────────────────
|
||||
// The Tauri shell injects vaults (VAULT_N_*) and root directories (DIR_N_*)
|
||||
// into the backend as environment variables. These helpers unregister them
|
||||
// from the desktop config, restart the backend and reload the page.
|
||||
|
||||
// Pure: classify a root name against the desktop config lists.
|
||||
// Returns 'vault' | 'dir' | null.
|
||||
export function resolveRootKind(vaults, dirs, name) {
|
||||
if ((vaults || []).some((v) => v.name === name)) return 'vault';
|
||||
if ((dirs || []).some((d) => d.name === name)) return 'dir';
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pure: last path segment, Windows or POSIX separators, trailing
|
||||
// separators ignored ('' for root-ish inputs).
|
||||
export function lastPathSegment(path) {
|
||||
const segments = String(path).replace(/[\\/]+$/, '').split(/[\\/]/);
|
||||
return segments[segments.length - 1] || '';
|
||||
}
|
||||
|
||||
// Remove a vault or a root directory from the desktop config. `confirmMsg`
|
||||
// (already translated) gates the destructive step; nothing on disk is
|
||||
// deleted. Returns 'vault' | 'dir', or undefined when not on desktop,
|
||||
// cancelled, unknown root, or a command failed.
|
||||
export async function removeRoot(name, confirmMsg) {
|
||||
if (!isTauriEnv() || !name) return undefined;
|
||||
if (confirmMsg && !window.confirm(confirmMsg)) return undefined;
|
||||
const vaults = (await invoke('list_vaults')) || [];
|
||||
const dirs = (await invoke('list_dirs')) || [];
|
||||
const kind = resolveRootKind(vaults, dirs, name);
|
||||
if (!kind) return undefined;
|
||||
const res = await invoke(kind === 'vault' ? 'remove_vault' : 'remove_dir', { name: name });
|
||||
if (res === undefined) return undefined; // command failed (see invoke wrapper)
|
||||
await invoke('restart_backend');
|
||||
window.location.reload();
|
||||
return kind;
|
||||
}
|
||||
|
||||
// Add a root directory: side-effect-free native folder picker, register it,
|
||||
// restart the backend and reload. Mirrors pickAndAddVault.
|
||||
export async function pickAndAddDir() {
|
||||
if (!isTauriEnv()) return undefined;
|
||||
const path = await invoke('pick_folder');
|
||||
if (typeof path !== 'string' || !path) return undefined;
|
||||
const name = lastPathSegment(path) || 'Dossier';
|
||||
await invoke('add_dir', { name: name, path: path });
|
||||
await invoke('restart_backend');
|
||||
window.location.reload();
|
||||
return path;
|
||||
}
|
||||
|
||||
// ── First-run wizard (DOM) ─────────────────────────────────────────────────
|
||||
// Shows a dismissible welcome banner on first desktop launch offering to pick
|
||||
// the user's Obsidian vault folder. Non-blocking; the default vault created
|
||||
|
||||
@@ -8,6 +8,7 @@ import { GraphViewManager } from './graph.js';
|
||||
import { DashboardConflictsWidget } from './dashboard.js';
|
||||
import { t, tr } from './i18n.js';
|
||||
import { toggleThemeMode } from './themes.js';
|
||||
import { isTauriEnv, removeRoot } from './desktop.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Right Sidebar Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1806,6 +1807,15 @@ export const ContextMenuManager = {
|
||||
this._addItem('file-plus', 'Nouveau fichier', () => this._createFile(), isReadonly);
|
||||
this._addSeparator();
|
||||
this._addItem('brain', '🧠 BooksLM', () => { import('./bookslm.js').then(m => m.default.open(this._targetVault, '')); }, false);
|
||||
// #159 — retrait d'un vault ou dossier racine : desktop uniquement,
|
||||
// déregistration seule (aucun fichier supprimé), confirmation exigée.
|
||||
if (isTauriEnv()) {
|
||||
this._addSeparator();
|
||||
this._addItem('folder-minus', t('desktop.remove_root'), () => {
|
||||
const name = this._targetVault;
|
||||
if (name) removeRoot(name, t('desktop.remove_root_confirm', { name: name }));
|
||||
}, false);
|
||||
}
|
||||
} else if (type === 'directory') {
|
||||
this._addItem('folder-plus', 'Nouveau sous-dossier', () => this._createDirectory(), isReadonly);
|
||||
this._addItem('file-plus', 'Nouveau fichier ici', () => this._createFile(), isReadonly);
|
||||
|
||||
@@ -512,6 +512,13 @@
|
||||
"config.section_format-du-payload": "Format du payload",
|
||||
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
||||
"config.section_hidden": "🗂️ Hidden files",
|
||||
"config.section_desktop": "🖥️ Vaults & folders (Desktop)",
|
||||
"config.desktop_roots_desc": "Add or remove the vaults and root folders loaded by the desktop application (local config removal only — no file is deleted).",
|
||||
"desktop.remove_root": "Remove from app",
|
||||
"desktop.remove_root_confirm": "Remove “{name}” from ObsiGate Desktop? No files will be deleted.",
|
||||
"desktop.add_vault": "📁 Add a vault",
|
||||
"desktop.add_dir": "📂 Add a folder",
|
||||
"desktop.roots_empty": "No vault or folder configured.",
|
||||
"config.section_historique-recent-redemarrage-non-requis": "📋 Recent History\n No restart required",
|
||||
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
||||
"config.section_intelligence-artificielle-dans-l-editeur": "🤖 AI in the Editor",
|
||||
|
||||
@@ -512,6 +512,13 @@
|
||||
"config.section_format-du-payload": "Format du payload",
|
||||
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
||||
"config.section_hidden": "🗂️ Fichiers cachés",
|
||||
"config.section_desktop": "🖥️ Vaults & dossiers (Desktop)",
|
||||
"config.desktop_roots_desc": "Ajoutez ou retirez les vaults et dossiers racine chargés par l'application desktop (retrait local de la configuration, aucun fichier supprimé).",
|
||||
"desktop.remove_root": "Retirer de l'application",
|
||||
"desktop.remove_root_confirm": "Retirer « {name} » de l'application ObsiGate Desktop ? Les fichiers ne seront pas supprimés.",
|
||||
"desktop.add_vault": "📁 Ajouter un vault",
|
||||
"desktop.add_dir": "📂 Ajouter un dossier",
|
||||
"desktop.roots_empty": "Aucun vault ou dossier configuré.",
|
||||
"config.section_historique-recent-redemarrage-non-requis": "📋 Historique récent\n Redémarrage non requis",
|
||||
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
||||
"config.section_intelligence-artificielle-dans-l-editeur": "🤖 Intelligence Artificielle dans l'Éditeur",
|
||||
|
||||
Reference in New Issue
Block a user