mdv-live 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,46 +52,47 @@ export async function renderFile(filePath) {
52
52
  const content = await fs.readFile(filePath, 'utf-8');
53
53
  const fileType = getFileType(filePath);
54
54
 
55
- // Markdown files
56
55
  if (fileType.type === 'markdown') {
57
- // Check if it's a Marp presentation
58
- if (isMarp(content)) {
59
- const { html, css } = renderMarp(content);
60
- return {
61
- content: html,
62
- css,
63
- raw: content,
64
- fileType: 'markdown',
65
- isMarp: true
66
- };
67
- }
56
+ return renderMarkdownFile(content);
57
+ }
68
58
 
69
- // Regular markdown
70
- const html = renderMarkdown(content);
59
+ if (fileType.type === 'code') {
71
60
  return {
72
- content: html,
61
+ content: renderCode(content, fileType.lang),
73
62
  raw: content,
74
- fileType: 'markdown',
75
- isMarp: false
63
+ fileType: 'code'
76
64
  };
77
65
  }
78
66
 
79
- // Code files
80
- if (fileType.type === 'code') {
81
- const html = renderCode(content, fileType.lang);
67
+ return {
68
+ content: renderText(content),
69
+ raw: content,
70
+ fileType: 'text'
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Render markdown content, detecting Marp presentations
76
+ * @param {string} content - Raw markdown content
77
+ * @returns {Object} Rendered content and metadata
78
+ */
79
+ function renderMarkdownFile(content) {
80
+ if (isMarp(content)) {
81
+ const { html, css } = renderMarp(content);
82
82
  return {
83
83
  content: html,
84
+ css,
84
85
  raw: content,
85
- fileType: 'code'
86
+ fileType: 'markdown',
87
+ isMarp: true
86
88
  };
87
89
  }
88
90
 
89
- // Plain text
90
- const html = renderText(content);
91
91
  return {
92
- content: html,
92
+ content: renderMarkdown(content),
93
93
  raw: content,
94
- fileType: 'text'
94
+ fileType: 'markdown',
95
+ isMarp: false
95
96
  };
96
97
  }
97
98
 
@@ -60,67 +60,54 @@ function convertFrontmatter(content) {
60
60
  */
61
61
  function protectMermaidBlocks(content) {
62
62
  const blocks = [];
63
- const protected_ = content.replace(MERMAID_PATTERN, (match, code) => {
63
+ const protectedContent = content.replace(MERMAID_PATTERN, (match, code) => {
64
64
  blocks.push(code);
65
65
  return `<!--MERMAID_PLACEHOLDER_${blocks.length - 1}-->`;
66
66
  });
67
- return { content: protected_, blocks };
67
+ return { content: protectedContent, blocks };
68
68
  }
69
69
 
70
70
  /**
71
- * Restore Mermaid blocks after markdown processing
72
- * @param {string} html - Rendered HTML
73
- * @param {string[]} blocks - Mermaid code blocks
71
+ * Escape HTML entities for safe display
72
+ * @param {string} text - Text to escape
74
73
  * @returns {string}
75
74
  */
76
- function restoreMermaidBlocks(html, blocks) {
77
- blocks.forEach((code, i) => {
78
- const escaped = code
79
- .replace(/&/g, '&amp;')
80
- .replace(/</g, '&lt;')
81
- .replace(/>/g, '&gt;');
82
- const mermaidHtml = `<pre><code class="language-mermaid">${escaped}</code></pre>`;
83
-
84
- // Replace both paragraph-wrapped and bare placeholders
85
- html = html.replace(`<p><!--MERMAID_PLACEHOLDER_${i}--></p>`, mermaidHtml);
86
- html = html.replace(`<!--MERMAID_PLACEHOLDER_${i}-->`, mermaidHtml);
87
- });
88
- return html;
75
+ function escapeHtmlEntities(text) {
76
+ return text
77
+ .replace(/&/g, '&amp;')
78
+ .replace(/</g, '&lt;')
79
+ .replace(/>/g, '&gt;');
89
80
  }
90
81
 
91
82
  /**
92
- * Add line numbers to rendered elements for editor sync
83
+ * Restore Mermaid blocks after markdown processing
93
84
  * @param {string} html - Rendered HTML
85
+ * @param {string[]} blocks - Mermaid code blocks
94
86
  * @returns {string}
95
87
  */
96
- function addLineNumbers(html) {
97
- // This is a simplified version - the full implementation would
98
- // track source positions during rendering
99
- return html;
88
+ function restoreMermaidBlocks(html, blocks) {
89
+ let result = html;
90
+ for (let i = 0; i < blocks.length; i++) {
91
+ const escaped = escapeHtmlEntities(blocks[i]);
92
+ const mermaidHtml = `<pre><code class="language-mermaid">${escaped}</code></pre>`;
93
+ // Replace both paragraph-wrapped and bare placeholders
94
+ result = result
95
+ .replace(`<p><!--MERMAID_PLACEHOLDER_${i}--></p>`, mermaidHtml)
96
+ .replace(`<!--MERMAID_PLACEHOLDER_${i}-->`, mermaidHtml);
97
+ }
98
+ return result;
100
99
  }
101
100
 
102
101
  /**
103
102
  * Render markdown to HTML
104
103
  * @param {string} content - Markdown content
105
- * @returns {string} HTML
104
+ * @returns {string}
106
105
  */
107
106
  export function renderMarkdown(content) {
108
- // Convert frontmatter to code block
109
- content = convertFrontmatter(content);
110
-
111
- // Protect Mermaid blocks
112
- const { content: protected_, blocks } = protectMermaidBlocks(content);
113
-
114
- // Render markdown
115
- let html = md.render(protected_);
116
-
117
- // Restore Mermaid blocks
118
- html = restoreMermaidBlocks(html, blocks);
119
-
120
- // Add line numbers
121
- html = addLineNumbers(html);
122
-
123
- return html;
107
+ const withFrontmatter = convertFrontmatter(content);
108
+ const { content: protectedContent, blocks } = protectMermaidBlocks(withFrontmatter);
109
+ const html = md.render(protectedContent);
110
+ return restoreMermaidBlocks(html, blocks);
124
111
  }
125
112
 
126
113
  export default { renderMarkdown, isMarp };
package/src/server.js CHANGED
@@ -7,103 +7,90 @@ import express from 'express';
7
7
  import { createServer } from 'http';
8
8
  import path from 'path';
9
9
  import { fileURLToPath } from 'url';
10
- import { setupWebSocket } from './websocket.js';
11
- import { setupWatcher } from './watcher.js';
12
- import { setupTreeRoutes } from './api/tree.js';
10
+
13
11
  import { setupFileRoutes } from './api/file.js';
14
- import { setupUploadRoutes } from './api/upload.js';
15
12
  import { setupPdfRoutes } from './api/pdf.js';
13
+ import { setupTreeRoutes } from './api/tree.js';
14
+ import { setupUploadRoutes } from './api/upload.js';
15
+ import { setupWatcher } from './watcher.js';
16
+ import { setupWebSocket } from './websocket.js';
16
17
 
17
- const __filename = fileURLToPath(import.meta.url);
18
- const __dirname = path.dirname(__filename);
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const STATIC_DIR = path.join(__dirname, 'static');
20
+ const VERSION = '0.3.3';
19
21
 
20
22
  /**
21
- * Create and configure the MDV server
22
- * @param {Object} options - Server options
23
- * @param {string} options.rootDir - Root directory to serve
24
- * @param {number} options.port - Port to listen on
25
- * @returns {Object} Server instance and control functions
23
+ * Setup API routes for the Express app
24
+ * @param {express.Application} app - Express application instance
26
25
  */
27
- export function createMdvServer(options) {
28
- const { rootDir, port = 8080 } = options;
29
-
30
- const app = express();
31
- const server = createServer(app);
32
-
33
- // Store root directory in app locals for access in routes
34
- app.locals.rootDir = path.resolve(rootDir);
35
-
36
- // Middleware
37
- app.use(express.json());
38
- app.use(express.urlencoded({ extended: true }));
39
-
40
- // Static files
41
- const staticDir = path.join(__dirname, 'static');
42
- app.use('/static', express.static(staticDir));
43
-
44
- // API routes
26
+ function setupApiRoutes(app) {
45
27
  setupTreeRoutes(app);
46
28
  setupFileRoutes(app);
47
29
  setupUploadRoutes(app);
48
30
  setupPdfRoutes(app);
49
31
 
50
- // Server info endpoint
51
32
  app.get('/api/info', (req, res) => {
52
33
  res.json({
53
34
  rootPath: app.locals.rootDir,
54
- version: '0.3.1'
35
+ version: VERSION
55
36
  });
56
37
  });
57
38
 
58
- // Shutdown endpoint
59
39
  app.post('/api/shutdown', (req, res) => {
60
40
  res.json({ success: true });
61
- setTimeout(() => {
62
- process.exit(0);
63
- }, 100);
41
+ setTimeout(() => process.exit(0), 100);
64
42
  });
43
+ }
44
+
45
+ /**
46
+ * Create and configure the MDV server
47
+ * @param {Object} options - Server options
48
+ * @param {string} options.rootDir - Root directory to serve
49
+ * @param {number} [options.port=8080] - Port to listen on
50
+ * @returns {{ app: express.Application, server: http.Server, watcher: FSWatcher, wss: WebSocketServer, port: number, start: () => Promise<{port: number}>, stop: () => Promise<void> }}
51
+ */
52
+ export function createMdvServer(options) {
53
+ const { rootDir, port = 8080 } = options;
54
+
55
+ const app = express();
56
+ const server = createServer(app);
57
+
58
+ app.locals.rootDir = path.resolve(rootDir);
59
+
60
+ app.use(express.json());
61
+ app.use(express.urlencoded({ extended: true }));
62
+ app.use('/static', express.static(STATIC_DIR));
63
+
64
+ setupApiRoutes(app);
65
65
 
66
- // Serve index.html for root
67
66
  app.get('/', (req, res) => {
68
- res.sendFile(path.join(staticDir, 'index.html'));
67
+ res.sendFile(path.join(STATIC_DIR, 'index.html'));
69
68
  });
70
69
 
71
- // Setup WebSocket
72
70
  const wss = setupWebSocket(server);
73
-
74
- // Setup file watcher
75
71
  const watcher = setupWatcher(app.locals.rootDir, wss);
76
72
 
77
- // Store watcher reference
78
73
  app.locals.watcher = watcher;
79
74
  app.locals.wss = wss;
80
75
 
81
- return {
82
- app,
83
- server,
84
- watcher,
85
- wss,
86
- port,
87
-
88
- start() {
89
- return new Promise((resolve) => {
90
- server.listen(port, () => {
91
- console.log(`MDV server running at http://localhost:${port}`);
92
- resolve({ port });
93
- });
94
- });
95
- },
96
-
97
- stop() {
98
- return new Promise((resolve) => {
99
- watcher.close();
100
- wss.close();
101
- server.close(() => {
102
- resolve();
103
- });
76
+ function start() {
77
+ return new Promise((resolve) => {
78
+ server.listen(port, () => {
79
+ console.log(`MDV server running at http://localhost:${port}`);
80
+ resolve({ port });
104
81
  });
105
- }
106
- };
82
+ });
83
+ }
84
+
85
+ function stop() {
86
+ return new Promise((resolve) => {
87
+ watcher.close();
88
+ wss.close();
89
+ server.close(resolve);
90
+ });
91
+ }
92
+
93
+ return { app, server, watcher, wss, port, start, stop };
107
94
  }
108
95
 
109
96
  export default createMdvServer;