Files
ObsiGate/frontend/js/navfacets.js
T
bruno a50227849d
CI / lint (push) Successful in 2m46s
CI / security (push) Successful in 1m57s
CI / build (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
CI / test (push) Canceled after 1h50m30s
feat: onglet de navigation avec facettes, tris et retour Home complet #158
- 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
2026-10-02 12:27:27 -04:00

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;
}