${section.name}
const fs = require('fs'); const path = require('path'); const matter = require('gray-matter'); const MarkdownIt = require('markdown-it'); const hljs = require('highlight.js'); const ROOT = path.resolve(__dirname, '..'); const CONTENT_DIR = path.join(ROOT, 'content'); const DIST_DIR = path.join(ROOT, 'dist'); const TEMPLATE_DIR = path.join(ROOT, 'templates'); const ASSETS_DIR = path.join(ROOT, 'assets'); const md = new MarkdownIt({ html: true, linkify: true, highlight: (str, lang) => { if (lang && hljs.getLanguage(lang)) { return `
${hljs.highlight(str, { language: lang }).value}`;
}
return `${md.utils.escapeHtml(str)}`;
},
});
const readTemplate = (name) => fs.readFileSync(path.join(TEMPLATE_DIR, name), 'utf8');
const templates = {
base: readTemplate('base.html'),
header: readTemplate('header.html'),
sidebar: readTemplate('sidebar.html'),
footer: readTemplate('footer.html'),
};
const render = (template, data) => template.replace(/{{\s*(\w+)\s*}}/g, (_, key) => data[key] ?? '');
const slugify = (value) =>
value
.toString()
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-');
const walkMarkdownFiles = (dir) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
return entries.flatMap((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) return walkMarkdownFiles(fullPath);
if (entry.isFile() && entry.name.endsWith('.md')) return [fullPath];
return [];
});
};
const cleanDir = (dir) => {
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
fs.mkdirSync(dir, { recursive: true });
};
const stripMarkdown = (text) => text.replace(/[`*_>#\-]/g, '').replace(/\s+/g, ' ').trim();
const loadNotes = () => {
const files = walkMarkdownFiles(CONTENT_DIR);
return files.map((filePath) => {
const raw = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(raw);
const relative = path.relative(CONTENT_DIR, filePath);
const fileName = path.basename(filePath, '.md');
const section = data.section || path.dirname(relative) || 'notes';
const sectionSlug = slugify(section);
const slug = slugify(data.slug || fileName);
const title = data.title || fileName.replace(/-/g, ' ');
const html = md.render(content);
const summary = (data.summary || stripMarkdown(content).slice(0, 180)) + '...';
return {
title,
section,
sectionSlug,
slug,
summary,
tags: data.tags || [],
nav: data.nav ?? Number.MAX_SAFE_INTEGER,
html,
sourcePath: relative,
};
});
};
const buildNavLinks = (sections, activeSection) => {
return sections
.map(({ name, slug }) => {
const isActive = activeSection === slug ? 'is-active' : '';
return `${name}`;
})
.join('');
};
const buildSidebarLinks = (notes, currentSlug) =>
notes
.map((note) => {
const isActive = currentSlug === note.slug ? 'is-active' : '';
return `${note.summary}
`) .join(''); const sectionContent = `Section
Notes grouped by ${section.name}. Use the sidebar or search to jump in.
${section.name}
${section.notes[0]?.summary || 'Section overview'}
`) .join(''); const homeContent = `Workspace
Markdown-first notes rendered into a fast static site. Use search or browse by section.