|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
import { state } from './state.js';
|
|
|
|
|
import { safeCreateIcons, getFileIcon, flushIcons } from './utils.js';
|
|
|
|
|
import { api } from './auth.js';
|
|
|
|
|
import { api, AuthManager } from './auth.js';
|
|
|
|
|
import { populateCustomDropdown, TabManager, closeMobileSidebar, ContextMenuManager } from './ui.js';
|
|
|
|
|
import { _populateRecentVaultFilter, switchSidebarTab, filterAIHistory, filterRecentFiles } from './config.js';
|
|
|
|
|
import { el, icon, getVaultIcon, smallBadge, attachTreeItemActionButton, attachTreeItemLongPress, showWelcome, appendHighlightedText, filterSavedSearches } from './viewer.js';
|
|
|
|
@@ -8,6 +8,47 @@ import { performAdvancedSearch } from './search.js';
|
|
|
|
|
import { t } from './i18n.js';
|
|
|
|
|
import { filterChatMessages as _filterChat } from './filechat.js';
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Shared-file decoration (#196) : fichier porteur d'un partage actif →
|
|
|
|
|
// icône « share » dans l'arborescence ; dossier virtuel « Partage » dans le
|
|
|
|
|
// home du destinataire.
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
let _sharedPaths = null; // Promise<Set<"vault::path">> — parts actives (créées ou reçues)
|
|
|
|
|
|
|
|
|
|
function loadSharedPaths() {
|
|
|
|
|
if (!_sharedPaths) {
|
|
|
|
|
_sharedPaths = api("/api/shares")
|
|
|
|
|
.then(shares => new Set(shares.map(s => `${s.vault}::${s.path}`)))
|
|
|
|
|
.catch(() => new Set());
|
|
|
|
|
}
|
|
|
|
|
return _sharedPaths;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function invalidateSharedPaths() { _sharedPaths = null; }
|
|
|
|
|
|
|
|
|
|
/** Partages dirigés reçus par l'utilisateur courant (#196). */
|
|
|
|
|
async function _loadReceivedShares() {
|
|
|
|
|
const me = (AuthManager.getUser() || {}).username;
|
|
|
|
|
if (!me) return [];
|
|
|
|
|
try {
|
|
|
|
|
const shares = await api("/api/shares");
|
|
|
|
|
return shares.filter(s => s.created_by && s.created_by !== me);
|
|
|
|
|
} catch { return []; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Icône « partagé » (#196) — share-2 bleu. */
|
|
|
|
|
function _sharedIcon(size = 16) {
|
|
|
|
|
const i = icon("share-2", size);
|
|
|
|
|
i.style.color = "var(--accent-blue, #3b82f6)";
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 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();
|
|
|
|
|
return icon(getFileIcon(itemName), 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Vault context switching
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@@ -306,7 +347,45 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
|
|
|
|
|
|
|
|
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
|
|
|
|
|
|
// #196 : icône « partagé » — résout le cache avant le rendu.
|
|
|
|
|
const sharedPaths = await loadSharedPaths();
|
|
|
|
|
|
|
|
|
|
// #196 : dossier virtuel « Partage » à la racine du home du destinataire.
|
|
|
|
|
const items = Array.isArray(data.items) ? data.items.slice() : [];
|
|
|
|
|
const me = (AuthManager.getUser() || {}).username;
|
|
|
|
|
if (me && vaultName === `home-${me}` && !dirPath) {
|
|
|
|
|
const myShares = await _loadReceivedShares();
|
|
|
|
|
if (myShares.length) items.unshift({ type: "virtual-share", shares: myShares });
|
|
|
|
|
}
|
|
|
|
|
data.items = items;
|
|
|
|
|
|
|
|
|
|
data.items.forEach((item) => {
|
|
|
|
|
// #196 : dossier virtuel « Partage » (fichiers reçus via partage dirigé).
|
|
|
|
|
if (item.type === "virtual-share") {
|
|
|
|
|
const shareDir = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": "Partage", "data-type": "directory" }, [icon("chevron-right", 14), _sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(t("sidebar.shared_folder"))]), smallBadge(item.shares.length)]);
|
|
|
|
|
const shareSub = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-Partage` });
|
|
|
|
|
item.shares.forEach((s) => {
|
|
|
|
|
const name = (s.path || "").split("/").pop().replace(/\.md$/i, "");
|
|
|
|
|
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": `Partage/${s.path.split("/").pop()}`, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
|
|
|
|
|
fItem.title = t("dashboard.shared_by", { user: s.created_by || "" });
|
|
|
|
|
fItem.addEventListener("click", () => {
|
|
|
|
|
scrollTreeItemIntoView(fItem, false);
|
|
|
|
|
window.open(`/s/${s.token}`, "_blank");
|
|
|
|
|
});
|
|
|
|
|
shareSub.appendChild(fItem);
|
|
|
|
|
});
|
|
|
|
|
shareDir.addEventListener("click", () => {
|
|
|
|
|
TabManager.openNav(vaultName, "Partage");
|
|
|
|
|
const expanded = !shareSub.classList.contains("collapsed");
|
|
|
|
|
shareSub.classList.toggle("collapsed", expanded);
|
|
|
|
|
const chev = shareDir.querySelector("[data-lucide]");
|
|
|
|
|
if (chev) chev.setAttribute("data-lucide", expanded ? "chevron-right" : "chevron-down");
|
|
|
|
|
safeCreateIcons();
|
|
|
|
|
});
|
|
|
|
|
fragment.appendChild(shareDir);
|
|
|
|
|
fragment.appendChild(shareSub);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!shouldDisplayPath(item.path, vaultName)) return;
|
|
|
|
|
|
|
|
|
|
const existing = existingItems[item.path];
|
|
|
|
@@ -374,11 +453,11 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
|
|
|
|
|
ContextMenuManager.show(e.clientX, e.clientY, vaultName, item.path, "directory", false);
|
|
|
|
|
});
|
|
|
|
|
} 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" }, [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 }));
|
|
|
|
|
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 }));
|
|
|
|
|
fileItem.addEventListener("click", () => {
|
|
|
|
|
scrollTreeItemIntoView(fileItem, false);
|
|
|
|
|
if (window.innerWidth <= 768) {
|
|
|
|
@@ -629,9 +708,47 @@ async function loadDirectory(vaultName, dirPath, container) {
|
|
|
|
|
}
|
|
|
|
|
container.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
// #196 : icône « partagé » — résout le cache avant le rendu synchrone.
|
|
|
|
|
const sharedPaths = await loadSharedPaths();
|
|
|
|
|
|
|
|
|
|
// #196 : dossier virtuel « Partage » à la racine du home du destinataire.
|
|
|
|
|
const items = Array.isArray(data.items) ? data.items.slice() : [];
|
|
|
|
|
const me = (AuthManager.getUser() || {}).username;
|
|
|
|
|
if (me && vaultName === `home-${me}` && !dirPath) {
|
|
|
|
|
const myShares = await _loadReceivedShares();
|
|
|
|
|
if (myShares.length) items.unshift({ type: "virtual-share", shares: myShares });
|
|
|
|
|
}
|
|
|
|
|
data.items = items;
|
|
|
|
|
|
|
|
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
|
|
|
|
|
|
data.items.forEach((item) => {
|
|
|
|
|
// #196 : dossier virtuel « Partage » (fichiers reçus via partage dirigé).
|
|
|
|
|
if (item.type === "virtual-share") {
|
|
|
|
|
const shareDir = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": "Partage", "data-type": "directory" }, [icon("chevron-right", 14), _sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(t("sidebar.shared_folder"))]), smallBadge(item.shares.length)]);
|
|
|
|
|
const shareSub = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-Partage` });
|
|
|
|
|
item.shares.forEach((s) => {
|
|
|
|
|
const name = (s.path || "").split("/").pop().replace(/\.md$/i, "");
|
|
|
|
|
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": `Partage/${s.path.split("/").pop()}`, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
|
|
|
|
|
fItem.title = t("dashboard.shared_by", { user: s.created_by || "" });
|
|
|
|
|
fItem.addEventListener("click", () => {
|
|
|
|
|
scrollTreeItemIntoView(fItem, false);
|
|
|
|
|
window.open(`/s/${s.token}`, "_blank");
|
|
|
|
|
});
|
|
|
|
|
shareSub.appendChild(fItem);
|
|
|
|
|
});
|
|
|
|
|
shareDir.addEventListener("click", () => {
|
|
|
|
|
TabManager.openNav(vaultName, "Partage");
|
|
|
|
|
const expanded = !shareSub.classList.contains("collapsed");
|
|
|
|
|
shareSub.classList.toggle("collapsed", expanded);
|
|
|
|
|
const chev = shareDir.querySelector("[data-lucide]");
|
|
|
|
|
if (chev) chev.setAttribute("data-lucide", expanded ? "chevron-right" : "chevron-down");
|
|
|
|
|
safeCreateIcons();
|
|
|
|
|
});
|
|
|
|
|
fragment.appendChild(shareDir);
|
|
|
|
|
fragment.appendChild(shareSub);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Apply client-side filtering for hidden files
|
|
|
|
|
if (!shouldDisplayPath(item.path, vaultName)) {
|
|
|
|
|
return; // Skip this item
|
|
|
|
@@ -673,7 +790,8 @@ async function loadDirectory(vaultName, dirPath, container) {
|
|
|
|
|
} 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" }, [icon(fileIconName, 16), el("span", { class: "tree-item-text" }, [document.createTextNode(displayName)])]);
|
|
|
|
|
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 }));
|
|
|
|
|
fileItem.addEventListener("click", () => {
|
|
|
|
@@ -705,6 +823,7 @@ async function loadDirectory(vaultName, dirPath, container) {
|
|
|
|
|
safeCreateIcons();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Sidebar filter
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@@ -1133,4 +1252,4 @@ function searchByTag(tag) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export { initVaultContext, setSelectedVaultContext, syncVaultSelectors, shouldDisplayPath, loadVaults, loadVaultSettings, initSidebarFilter, TagFilterService, loadTags, scrollTreeItemIntoView, refreshSidebarForContext, focusVaultInSidebar, focusPathInSidebar, refreshTagsForContext, syncActiveFileTreeItem, searchByTag, addTagFilter, buildSearchResultsHeader, removeTagFilter };
|
|
|
|
|
export { initVaultContext, setSelectedVaultContext, syncVaultSelectors, shouldDisplayPath, loadVaults, loadVaultSettings, initSidebarFilter, TagFilterService, loadTags, scrollTreeItemIntoView, refreshSidebarForContext, focusVaultInSidebar, focusPathInSidebar, refreshTagsForContext, syncActiveFileTreeItem, searchByTag, addTagFilter, buildSearchResultsHeader, removeTagFilter, invalidateSharedPaths };
|
|
|
|
|