- clic sur un repertoire de l'arbre (modes focus vault et All) ouvre un onglet de navigation dedie qui coexiste avec les onglets de fichiers ouverts
- vue: sous-repertoires cliquables, facettes Vaults/Tags/Extensions (meme mecanisme que la recherche), tris Pertinence/Date, bouton Sauver; tags indexes exposes par GET /api/vault/{v}/files
- BUG-100: showWelcome re-injecte la section Raccourcis & Astuces et goHome efface la recherche globale + le filtre de la sidebar
78 lines
2.6 KiB
JavaScript
78 lines
2.6 KiB
JavaScript
/**
|
|
* Pure helpers for the navigation page facets / filters (#158).
|
|
*
|
|
* No DOM, no imports — usable straight from Node tests.
|
|
* Mirrors the search page's facet mechanism (Vaults / Tags / Extensions)
|
|
* over a plain directory listing instead of a search result set.
|
|
*/
|
|
|
|
/** Strip the leading dot and lowercase a file extension. */
|
|
function normExt(extension) {
|
|
return String(extension || "").toLowerCase().replace(/^\./, "");
|
|
}
|
|
|
|
/** Display name used for labels and alphabetical sorting (.md removed). */
|
|
export function navDisplayName(file) {
|
|
const name = file.name || String(file.path || "").split("/").pop() || "";
|
|
return name.endsWith(".md") ? name.slice(0, -3) : name;
|
|
}
|
|
|
|
/**
|
|
* Count vaults / tags / extensions over a file listing.
|
|
* @param {Array<object>} files
|
|
* @returns {{vaults: object, tags: object, extensions: object}} counts sorted desc
|
|
*/
|
|
export function buildNavFacets(files) {
|
|
const facets = { vaults: {}, tags: {}, extensions: {} };
|
|
for (const f of files || []) {
|
|
if (f.vault) facets.vaults[f.vault] = (facets.vaults[f.vault] || 0) + 1;
|
|
const ext = normExt(f.extension);
|
|
if (ext) facets.extensions[ext] = (facets.extensions[ext] || 0) + 1;
|
|
for (const tag of f.tags || []) {
|
|
facets.tags[tag] = (facets.tags[tag] || 0) + 1;
|
|
}
|
|
}
|
|
for (const key of Object.keys(facets)) {
|
|
facets[key] = Object.fromEntries(
|
|
Object.entries(facets[key]).sort((a, b) => b[1] - a[1]),
|
|
);
|
|
}
|
|
return facets;
|
|
}
|
|
|
|
/**
|
|
* Keep only the files matching the active tag / extension filters.
|
|
* @param {Array<object>} files
|
|
* @param {{tag?: string, ext?: string}} filters
|
|
*/
|
|
export function filterNavFiles(files, filters) {
|
|
const tag = (filters && filters.tag) || "";
|
|
const ext = normExt((filters && filters.ext) || "");
|
|
if (!tag && !ext) return files || [];
|
|
return (files || []).filter((f) => {
|
|
if (ext && normExt(f.extension) !== ext) return false;
|
|
if (tag && !(f.tags || []).includes(tag)) return false;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sort the listing: `modified` (newest first, server default) or
|
|
* `relevance` (A→Z).
|
|
*
|
|
* ponytail: a directory listing carries no relevance signal, `relevance` is an
|
|
* alphabetical stand-in — upgrade path: sort by scoped search score once the
|
|
* navigation page owns a query.
|
|
*/
|
|
export function sortNavFiles(files, mode) {
|
|
const out = (files || []).slice();
|
|
if (mode === "relevance") {
|
|
out.sort((a, b) =>
|
|
navDisplayName(a).localeCompare(navDisplayName(b), undefined, { sensitivity: "base" }),
|
|
);
|
|
} else {
|
|
out.sort((a, b) => (b.modified || 0) - (a.modified || 0));
|
|
}
|
|
return out;
|
|
}
|