/** * Logique pure partagée : génération markdown, lecture meta, échappements. */ import fs from 'node:fs' import path from 'node:path' export const FEATURED_START = '' export const FEATURED_END = '' export function escMd(s) { if (typeof s !== 'string') return '' return s.replace(/\|/g, '\\|') } export function escHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') } export function pickFeatured(items) { const marked = items.filter((p) => p.featured).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) if (marked.length) return marked.slice(0, 6) return [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).slice(0, 3) } /** @param {Function} sitePath - ex. import { sitePath } from './site-base.mjs' */ export function buildFeaturedHtml(items, sitePath) { const featured = pickFeatured(items) if (!featured.length) return '' const lines = [ '
', ' ', '
', ] for (const p of featured) { const href = sitePath(`/presentations/${p.slug}/`) const meta = `Présentation · ${p.slug}` lines.push(` `) lines.push(`

${escHtml(p.title)}

`) lines.push(`

${escHtml(p.description || '')}

`) lines.push(` ${escHtml(meta)}`) lines.push('
') } lines.push('
', '
') return lines.join('\n') } export function readAllMeta(slidesRoot) { if (!fs.existsSync(slidesRoot)) return [] const dirs = fs .readdirSync(slidesRoot, { withFileTypes: true }) .filter((d) => d.isDirectory() && !d.name.startsWith('wip_') && !d.name.startsWith('_')) .map((d) => d.name) const list = [] for (const dir of dirs) { const metaPath = path.join(slidesRoot, dir, 'meta.json') if (!fs.existsSync(metaPath)) continue const raw = fs.readFileSync(metaPath, 'utf8') const meta = JSON.parse(raw) if (meta.slug && meta.slug !== dir) { console.warn(`[sync] slug "${meta.slug}" ≠ dossier "${dir}", le dossier fait foi pour les URLs Slidev`) } list.push({ ...meta, slug: dir, folder: dir }) } list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) return list } /** @param {Function} sitePath */ export function generateListPageMarkdown(items, sitePath) { const lines = [ '---', 'title: Présentations', 'outline: deep', '---', '', '# Présentations', '', 'Decks disponibles — fiche portail et build Slidev statique.', '', '
', ] for (const p of items) { const detail = sitePath(`/presentations/${p.slug}/`) /** Première planche (`/1`) + classe `vp-raw` : le routeur VitePress ignore ces liens (navigation réelle vers Slidev). */ const deck = sitePath(`/slides/${p.slug}/1`) const tags = (p.tags || []).map((t) => `\`${escMd(t)}\``).join(' · ') lines.push('
') lines.push(`

${escMd(p.title)}

`) lines.push(`

${escMd(p.description || '')}

`) if (tags) lines.push(`

${tags}

`) lines.push('
') lines.push(` Fiche`) lines.push( ` Slides`, ) lines.push('
') lines.push('
') } lines.push('
', '') return lines.join('\n') } /** @param {Function} sitePath */ export function generateDetailPageMarkdown(p, sitePath) { /** `vp-raw` : VitePress ne intercepte pas le clic (sinon 404 SPA sur /slides/…). */ const deck = sitePath(`/slides/${p.slug}/1`) const tags = (p.tags || []).map((t) => `\`${t}\``).join(' · ') const lines = [ '---', `title: ${p.title.replace(/"/g, '\\"')}`, 'outline: deep', '---', '', `# ${p.title}`, '', ...(tags ? [`**Tags :** ${tags}`, ''] : []), '## Résumé', '', p.description || '_Pas de description._', '', '## Accès', '', `

`, ` Voir les slides`, ` Ouvrir dans un nouvel onglet`, `

`, '', '## Partager les slides', '', 'Depuis la salle, scannez le code pour ouvrir les slides sur mobile.', '', ``, '', '::: info À propos', 'Les slides sont servies en SPA statique (Slidev). Les chemins d’assets suivent le `base` défini au build.', ':::', '', ] return lines.filter((l) => l !== undefined).join('\n') } export function patchIndexFeaturedContent(indexContent, items, sitePath) { const i0 = indexContent.indexOf(FEATURED_START) const i1 = indexContent.indexOf(FEATURED_END) if (i0 === -1 || i1 === -1 || i1 <= i0) { return { ok: false, content: indexContent } } const before = indexContent.slice(0, i0 + FEATURED_START.length) const after = indexContent.slice(i1) const block = buildFeaturedHtml(items, sitePath) const content = `${before}\n\n${block}\n\n${after}` return { ok: true, content } }