feat: vault DIR — exclusions par vault et lecture seule déclarable (vault Spark)

This commit is contained in:
Spark
2026-11-03 14:09:12 -05:00
committed by bruno
parent 52dcbf3607
commit 3ba8b7b667
13 changed files with 545 additions and 46 deletions
+29 -18
View File
@@ -209,6 +209,17 @@ function _openSecretsMenu(x, y) {
}, 0);
}
/**
* Vault déclaré en lecture seule (DIR_N_READONLY, champ « readonly » de
* /api/vaults) : ses menus et boutons d'action ne doivent proposer aucune
* mutation (renommer, déplacer, supprimer…) — le backend les refuse en
* 403 de toute façon, l'UI le dit avant le clic.
*/
function vaultIsReadonly(vaultName) {
const v = (state.allVaults || []).find((x) => x.name === vaultName);
return !!(v && v.readonly);
}
/** Fichier partagé (créé ou reçu) → icône share-2 bleue, sinon icône usuelle. */
function sharedFileIconSync(sharedPaths, itemName, vaultName, itemPath) {
if (sharedPaths && sharedPaths.has(`${vaultName}::${itemPath}`)) return _sharedIcon();
@@ -356,11 +367,11 @@ async function refreshSidebarForContext() {
vaultItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
const isReadonly = false;
const isReadonly = vaultIsReadonly(v.name);
ContextMenuManager.show(e.clientX, e.clientY, v.name, '', 'vault', isReadonly);
});
attachTreeItemActionButton(vaultItem, v.name, "", "vault", false);
attachTreeItemLongPress(vaultItem, () => ({ vault: v.name, path: "", type: "vault", isReadonly: false }));
attachTreeItemActionButton(vaultItem, v.name, "", "vault", vaultIsReadonly(v.name));
attachTreeItemLongPress(vaultItem, () => ({ vault: v.name, path: "", type: "vault", isReadonly: vaultIsReadonly(v.name) }));
container.appendChild(vaultItem);
@@ -460,11 +471,11 @@ async function loadVaults() {
vaultItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
const isReadonly = false;
const isReadonly = vaultIsReadonly(v.name);
ContextMenuManager.show(e.clientX, e.clientY, v.name, '', 'vault', isReadonly);
});
attachTreeItemActionButton(vaultItem, v.name, "", "vault", false);
attachTreeItemLongPress(vaultItem, () => ({ vault: v.name, path: "", type: "vault", isReadonly: false }));
attachTreeItemActionButton(vaultItem, v.name, "", "vault", vaultIsReadonly(v.name));
attachTreeItemLongPress(vaultItem, () => ({ vault: v.name, path: "", type: "vault", isReadonly: vaultIsReadonly(v.name) }));
container.appendChild(vaultItem);
@@ -596,8 +607,8 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
// New item — create it
if (item.type === "directory") {
const dirItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path, "data-type": "directory" }, [icon("chevron-right", 14), icon("folder", 16), el("span", { class: "tree-item-text" }, [document.createTextNode(item.name)]), smallBadge(item.children_count)]);
attachTreeItemActionButton(dirItem, vaultName, item.path, "directory", false);
attachTreeItemLongPress(dirItem, () => ({ vault: vaultName, path: item.path, type: "directory", isReadonly: false }));
attachTreeItemActionButton(dirItem, vaultName, item.path, "directory", vaultIsReadonly(vaultName));
attachTreeItemLongPress(dirItem, () => ({ vault: vaultName, path: item.path, type: "directory", isReadonly: vaultIsReadonly(vaultName) }));
fragment.appendChild(dirItem);
const subContainer = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-${item.path}` });
@@ -624,14 +635,14 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
dirItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, "directory", false);
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, "directory", vaultIsReadonly(vaultName));
});
} else {
const fileIconName = getFileIcon(item.name);
const displayName = item.name.match(/\.md$/i) ? item.name.replace(/\.md$/i, "") : item.name;
const fileItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path, "data-type": "file" }, [sharedFileIconSync(sharedPaths, item.name, vaultName, item.path), el("span", { class: "tree-item-text" }, [document.createTextNode(displayName)])]);
attachTreeItemActionButton(fileItem, vaultName, item.path, "file", false);
attachTreeItemLongPress(fileItem, () => ({ vault: vaultName, path: item.path, type: "file", isReadonly: false }));
attachTreeItemActionButton(fileItem, vaultName, item.path, "file", vaultIsReadonly(vaultName));
attachTreeItemLongPress(fileItem, () => ({ vault: vaultName, path: item.path, type: "file", isReadonly: vaultIsReadonly(vaultName) }));
fileItem.addEventListener("click", () => {
scrollTreeItemIntoView(fileItem, false);
if (window.innerWidth <= 768) {
@@ -649,7 +660,7 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
fileItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, "file", false);
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, "file", vaultIsReadonly(vaultName));
});
fragment.appendChild(fileItem);
@@ -944,8 +955,8 @@ async function loadDirectory(vaultName, dirPath, container) {
if (item.type === "directory") {
const dirItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path, "data-type": "directory" }, [icon("chevron-right", 14), icon("folder", 16), el("span", { class: "tree-item-text" }, [document.createTextNode(item.name)]), smallBadge(item.children_count)]);
attachTreeItemActionButton(dirItem, vaultName, item.path, "directory", false);
attachTreeItemLongPress(dirItem, () => ({ vault: vaultName, path: item.path, type: "directory", isReadonly: false }));
attachTreeItemActionButton(dirItem, vaultName, item.path, "directory", vaultIsReadonly(vaultName));
attachTreeItemLongPress(dirItem, () => ({ vault: vaultName, path: item.path, type: "directory", isReadonly: vaultIsReadonly(vaultName) }));
fragment.appendChild(dirItem);
const subContainer = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-${item.path}` });
@@ -972,7 +983,7 @@ async function loadDirectory(vaultName, dirPath, container) {
dirItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
const isReadonly = false;
const isReadonly = vaultIsReadonly(vaultName);
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, 'directory', isReadonly);
});
} else {
@@ -980,8 +991,8 @@ async function loadDirectory(vaultName, dirPath, container) {
const displayName = item.name.match(/\.md$/i) ? item.name.replace(/\.md$/i, "") : item.name;
const isShared = sharedPaths.has(`${vaultName}::${item.path}`);
const fileItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": item.path, "data-type": "file" }, [isShared ? _sharedIcon() : icon(fileIconName, 16), el("span", { class: "tree-item-text" }, [document.createTextNode(displayName)])]);
attachTreeItemActionButton(fileItem, vaultName, item.path, "file", false);
attachTreeItemLongPress(fileItem, () => ({ vault: vaultName, path: item.path, type: "file", isReadonly: false }));
attachTreeItemActionButton(fileItem, vaultName, item.path, "file", vaultIsReadonly(vaultName));
attachTreeItemLongPress(fileItem, () => ({ vault: vaultName, path: item.path, type: "file", isReadonly: vaultIsReadonly(vaultName) }));
fileItem.addEventListener("click", () => {
scrollTreeItemIntoView(fileItem, false);
if (window.innerWidth <= 768) {
@@ -999,7 +1010,7 @@ async function loadDirectory(vaultName, dirPath, container) {
fileItem.addEventListener("contextmenu", (e) => {
e.preventDefault();
const isReadonly = false;
const isReadonly = vaultIsReadonly(vaultName);
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, 'file', isReadonly);
});