mdv-live 0.3.8 → 0.3.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdv-live",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Markdown Viewer - File tree + Live preview + Marp support + Hot reload",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/api/file.js CHANGED
@@ -89,6 +89,32 @@ function buildBinaryFileResponse(name, fileType, downloadUrl) {
89
89
  export function setupFileRoutes(app) {
90
90
  const { rootDir } = app.locals;
91
91
 
92
+ // Serve raw files (for HTML preview with relative paths)
93
+ app.get('/raw/*', async (req, res) => {
94
+ const relativePath = req.params[0];
95
+ const { valid, fullPath } = resolveAndValidate(relativePath, rootDir);
96
+
97
+ if (!relativePath || !valid) {
98
+ return res.status(403).json({ error: 'Access denied' });
99
+ }
100
+
101
+ try {
102
+ const stat = await fs.stat(fullPath);
103
+ if (!stat.isFile()) {
104
+ return res.status(400).json({ error: 'Not a file' });
105
+ }
106
+
107
+ const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
108
+ res.setHeader('Content-Type', mimeType);
109
+ res.sendFile(fullPath);
110
+ } catch (err) {
111
+ if (err.code === 'ENOENT') {
112
+ return res.status(404).json({ error: 'File not found' });
113
+ }
114
+ res.status(500).json({ error: err.message });
115
+ }
116
+ });
117
+
92
118
  // Get file content
93
119
  app.get('/api/file', async (req, res) => {
94
120
  const { path: relativePath } = req.query;
@@ -115,6 +141,25 @@ export function setupFileRoutes(app) {
115
141
  return res.json(buildBinaryFileResponse(name, fileType, downloadUrl));
116
142
  }
117
143
 
144
+ // HTML files: return htmlUrl for iframe preview + raw content for editing
145
+ if (fileType.type === 'html') {
146
+ const content = await fs.readFile(fullPath, 'utf-8');
147
+ const escaped = content
148
+ .replace(/&/g, '&')
149
+ .replace(/</g, '&lt;')
150
+ .replace(/>/g, '&gt;')
151
+ .replace(/"/g, '&quot;')
152
+ .replace(/'/g, '&#x27;');
153
+ return res.json({
154
+ name,
155
+ fileType: 'html',
156
+ icon: 'html',
157
+ htmlUrl: `/raw/${relativePath}`,
158
+ content: `<pre><code class="language-html">${escaped}</code></pre>`,
159
+ raw: content
160
+ });
161
+ }
162
+
118
163
  const rendered = await renderFile(fullPath);
119
164
  res.json({ name, ...rendered });
120
165
  } catch (err) {
package/src/static/app.js CHANGED
@@ -830,6 +830,17 @@
830
830
  `;
831
831
  },
832
832
 
833
+ renderHTML(htmlUrl, name) {
834
+ elements.content.style.padding = '0';
835
+ elements.content.innerHTML = `
836
+ <div class="html-preview">
837
+ <iframe src="${htmlUrl}" title="${name}"
838
+ sandbox="allow-scripts allow-same-origin allow-forms">
839
+ </iframe>
840
+ </div>
841
+ `;
842
+ },
843
+
833
844
  renderVideo(mediaUrl, name) {
834
845
  elements.content.innerHTML = `
835
846
  <div class="video-preview">
@@ -908,6 +919,7 @@
908
919
  css: data.css || null, // Marp CSS from marp-core
909
920
  imageUrl: data.imageUrl,
910
921
  pdfUrl: data.pdfUrl,
922
+ htmlUrl: data.htmlUrl,
911
923
  mediaUrl: data.mediaUrl,
912
924
  downloadUrl: data.downloadUrl,
913
925
  scrollTop: 0
@@ -1018,6 +1030,8 @@
1018
1030
  ContentRenderer.renderImage(tab.imageUrl, tab.name);
1019
1031
  } else if (fileType === 'pdf') {
1020
1032
  ContentRenderer.renderPDF(tab.pdfUrl, tab.name);
1033
+ } else if (fileType === 'html' && tab.htmlUrl && !state.isEditMode) {
1034
+ ContentRenderer.renderHTML(tab.htmlUrl, tab.name);
1021
1035
  } else if (fileType === 'video') {
1022
1036
  ContentRenderer.renderVideo(tab.mediaUrl, tab.name);
1023
1037
  } else if (fileType === 'audio') {
@@ -520,6 +520,22 @@ body {
520
520
  text-align: center;
521
521
  }
522
522
 
523
+ /* ============================================================
524
+ HTML Preview
525
+ ============================================================ */
526
+
527
+ .html-preview {
528
+ width: 100%;
529
+ height: 100%;
530
+ }
531
+
532
+ .html-preview iframe {
533
+ width: 100%;
534
+ height: 100%;
535
+ border: none;
536
+ background: white;
537
+ }
538
+
523
539
  /* ============================================================
524
540
  Editor Mode
525
541
  ============================================================ */
@@ -29,8 +29,8 @@ const FILE_TYPES = {
29
29
  jsx: code('react', 'jsx'),
30
30
 
31
31
  // Code - Web
32
- html: code('html', 'html'),
33
- htm: code('html', 'html'),
32
+ html: { type: 'html', icon: 'html', lang: 'html', binary: false },
33
+ htm: { type: 'html', icon: 'html', lang: 'html', binary: false },
34
34
  css: code('css', 'css'),
35
35
  scss: code('css', 'scss'),
36
36
  less: code('css', 'less'),