fix: tri naturel de la sidebar et police uniforme dossiers/fichiers

Les entrees a prefixe numerique etaient triees lexicographiquement (10 avant 2).
Passage a un tri naturel via Intl.Collator numeric. Les libelles de dossiers
adoptent desormais la meme taille et graisse que les fichiers (chevron seul
marqueur visuel).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 07:54:19 +02:00
parent 4bb2c6cc77
commit 1220ce1bb0
2 changed files with 58 additions and 24 deletions
+42 -19
View File
@@ -1,4 +1,5 @@
/* ── Reset ── */ /* ── Reset ── */
:root { --sidebar-width: 264px; }
*, *::before, *::after { box-sizing: border-box; } *, *::before, *::after { box-sizing: border-box; }
::selection { background: #ffc3c3; } ::selection { background: #ffc3c3; }
html, body { height: 100%; margin: 0; } html, body { height: 100%; margin: 0; }
@@ -91,6 +92,13 @@ body {
background: #1a1b2e; background: #1a1b2e;
} }
.home-logo {
width: 160px;
height: 160px;
object-fit: contain;
margin: 0 0 16px;
}
.home-title { .home-title {
color: #fff; color: #fff;
font-size: 4rem; font-size: 4rem;
@@ -206,7 +214,7 @@ body {
position: fixed; position: fixed;
top: 36px; top: 36px;
left: 0; left: 0;
width: 264px; width: var(--sidebar-width);
height: calc(100vh - 36px); height: calc(100vh - 36px);
background: #1a1b2e; background: #1a1b2e;
display: flex; display: flex;
@@ -215,6 +223,22 @@ body {
overflow: hidden; overflow: hidden;
} }
/* Poignée de redimensionnement */
.sidebar__resizer {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 100;
background: transparent;
transition: background 0.15s;
}
.sidebar__resizer:hover,
.sidebar__resizer.is-dragging { background: rgba(129, 140, 248, 0.4); }
body.is-resizing-sidebar { cursor: col-resize; user-select: none; }
.sidebar__scroll { .sidebar__scroll {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -315,10 +339,10 @@ body {
.nav-root-link { .nav-root-link {
display: block; display: block;
padding: 7px 24px; padding: 6px 24px;
color: rgba(255,255,255,0.85); color: rgba(255,255,255,0.85);
text-decoration: none; text-decoration: none;
font-size: 16px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 1.4; line-height: 1.4;
transition: color 0.2s; transition: color 0.2s;
@@ -358,33 +382,32 @@ body {
.nav-arrow:hover { opacity: 1; background: rgba(255,255,255,0.08); } .nav-arrow:hover { opacity: 1; background: rgba(255,255,255,0.08); }
.nav-folder[open] > summary .nav-arrow { transform: rotate(90deg); opacity: 0.8; } .nav-folder[open] > summary .nav-arrow { transform: rotate(90deg); opacity: 0.8; }
/* Les libellés de dossiers partagent la même police que les fichiers :
même taille (14px), même graisse (400), sans majuscules. Seule la flèche ▶
distingue visuellement un dossier d'un fichier. */
.nav-folder-link, .nav-folder-name { .nav-folder-link, .nav-folder-name {
flex: 1; flex: 1;
display: block; display: block;
padding: 6px 24px 6px 0; padding: 6px 24px 6px 0;
font-size: 11px; font-size: 14px;
font-weight: 700; font-weight: 400;
text-transform: uppercase; line-height: 1.4;
letter-spacing: 0.08em; color: rgba(255,255,255,0.85);
color: rgba(255,255,255,0.35);
text-decoration: none; text-decoration: none;
transition: color 0.2s; transition: color 0.2s;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.nav-folder-link:hover { color: rgba(255,255,255,0.85); } .nav-folder-link:hover { color: #ff5577; }
.nav-folder-link.active { color: #ff5577; } .nav-folder-link.active { color: #ff5577; }
/* Dossiers imbriqués : même style que les fichiers imbriqués (couleur atténuée). */
.nav-folder .nav-folder > summary .nav-folder-link, .nav-folder .nav-folder > summary .nav-folder-link,
.nav-folder .nav-folder > summary .nav-folder-name { .nav-folder .nav-folder > summary .nav-folder-name {
font-size: 13px; color: rgba(255,255,255,0.55);
font-weight: 500;
text-transform: none;
letter-spacing: 0;
color: rgba(255,255,255,0.45);
} }
.nav-folder .nav-folder > summary .nav-folder-link:hover { color: rgba(255,255,255,0.9); } .nav-folder .nav-folder > summary .nav-folder-link:hover { color: #ff5577; }
.nav-file-link { .nav-file-link {
display: block; display: block;
@@ -402,16 +425,16 @@ body {
font-weight: 500; font-weight: 500;
} }
.nav-depth-1 > .nav-file-link { padding: 5px 24px 5px 52px; } .nav-depth-1 > .nav-file-link { padding: 6px 24px 6px 52px; }
.nav-depth-1 > .nav-folder > summary { padding-left: 24px; } .nav-depth-1 > .nav-folder > summary { padding-left: 24px; }
.nav-depth-2 > .nav-file-link { padding: 4px 24px 4px 68px; } .nav-depth-2 > .nav-file-link { padding: 6px 24px 6px 68px; }
.nav-depth-2 > .nav-folder > summary { padding-left: 40px; } .nav-depth-2 > .nav-folder > summary { padding-left: 40px; }
.nav-depth-3 > .nav-file-link { padding: 4px 24px 4px 84px; } .nav-depth-3 > .nav-file-link { padding: 6px 24px 6px 84px; }
.nav-depth-3 > .nav-folder > summary { padding-left: 56px; } .nav-depth-3 > .nav-folder > summary { padding-left: 56px; }
/* ── Content ── */ /* ── Content ── */
.content { .content {
margin-left: 264px; margin-left: var(--sidebar-width);
padding-top: 48px; padding-top: 48px;
padding-right: 72px; padding-right: 72px;
padding-bottom: 96px; padding-bottom: 96px;
+16 -5
View File
@@ -1,5 +1,13 @@
import { escapeHtml } from '../utils.js'; import { escapeHtml } from '../utils.js';
// Tri « naturel » : les préfixes numériques sont comparés comme des nombres
// (10 vient après 9, pas après 1).
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
function cleanLabel(name) {
return name.replace(/\.md$/i, '').replace(/-/g, ' ');
}
function buildTree(relPaths, sep) { function buildTree(relPaths, sep) {
const tree = { _files: [], _dirs: {} }; const tree = { _files: [], _dirs: {} };
relPaths.forEach((rel) => { relPaths.forEach((rel) => {
@@ -19,8 +27,11 @@ function renderTree(node, prefix, sep, currentRel, depth) {
let html = ''; let html = '';
const depthClass = depth > 0 ? ` class="nav-depth-${Math.min(depth, 3)}"` : ''; const depthClass = depth > 0 ? ` class="nav-depth-${Math.min(depth, 3)}"` : '';
node._files.slice().sort().forEach((rel) => { node._files
const label = rel.split(sep).pop().replace(/\.md$/i, '').replace(/-/g, ' '); .slice()
.sort((a, b) => collator.compare(a.split(sep).pop(), b.split(sep).pop()))
.forEach((rel) => {
const label = cleanLabel(rel.split(sep).pop());
const abs = prefix + rel; const abs = prefix + rel;
const cls = rel === currentRel ? ' active' : ''; const cls = rel === currentRel ? ' active' : '';
if (depth === 0) { if (depth === 0) {
@@ -30,7 +41,7 @@ function renderTree(node, prefix, sep, currentRel, depth) {
} }
}); });
Object.keys(node._dirs).sort().forEach((dirName) => { Object.keys(node._dirs).sort((a, b) => collator.compare(a, b)).forEach((dirName) => {
const child = node._dirs[dirName]; const child = node._dirs[dirName];
const isOpen = currentRel && currentRel.split(sep).includes(dirName); const isOpen = currentRel && currentRel.split(sep).includes(dirName);
const openAttr = isOpen ? ' open' : ''; const openAttr = isOpen ? ' open' : '';
@@ -40,9 +51,9 @@ function renderTree(node, prefix, sep, currentRel, depth) {
if (homeFile) { if (homeFile) {
const abs = prefix + homeFile; const abs = prefix + homeFile;
const cls = homeFile === currentRel ? ' active' : ''; const cls = homeFile === currentRel ? ' active' : '';
folderLabel = `<a href="#" class="nav-folder-link${cls}" data-path="${escapeHtml(abs)}" onclick="event.stopPropagation()">${escapeHtml(dirName)}</a>`; folderLabel = `<a href="#" class="nav-folder-link${cls}" data-path="${escapeHtml(abs)}" onclick="event.stopPropagation()">${escapeHtml(cleanLabel(dirName))}</a>`;
} else { } else {
folderLabel = `<span class="nav-folder-name">${escapeHtml(dirName)}</span>`; folderLabel = `<span class="nav-folder-name">${escapeHtml(cleanLabel(dirName))}</span>`;
} }
const arrow = `<span class="nav-arrow" onclick="event.preventDefault();event.stopPropagation();var d=this.closest('details');d.open=!d.open">▶</span>`; const arrow = `<span class="nav-arrow" onclick="event.preventDefault();event.stopPropagation();var d=this.closest('details');d.open=!d.open">▶</span>`;