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 +1 -1
- package/src/api/file.js +45 -0
- package/src/static/app.js +14 -0
- package/src/static/styles.css +16 -0
- package/src/utils/fileTypes.js +2 -2
package/package.json
CHANGED
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, '<')
|
|
150
|
+
.replace(/>/g, '>')
|
|
151
|
+
.replace(/"/g, '"')
|
|
152
|
+
.replace(/'/g, ''');
|
|
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') {
|
package/src/static/styles.css
CHANGED
|
@@ -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
|
============================================================ */
|
package/src/utils/fileTypes.js
CHANGED
|
@@ -29,8 +29,8 @@ const FILE_TYPES = {
|
|
|
29
29
|
jsx: code('react', 'jsx'),
|
|
30
30
|
|
|
31
31
|
// Code - Web
|
|
32
|
-
html:
|
|
33
|
-
htm:
|
|
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'),
|