@@ -518,21 +518,65 @@ function buildTocFromTokens(tokens) {
518518 return toc .slice (0 , 60 );
519519}
520520
521+ function tryFixMojibakeUtf8 (s ) {
522+ const src = (s || " " ).toString ();
523+ if (! src) return src;
524+
525+ // Heuristique: si on voit "â" ou "Ã", c'est souvent un UTF-8 mal décodé
526+ const looksMojibake = src .includes (" â" ) || src .includes (" Ã" );
527+ if (! looksMojibake) return src;
528+
529+ try {
530+ // Reconstituer les bytes 0..255, puis décoder en UTF-8
531+ const bytes = Uint8Array .from (src, (ch ) => ch .charCodeAt (0 ) & 0xff );
532+ const fixed = new TextDecoder (" utf-8" , { fatal: false }).decode (bytes);
533+
534+ // Garde la version "fixed" seulement si elle réduit clairement le mojibake
535+ const badCount = (src .match (/ [âÃ] / g ) || []).length ;
536+ const fixedBadCount = (fixed .match (/ [âÃ] / g ) || []).length ;
537+
538+ return fixedBadCount < badCount ? fixed : src;
539+ } catch {
540+ return src;
541+ }
542+ }
543+
521544async function renderMarkdown (md , { tocOut = null } = {}) {
522- const source = (md || " " ).toString ();
545+ let source = (md || " " ).toString ();
523546 if (! source) return " " ;
524547
548+ // FIX: réparer le texte avant parsing
549+ source = tryFixMojibakeUtf8 (source);
550+
525551 const hl = await ensureHighlighter ();
526552 const renderer = new marked.Renderer ();
527553
528- renderer .heading = (text , level ) => {
554+ renderer .heading = (... args ) => {
555+ // marked peut appeler:
556+ // - heading(text, level, raw, slugger) (ancien)
557+ // - heading(token) (nouveau)
558+ let text = " " ;
559+ let level = 1 ;
560+
561+ const a0 = args[0 ];
562+
563+ if (typeof a0 === " string" ) {
564+ text = a0;
565+ level = Number (args[1 ] || 1 );
566+ } else if (a0 && typeof a0 === " object" ) {
567+ // token shape
568+ text = typeof a0 .text === " string" ? a0 .text : " " ;
569+ level = Number (a0 .depth || a0 .level || 1 );
570+ }
571+
529572 const t = (text || " " ).toString ();
530573 const id = t
531574 .toLowerCase ()
532575 .replace (/ [^ \w\s -] / g , " " )
533576 .trim ()
534577 .replace (/ \s + / g , " -" )
535578 .slice (0 , 80 );
579+
536580 return ` <h${ level} id="${ id} ">${ t} </h${ level} >` ;
537581 };
538582
@@ -1853,16 +1897,6 @@ watch(
18531897 </div>
18541898
18551899 <template v-else>
1856- <div class="pinned" v-if="pinnedRootNodes.length">
1857- <div class="label">Pinned</div>
1858- <div class="pinned-grid">
1859- <button class="pin" v-for="n in pinnedRootNodes" :key="n.path" @click="openNode(n)">
1860- <span class="icon" :class="n.type === 'dir' ? 'dir' : 'file'"></span>
1861- <span class="pin-name">{{ n.name }}</span>
1862- </button>
1863- </div>
1864- </div>
1865-
18661900 <div class="file-list">
18671901 <div class="file-row head">
18681902 <div class="c1">Name</div>
0 commit comments