const express = require('express'); const path = require('path'); const fs = require('fs'); const markdownIt = require('markdown-it'); const app = express(); const PORT = 8100; const MD_ROOT = path.resolve(__dirname, '..'); // serve the workspace root const md = markdownIt({html:true, linkify:true, typographer:true}); // Serve static assets (client side) app.use('/', express.static(path.join(__dirname, 'public'))); // API: list directory tree (relative to workspace root) app.get('/api/tree', (req, res) => { const rel = req.query.path || ''; const absPath = path.join(MD_ROOT, rel); fs.readdir(absPath, {withFileTypes: true}, (err, entries) => { if (err) return res.status(500).json({error: err.message}); const items = entries.map(e => ({ name: e.name, path: path.join(rel, e.name).replace(/\\/g,'/'), type: e.isDirectory() ? 'dir' : (e.isFile() && e.name.endsWith('.md') ? 'file' : 'other') })).filter(i => i.type !== 'other'); // hide non‑md files res.json(items); }); }); // API: get markdown content and render HTML app.get('/api/md', (req, res) => { const rel = req.query.path; if (!rel) return res.status(400).json({error: 'path is required'}); const absPath = path.join(MD_ROOT, rel); if (!absPath.startsWith(MD_ROOT)) return res.status(400).json({error: 'invalid path'}); fs.readFile(absPath, 'utf8', (err, data) => { if (err) return res.status(404).json({error: err.message}); const html = md.render(data); res.json({html}); }); }); app.listen(PORT, () => { console.log(`🗂️ Markdown viewer running at http://localhost:${PORT}`); });