feat: gestion desktop des vaults & dossiers — retrait par menu contextuel et ajout en configuration #159
This commit is contained in:
@@ -39,6 +39,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
node tests/frontend/unit.test.mjs
|
node tests/frontend/unit.test.mjs
|
||||||
node tests/frontend/navfacets.test.mjs
|
node tests/frontend/navfacets.test.mjs
|
||||||
|
node tests/frontend/desktop-roots.test.mjs
|
||||||
node tests/frontend/image-viewer.test.mjs
|
node tests/frontend/image-viewer.test.mjs
|
||||||
node tests/frontend/pdf-viewer.test.mjs
|
node tests/frontend/pdf-viewer.test.mjs
|
||||||
node tests/frontend/forge-completion.test.mjs
|
node tests/frontend/forge-completion.test.mjs
|
||||||
|
|||||||
+13
-3
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
|
|||||||
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
> **En cours de développement** : les changements à venir sont listés dans la section
|
> **En cours de développement** : les changements à venir sont listés dans la section
|
||||||
> [Unreleased](#unreleased). La dernière version livrée est **2.49.4**.
|
> [Unreleased](#unreleased). La dernière version livrée est **2.50.0**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,11 +14,21 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [2.49.4] — 2026-10-02
|
## [2.50.0] — 2026-10-02
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
|
||||||
|
- **#159 — Gestion desktop des vaults & dossiers** : retrait d'un vault ou dossier
|
||||||
|
racine par menu contextuel (« Retirer de l'application », desktop uniquement,
|
||||||
|
confirmation — déregistration locale, aucun fichier supprimé) et nouvelle section
|
||||||
|
Configuration « 🖥️ Vaults & dossiers (Desktop) » avec listes, retrait par ligne et
|
||||||
|
ajout de vault / dossier racine (nouvelle commande `pick_folder`, sélecteur natif
|
||||||
|
sans effet de bord). La jump list des raccourcis suit les ajouts et retraits de
|
||||||
|
vaults.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [2.49.3] — 2026-10-02
|
## [2.49.4] — 2026-10-02
|
||||||
|
|
||||||
### Corrigé
|
### Corrigé
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
|
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
|
||||||
|
|
||||||
[]()
|
[]()
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 Changelog
|
||||||
|
|
||||||
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.49.4).
|
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.50.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Projet : ObsiGate | Version : 2.49.4 | Dernière mise à jour : Septembre 2026*
|
*Projet : ObsiGate | Version : 2.50.0 | Dernière mise à jour : Septembre 2026*
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
|
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
|
||||||
|
|
||||||
[]()
|
[]()
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://www.docker.com/)
|
[](https://www.docker.com/)
|
||||||
[](https://www.python.org/)
|
[](https://www.python.org/)
|
||||||
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
|
|||||||
|
|
||||||
## 📝 Changelog
|
## 📝 Changelog
|
||||||
|
|
||||||
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.49.4).
|
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.50.0).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Project: ObsiGate | Version: 2.49.4 | Last updated: September 2026*
|
*Project: ObsiGate | Version: 2.50.0 | Last updated: September 2026*
|
||||||
|
|||||||
Generated
+1
-1
@@ -2626,7 +2626,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.49.4"
|
version = "2.50.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.49.4"
|
version = "2.50.0"
|
||||||
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
||||||
authors = ["Bruno Charest"]
|
authors = ["Bruno Charest"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@ commands.allow = [
|
|||||||
"get_wizard_state",
|
"get_wizard_state",
|
||||||
"complete_wizard",
|
"complete_wizard",
|
||||||
"pick_vault_folder",
|
"pick_vault_folder",
|
||||||
|
"pick_folder",
|
||||||
"get_system_theme",
|
"get_system_theme",
|
||||||
"restart_backend",
|
"restart_backend",
|
||||||
"check_backend_health",
|
"check_backend_health",
|
||||||
|
|||||||
@@ -320,6 +320,18 @@ async fn pick_vault_folder(app: tauri::AppHandle) -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sélecteur de dossier générique, sans effet de bord (contrairement à
|
||||||
|
/// `pick_vault_folder` qui écrit `vault_path` + `wizard_done` pour le wizard).
|
||||||
|
/// Sert à l'ajout d'un dossier racine depuis la section Configuration (#159).
|
||||||
|
#[tauri::command]
|
||||||
|
async fn pick_folder(app: tauri::AppHandle) -> Result<String, String> {
|
||||||
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
match app.dialog().file().blocking_pick_folder() {
|
||||||
|
Some(p) => Ok(p.to_string()),
|
||||||
|
None => Err("No folder selected".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_wizard_state() -> bool {
|
fn get_wizard_state() -> bool {
|
||||||
load_config().wizard_done
|
load_config().wizard_done
|
||||||
@@ -343,6 +355,13 @@ fn get_system_theme(app: tauri::AppHandle) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// La jump list des raccourcis suit les vaults : à rafraîchir après tout
|
||||||
|
/// ajout/retrait de vault (#159). Best-effort — `update_jumplist` logge et n'échoue jamais.
|
||||||
|
fn refresh_jumplist() {
|
||||||
|
let vaults = load_config().vaults;
|
||||||
|
jumplist::update_jumplist(&jumplist::build_vault_args(&vaults));
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn add_vault(name: String, path: String) -> Result<(), String> {
|
fn add_vault(name: String, path: String) -> Result<(), String> {
|
||||||
let mut config = load_config();
|
let mut config = load_config();
|
||||||
@@ -350,6 +369,7 @@ fn add_vault(name: String, path: String) -> Result<(), String> {
|
|||||||
config.vaults.retain(|v| v.name != name);
|
config.vaults.retain(|v| v.name != name);
|
||||||
config.vaults.push(VaultConfig { name: name.clone(), path });
|
config.vaults.push(VaultConfig { name: name.clone(), path });
|
||||||
save_config(&config);
|
save_config(&config);
|
||||||
|
refresh_jumplist();
|
||||||
info!("Vault added: {} — restart backend to apply", name);
|
info!("Vault added: {} — restart backend to apply", name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -359,6 +379,7 @@ fn remove_vault(name: String) -> Result<(), String> {
|
|||||||
let mut config = load_config();
|
let mut config = load_config();
|
||||||
config.vaults.retain(|v| v.name != name);
|
config.vaults.retain(|v| v.name != name);
|
||||||
save_config(&config);
|
save_config(&config);
|
||||||
|
refresh_jumplist();
|
||||||
info!("Vault removed: {} — restart backend to apply", name);
|
info!("Vault removed: {} — restart backend to apply", name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -589,6 +610,7 @@ fn main() {
|
|||||||
get_wizard_state,
|
get_wizard_state,
|
||||||
complete_wizard,
|
complete_wizard,
|
||||||
pick_vault_folder,
|
pick_vault_folder,
|
||||||
|
pick_folder,
|
||||||
get_system_theme,
|
get_system_theme,
|
||||||
restart_backend,
|
restart_backend,
|
||||||
check_backend_health,
|
check_backend_health,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
||||||
"productName": "ObsiGate",
|
"productName": "ObsiGate",
|
||||||
"version": "2.49.4",
|
"version": "2.50.0",
|
||||||
"identifier": "com.obsigate.desktop",
|
"identifier": "com.obsigate.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../frontend",
|
"frontendDist": "../frontend",
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ commande**.
|
|||||||
| Icône de barre des tâches (tray) | ❌ | ✅ |
|
| Icône de barre des tâches (tray) | ❌ | ✅ |
|
||||||
| Auto-update | ❌ | ✅ (vérifie les releases Gitea) |
|
| Auto-update | ❌ | ✅ (vérifie les releases Gitea) |
|
||||||
| Mode hors-ligne | Limité | Complet (backend local) |
|
| Mode hors-ligne | Limité | Complet (backend local) |
|
||||||
|
| Gestion vaults & dossiers | Fichiers de config serveur | ✅ UI dédiée + menu contextuel |
|
||||||
|
|
||||||
|
### Gestion des vaults et dossiers (#159)
|
||||||
|
|
||||||
|
Deux surfaces, réservées à l'application desktop :
|
||||||
|
|
||||||
|
- **Menu contextuel** : clic droit sur un vault ou un dossier racine dans la
|
||||||
|
sidebar → « Retirer de l'application » (confirmation, déregistration de la
|
||||||
|
configuration locale — **aucun fichier n'est supprimé**), puis
|
||||||
|
redémarrage automatique du backend.
|
||||||
|
- **Configuration** : section « 🖥️ Vaults & dossiers (Desktop) » listant les
|
||||||
|
vaults et dossiers chargés, avec boutons « Ajouter un vault » (sélecteur de
|
||||||
|
dossier natif) et « Ajouter un dossier ».
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -1,6 +1,6 @@
|
|||||||
# ObsiGate — Roadmap
|
# ObsiGate — Roadmap
|
||||||
|
|
||||||
> **Version :** 2.49.4 | **Dernière mise à jour :** 2026-10-02
|
> **Version :** 2.50.0 | **Dernière mise à jour :** 2026-10-02
|
||||||
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||||
> vers les fonctionnalités livrées.
|
> vers les fonctionnalités livrées.
|
||||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
||||||
@@ -40,6 +40,16 @@
|
|||||||
- [ ] **Signature de code Windows** : **non retenue — décision confirmée le 2026-09-26** : livraison non signée + documentation SmartScreen (« Exécuter quand même »). Alternatives écartées sauf retour utilisateur : SignPath.io (OSS gratuit), Certum OSS, Azure Trusted Signing, certificat EV
|
- [ ] **Signature de code Windows** : **non retenue — décision confirmée le 2026-09-26** : livraison non signée + documentation SmartScreen (« Exécuter quand même »). Alternatives écartées sauf retour utilisateur : SignPath.io (OSS gratuit), Certum OSS, Azure Trusted Signing, certificat EV
|
||||||
- [ ] Exécuter les 6 tests E2E **manuels** — protocole documenté : [DESKTOP_E2E_CHECKLIST.md](./DESKTOP_E2E_CHECKLIST.md)
|
- [ ] Exécuter les 6 tests E2E **manuels** — protocole documenté : [DESKTOP_E2E_CHECKLIST.md](./DESKTOP_E2E_CHECKLIST.md)
|
||||||
|
|
||||||
|
### 159. Gestion desktop des vaults & dossiers — retrait par menu contextuel + ajout en configuration
|
||||||
|
|
||||||
|
- **Effort :** 0,5-1 jour | **Impact :** 🟢 | **Framework :** commandes Rust existantes (`add/remove/list_vaults|dirs`) + `ContextMenuManager`
|
||||||
|
- **Statut :** ✅ livré le 2026-10-02 (v2.50.0) — ouvert le 2026-10-02 (prérequis : BUG-104/BUG-105 livrés)
|
||||||
|
- **Reste à faire :**
|
||||||
|
- [x] Retrait d'un vault **ou dossier racine** par menu contextuel (entrée « Retirer de l'application », desktop uniquement, confirmation, déregistration seule)
|
||||||
|
- [x] Section Configuration : liste des vaults/dossiers desktop + ajout (bouton vault existant `pickAndAddVault` + nouveau sélecteur `pick_folder` sans effet de bord pour les dossiers)
|
||||||
|
- [x] Rafraîchissement de la jump list après chaque ajout/retrait
|
||||||
|
- [x] i18n FR/EN, tests (garde-fou ACL + unit), E2E, fiche features, release
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔵 En cours — Visionneuse & édition Excel (P0/P1/P2)
|
## 🔵 En cours — Visionneuse & édition Excel (P0/P1/P2)
|
||||||
@@ -271,6 +281,7 @@
|
|||||||
| 154 | Tableur — Refonte UI/UX (ruban groupé, onglets permanents, badges d'état, inspecteur droit, undo/redo) | 2.40.0→2.43.1 | [features/xlsx-ui-redesign.md](./features/xlsx-ui-redesign.md) |
|
| 154 | Tableur — Refonte UI/UX (ruban groupé, onglets permanents, badges d'état, inspecteur droit, undo/redo) | 2.40.0→2.43.1 | [features/xlsx-ui-redesign.md](./features/xlsx-ui-redesign.md) |
|
||||||
| 155 | Tableur — Menu contextuel (clic droit / appui long) & sélection type Excel | 2.44.0 | [features/xlsx-context-menu.md](./features/xlsx-context-menu.md) |
|
| 155 | Tableur — Menu contextuel (clic droit / appui long) & sélection type Excel | 2.44.0 | [features/xlsx-context-menu.md](./features/xlsx-context-menu.md) |
|
||||||
| 156 | Tableur — Complétude éditeur : presse-papiers/clavier (A5-A7), mise en forme (A8), décision calcul (A9), undo unifié (A10), export + recherche multi-feuilles (A11), concurrence optimiste (A12), cache méta (A13), outils IA `.xlsm`/`.csv` (A14) + BUG-096 → BUG-099 | 2.45.0 | [features/xlsx-editor-completeness.md](./features/xlsx-editor-completeness.md) |
|
| 156 | Tableur — Complétude éditeur : presse-papiers/clavier (A5-A7), mise en forme (A8), décision calcul (A9), undo unifié (A10), export + recherche multi-feuilles (A11), concurrence optimiste (A12), cache méta (A13), outils IA `.xlsm`/`.csv` (A14) + BUG-096 → BUG-099 | 2.45.0 | [features/xlsx-editor-completeness.md](./features/xlsx-editor-completeness.md) |
|
||||||
|
| 159 | Desktop — gestion des vaults & dossiers : retrait par menu contextuel + section Configuration (ajout vault/dossier racine) | 2.50.0 | [features/desktop-tauri.md](./features/desktop-tauri.md) |
|
||||||
| BUG-047 | Versionnage — source unique `VERSION` + bump SemVer automatique au commit (hooks + tag) | 2.3.0 | [DEVELOPMENT_AND_RELEASES.md](./DEVELOPMENT_AND_RELEASES.md) |
|
| BUG-047 | Versionnage — source unique `VERSION` + bump SemVer automatique au commit (hooks + tag) | 2.3.0 | [DEVELOPMENT_AND_RELEASES.md](./DEVELOPMENT_AND_RELEASES.md) |
|
||||||
| 90 | Barre d'actions du document — regroupement fonctionnel + spacers | 2.3.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
| 90 | Barre d'actions du document — regroupement fonctionnel + spacers | 2.3.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||||
| 89 | Drag & drop complet de fichiers/dossiers & intégration Assistant IA | 2.3.0 | [features/drag-and-drop-ai.md](./features/drag-and-drop-ai.md) |
|
| 89 | Drag & drop complet de fichiers/dossiers & intégration Assistant IA | 2.3.0 | [features/drag-and-drop-ai.md](./features/drag-and-drop-ai.md) |
|
||||||
|
|||||||
@@ -86,3 +86,24 @@
|
|||||||
- Python 3.11 embed — téléchargé depuis python.org
|
- Python 3.11 embed — téléchargé depuis python.org
|
||||||
- NSIS (Windows) — pour le générateur d'installateur `.exe`
|
- NSIS (Windows) — pour le générateur d'installateur `.exe`
|
||||||
- AppImageKit (Linux) — pour le packaging portable
|
- AppImageKit (Linux) — pour le packaging portable
|
||||||
|
|
||||||
|
## G. Gestion des vaults & dossiers — #159 — ✅ livré (2026-10-02, v2.50.0)
|
||||||
|
|
||||||
|
- [x] Retrait vault/dossier racine par menu contextuel (`ContextMenuManager`,
|
||||||
|
branche `vault`, gate `isTauriEnv()`) → `removeRoot()` dans `desktop.js` :
|
||||||
|
résolution vault/dossier via `list_vaults`/`list_dirs`, confirmation i18n,
|
||||||
|
`remove_vault`/`remove_dir` + `restart_backend` + reload — déregistration
|
||||||
|
seule, zéro suppression disque.
|
||||||
|
- [x] Section Configuration `cfg-desktop-roots` (+ entrée TOC i18n FR/EN) :
|
||||||
|
liste des roots injectés, retrait par ligne, ajout vault
|
||||||
|
(`pickAndAddVault`, existant) et ajout dossier (nouvelle commande Rust
|
||||||
|
`pick_folder` — sélecteur sans effet de bord, contrairement à
|
||||||
|
`pick_vault_folder` réservé au wizard). Section masquée hors desktop.
|
||||||
|
- [x] Jump list rafraîchie après chaque ajout/retrait de vault
|
||||||
|
(`refresh_jumplist()`).
|
||||||
|
- [x] i18n FR/EN (7 clés `config.*`/`desktop.*`), garde-fou ACL automatique
|
||||||
|
(`test_frontend_invokes_are_acl_allowed` : toute commande invoquée par le
|
||||||
|
frontend doit figurer dans `permissions/commands.toml`).
|
||||||
|
- [x] Tests : `tests/frontend/desktop-roots.test.mjs` (4 — helpers purs +
|
||||||
|
gating hors desktop), inscrit au CI ; suites frontend/JSDOM vertes,
|
||||||
|
`cargo test` 25 passed.
|
||||||
|
|||||||
@@ -1587,6 +1587,7 @@
|
|||||||
<li><a href="#cfg-recent" class="help-nav-link" data-i18n="config.section_recent"></a></li>
|
<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-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-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-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-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>
|
<li><a href="#cfg-diags" class="help-nav-link" data-i18n="settings.diagnostics"></a></li>
|
||||||
@@ -1950,6 +1951,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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 -->
|
<!-- Watcher / Synchronisation -->
|
||||||
<section
|
<section
|
||||||
class="config-section help-section"
|
class="config-section help-section"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { escapeHtml, safeCreateIcons } from './utils.js';
|
|||||||
import { showToast, closeHeaderMenu, closeMobileSidebar } from './ui.js';
|
import { showToast, closeHeaderMenu, closeMobileSidebar } from './ui.js';
|
||||||
import { t, setLocale, getLocale } from './i18n.js';
|
import { t, setLocale, getLocale } from './i18n.js';
|
||||||
import { getModelCapabilities, renderCapabilityBadges, refreshAIPickers } from './ai.js';
|
import { getModelCapabilities, renderCapabilityBadges, refreshAIPickers } from './ai.js';
|
||||||
|
import { isTauriEnv, invoke, pickAndAddVault, pickAndAddDir, removeRoot } from './desktop.js';
|
||||||
|
|
||||||
let _recentTimestampTimer = null;
|
let _recentTimestampTimer = null;
|
||||||
let _recentFilesCache = [];
|
let _recentFilesCache = [];
|
||||||
@@ -776,6 +777,49 @@ function closeHelpModal() {
|
|||||||
if (modal) modal.classList.remove("active");
|
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() {
|
function initConfigModal() {
|
||||||
const openBtn = document.getElementById("config-open-btn");
|
const openBtn = document.getElementById("config-open-btn");
|
||||||
const closeBtn = document.getElementById("config-close");
|
const closeBtn = document.getElementById("config-close");
|
||||||
@@ -806,6 +850,7 @@ function initConfigModal() {
|
|||||||
loadTokensUI();
|
loadTokensUI();
|
||||||
loadSharesUI();
|
loadSharesUI();
|
||||||
loadToolKeys();
|
loadToolKeys();
|
||||||
|
renderDesktopRoots();
|
||||||
safeCreateIcons();
|
safeCreateIcons();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+52
-2
@@ -163,14 +163,64 @@ export async function pickAndAddVault() {
|
|||||||
if (!path) return undefined;
|
if (!path) return undefined;
|
||||||
|
|
||||||
// Derive a display name from the final path segment.
|
// Derive a display name from the final path segment.
|
||||||
const segments = path.replace(/[\\/]+$/, '').split(/[\\/]/);
|
const name = lastPathSegment(path) || 'Vault';
|
||||||
const name = segments[segments.length - 1] || 'Vault';
|
|
||||||
|
|
||||||
await invoke('add_vault', { name: name, path: path });
|
await invoke('add_vault', { name: name, path: path });
|
||||||
await invoke('restart_backend');
|
await invoke('restart_backend');
|
||||||
return path;
|
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) ─────────────────────────────────────────────────
|
// ── First-run wizard (DOM) ─────────────────────────────────────────────────
|
||||||
// Shows a dismissible welcome banner on first desktop launch offering to pick
|
// Shows a dismissible welcome banner on first desktop launch offering to pick
|
||||||
// the user's Obsidian vault folder. Non-blocking; the default vault created
|
// 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 { DashboardConflictsWidget } from './dashboard.js';
|
||||||
import { t, tr } from './i18n.js';
|
import { t, tr } from './i18n.js';
|
||||||
import { toggleThemeMode } from './themes.js';
|
import { toggleThemeMode } from './themes.js';
|
||||||
|
import { isTauriEnv, removeRoot } from './desktop.js';
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Right Sidebar Manager
|
// Right Sidebar Manager
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1806,6 +1807,15 @@ export const ContextMenuManager = {
|
|||||||
this._addItem('file-plus', 'Nouveau fichier', () => this._createFile(), isReadonly);
|
this._addItem('file-plus', 'Nouveau fichier', () => this._createFile(), isReadonly);
|
||||||
this._addSeparator();
|
this._addSeparator();
|
||||||
this._addItem('brain', '🧠 BooksLM', () => { import('./bookslm.js').then(m => m.default.open(this._targetVault, '')); }, false);
|
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') {
|
} else if (type === 'directory') {
|
||||||
this._addItem('folder-plus', 'Nouveau sous-dossier', () => this._createDirectory(), isReadonly);
|
this._addItem('folder-plus', 'Nouveau sous-dossier', () => this._createDirectory(), isReadonly);
|
||||||
this._addItem('file-plus', 'Nouveau fichier ici', () => this._createFile(), 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_format-du-payload": "Format du payload",
|
||||||
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
||||||
"config.section_hidden": "🗂️ Hidden files",
|
"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_historique-recent-redemarrage-non-requis": "📋 Recent History\n No restart required",
|
||||||
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
||||||
"config.section_intelligence-artificielle-dans-l-editeur": "🤖 AI in the Editor",
|
"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_format-du-payload": "Format du payload",
|
||||||
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
"config.section_gestion-des-onglets": "📑 Gestion des onglets",
|
||||||
"config.section_hidden": "🗂️ Fichiers cachés",
|
"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_historique-recent-redemarrage-non-requis": "📋 Historique récent\n Redémarrage non requis",
|
||||||
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
"config.section_indicateurs-visuels": "Indicateurs visuels",
|
||||||
"config.section_intelligence-artificielle-dans-l-editeur": "🤖 Intelligence Artificielle dans l'Éditeur",
|
"config.section_intelligence-artificielle-dans-l-editeur": "🤖 Intelligence Artificielle dans l'Éditeur",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsigate",
|
"name": "obsigate",
|
||||||
"version": "2.49.4",
|
"version": "2.50.0",
|
||||||
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
|
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
|
||||||
"main": "patch.js",
|
"main": "patch.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Tests unitaires — gestion desktop des vaults & dossiers (#159)
|
||||||
|
// Helpers purs de frontend/js/desktop.js (module sans import, importable en Node).
|
||||||
|
//
|
||||||
|
// Usage: node tests/frontend/desktop-roots.test.mjs
|
||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import {
|
||||||
|
resolveRootKind,
|
||||||
|
lastPathSegment,
|
||||||
|
removeRoot,
|
||||||
|
pickAndAddDir,
|
||||||
|
} from '../../frontend/js/desktop.js';
|
||||||
|
|
||||||
|
const vaults = [
|
||||||
|
{ name: 'Obsidian', path: 'C:/Users/x/voute_obsidian' },
|
||||||
|
{ name: 'Work', path: 'D:/work' },
|
||||||
|
];
|
||||||
|
const dirs = [{ name: 'Bruno-Download', path: 'C:/Users/x/Downloads' }];
|
||||||
|
|
||||||
|
function testResolveRootKind() {
|
||||||
|
assert.equal(resolveRootKind(vaults, dirs, 'Obsidian'), 'vault');
|
||||||
|
assert.equal(resolveRootKind(vaults, dirs, 'Bruno-Download'), 'dir');
|
||||||
|
assert.equal(resolveRootKind(vaults, dirs, 'inconnu'), null);
|
||||||
|
// Listes absentes (invoke en échec → undefined) : pas de crash.
|
||||||
|
assert.equal(resolveRootKind(undefined, undefined, 'Obsidian'), null);
|
||||||
|
// Un nom ne peut être que vault OU dir (les deux listes sont disjointes).
|
||||||
|
assert.equal(resolveRootKind(vaults, dirs, 'Work'), 'vault');
|
||||||
|
console.log(' ✓ resolveRootKind');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLastPathSegment() {
|
||||||
|
assert.equal(lastPathSegment('C:\\Obsidian_doc\\Obsidian-Sandbox'), 'Obsidian-Sandbox');
|
||||||
|
assert.equal(lastPathSegment('C:/vaults/perso/'), 'perso');
|
||||||
|
assert.equal(lastPathSegment('/home/bruno/vault'), 'vault');
|
||||||
|
assert.equal(lastPathSegment('/'), '');
|
||||||
|
assert.equal(lastPathSegment('C:\\'), 'C:');
|
||||||
|
console.log(' ✓ lastPathSegment');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testDesktopGating() {
|
||||||
|
// Hors desktop (pas de window.__TAURI__) : tout est un no-op silencieux —
|
||||||
|
// c'est ce qui protège l'app web (Docker) où la section est masquée.
|
||||||
|
assert.equal(await removeRoot('Obsidian', 'confirmer ?'), undefined);
|
||||||
|
assert.equal(await pickAndAddDir(), undefined);
|
||||||
|
assert.equal(await removeRoot('', 'x'), undefined);
|
||||||
|
console.log(' ✓ gating hors desktop');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Le nom dérivé par pickAndAddDir/pickAndAddVault doit être un segment simple
|
||||||
|
// (jamais un chemin complet) : réutilisé telle quelle par add_dir/add_vault.
|
||||||
|
function testDerivedNameIsBasename() {
|
||||||
|
for (const p of ['C:\\Obsidian_doc\\Obsidian-Sandbox', '/home/bruno/Downloads/']) {
|
||||||
|
const name = lastPathSegment(p) || 'Dossier';
|
||||||
|
assert.ok(!name.includes('/') && !name.includes('\\'), `basename expected for ${p}`);
|
||||||
|
}
|
||||||
|
console.log(' ✓ derived name is a basename');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
testResolveRootKind,
|
||||||
|
testLastPathSegment,
|
||||||
|
testDesktopGating,
|
||||||
|
testDerivedNameIsBasename,
|
||||||
|
];
|
||||||
|
|
||||||
|
let failed = 0;
|
||||||
|
for (const fn of tests) {
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
} catch (e) {
|
||||||
|
failed++;
|
||||||
|
console.error(` ✗ ${fn.name}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(failed === 0 ? `${tests.length} passed, 0 failed` : `${tests.length - failed} passed, ${failed} failed`);
|
||||||
|
process.exit(failed === 0 ? 0 : 1);
|
||||||
Reference in New Issue
Block a user