marp-dev-preview 0.0.5 → 0.1.4
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/README.md +2 -1
- package/client.js +143 -0
- package/marp-dev-preview.mjs +127 -162
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ The tool is mainly intended for slide deck authors who want to preview their sli
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
* Live preview of Marp markdown files.
|
|
9
|
+
* Live preview of Marp markdown files, with position syncing.
|
|
10
|
+
* API to reload the slides using incremental updates.
|
|
10
11
|
* Automatic browser reload on file changes.
|
|
11
12
|
* Custom theme support.
|
|
12
13
|
* Keyboard navigation for slides.
|
package/client.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
+
const wsPort = document.querySelector('meta[name="ws-port"]').content;
|
|
3
|
+
const ws = new WebSocket(`ws://localhost:${wsPort}`);
|
|
4
|
+
|
|
5
|
+
let slides = Array.from(document.querySelectorAll('section[id]'));
|
|
6
|
+
const commandPrompt = document.getElementById('command-prompt');
|
|
7
|
+
const helpBox = document.getElementById('help-box');
|
|
8
|
+
|
|
9
|
+
let lastKey = '';
|
|
10
|
+
let command = '';
|
|
11
|
+
let commandMode = false;
|
|
12
|
+
|
|
13
|
+
function goToSlide(slideNumber) {
|
|
14
|
+
if (isNaN(slideNumber)) {
|
|
15
|
+
console.error('Invalid slide number: ' + slideNumber);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (slideNumber <= 0) {
|
|
20
|
+
console.error('Slide number must be greater than 0: ' + slideNumber);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (slideNumber > slides.length) {
|
|
25
|
+
console.error('Slide number exceeds total slides: ' + slideNumber);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.info('Navigating to slide: ' + slideNumber);
|
|
30
|
+
|
|
31
|
+
slides[slideNumber - 1].scrollIntoView({ behavior: 'smooth' });
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findSlideByString(string) {
|
|
36
|
+
const lowerString = string.toLowerCase();
|
|
37
|
+
let found = false;
|
|
38
|
+
for (let i = 0; i < slides.length && !found; i++) {
|
|
39
|
+
if (slides[i].textContent.toLowerCase().includes(lowerString)) {
|
|
40
|
+
slides[i].scrollIntoView({ behavior: 'smooth' });
|
|
41
|
+
found = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!found) {
|
|
45
|
+
console.error('No slide contains the string: ' + string);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ws.onmessage = (event) => {
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(event.data);
|
|
55
|
+
if (data.type === 'update') {
|
|
56
|
+
const marpContainer = document.getElementById('marp-container');
|
|
57
|
+
if (marpContainer) {
|
|
58
|
+
morphdom(marpContainer, `<div id="marp-container">${data.html}</div>`);
|
|
59
|
+
}
|
|
60
|
+
if (document.getElementById('marp-style').innerHTML !== data.css) {
|
|
61
|
+
document.getElementById('marp-style').innerHTML = data.css;
|
|
62
|
+
}
|
|
63
|
+
slides = Array.from(document.querySelectorAll('section[id]'));
|
|
64
|
+
} else if (data.command === 'goto' && data.slide) {
|
|
65
|
+
goToSlide(parseInt(data.slide, 10));
|
|
66
|
+
} else if (data.command === 'find' && data.string) {
|
|
67
|
+
findSlideByString(data.string);
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error('Failed to parse WebSocket message:', e);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function updatePrompt(text, isError = false) {
|
|
75
|
+
if (commandMode) {
|
|
76
|
+
commandPrompt.style.display = 'block';
|
|
77
|
+
commandPrompt.textContent = text;
|
|
78
|
+
commandPrompt.style.color = isError ? 'red' : 'white';
|
|
79
|
+
} else {
|
|
80
|
+
commandPrompt.style.display = 'none';
|
|
81
|
+
commandPrompt.style.color = 'white'; // Reset color when hidden
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
document.addEventListener('keydown', (e) => {
|
|
86
|
+
if (commandMode) {
|
|
87
|
+
if (e.key === 'Enter') {
|
|
88
|
+
const slideNumber = parseInt(command, 10);
|
|
89
|
+
if (goToSlide(slideNumber)) {
|
|
90
|
+
commandMode = false;
|
|
91
|
+
command = '';
|
|
92
|
+
updatePrompt(':' + command);
|
|
93
|
+
} else {
|
|
94
|
+
updatePrompt(`Error: Slide not found.`, true); // Pass message and error flag
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
commandMode = false;
|
|
97
|
+
command = '';
|
|
98
|
+
updatePrompt(':' + command); // Reset to normal prompt
|
|
99
|
+
}, 2000);
|
|
100
|
+
}
|
|
101
|
+
} else if (e.key === 'Backspace') {
|
|
102
|
+
command = command.slice(0, -1);
|
|
103
|
+
updatePrompt(':' + command);
|
|
104
|
+
} else if (e.key.length === 1 && !isNaN(parseInt(e.key, 10))) {
|
|
105
|
+
command += e.key;
|
|
106
|
+
updatePrompt(':' + command);
|
|
107
|
+
} else if (e.key === 'Escape') {
|
|
108
|
+
commandMode = false;
|
|
109
|
+
command = '';
|
|
110
|
+
updatePrompt(':' + command);
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (e.key === 'g') {
|
|
116
|
+
if (lastKey === 'g') {
|
|
117
|
+
// gg
|
|
118
|
+
if (slides.length > 0) {
|
|
119
|
+
slides[0].scrollIntoView({ behavior: 'smooth' });
|
|
120
|
+
}
|
|
121
|
+
lastKey = '';
|
|
122
|
+
} else {
|
|
123
|
+
lastKey = 'g';
|
|
124
|
+
setTimeout(() => { lastKey = '' }, 500); // reset after 500ms
|
|
125
|
+
}
|
|
126
|
+
} else if (e.key === 'G') {
|
|
127
|
+
if (slides.length > 0) {
|
|
128
|
+
slides[slides.length - 1].scrollIntoView({ behavior: 'smooth' });
|
|
129
|
+
}
|
|
130
|
+
lastKey = '';
|
|
131
|
+
} else if (e.key === ':') {
|
|
132
|
+
commandMode = true;
|
|
133
|
+
command = '';
|
|
134
|
+
lastKey = '';
|
|
135
|
+
updatePrompt(':' + command);
|
|
136
|
+
} else if (e.key === '?') {
|
|
137
|
+
helpBox.style.display = helpBox.style.display === 'none' ? 'block' : 'none';
|
|
138
|
+
lastKey = ''; // Reset lastKey to prevent unintended 'gg'
|
|
139
|
+
} else {
|
|
140
|
+
lastKey = '';
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
package/marp-dev-preview.mjs
CHANGED
|
@@ -10,6 +10,11 @@ import { hideBin } from 'yargs/helpers';
|
|
|
10
10
|
import markdownItFootnote from 'markdown-it-footnote';
|
|
11
11
|
import markdownItMark from 'markdown-it-mark';
|
|
12
12
|
import markdownItContainer from 'markdown-it-container';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
13
18
|
|
|
14
19
|
const argv = yargs(hideBin(process.argv))
|
|
15
20
|
.usage('Usage: $0 <markdown-file> [options]')
|
|
@@ -28,6 +33,12 @@ const argv = yargs(hideBin(process.argv))
|
|
|
28
33
|
type: 'number',
|
|
29
34
|
default: 8080
|
|
30
35
|
})
|
|
36
|
+
.option('verbose', {
|
|
37
|
+
alias: 'v',
|
|
38
|
+
describe: 'Enable verbose logging',
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
default: false
|
|
41
|
+
})
|
|
31
42
|
.config('config', 'Path to a JSON config file')
|
|
32
43
|
.default('config', '.mp-config.json')
|
|
33
44
|
.demandCommand(1, 'You must provide a markdown file.')
|
|
@@ -36,6 +47,41 @@ const argv = yargs(hideBin(process.argv))
|
|
|
36
47
|
const markdownFile = argv._[0]
|
|
37
48
|
const themeDir = argv.themeDir;
|
|
38
49
|
const port = argv.port;
|
|
50
|
+
const verbose = argv.verbose;
|
|
51
|
+
|
|
52
|
+
async function findPackageJson(startDir) {
|
|
53
|
+
let dir = startDir;
|
|
54
|
+
while (dir !== path.parse(dir).root) {
|
|
55
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(pkgPath);
|
|
58
|
+
return pkgPath;
|
|
59
|
+
} catch {
|
|
60
|
+
dir = path.dirname(dir);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Version reporting block (replace your current version block)
|
|
67
|
+
if (argv.version || argv.v) {
|
|
68
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
69
|
+
const __dirname = dirname(__filename);
|
|
70
|
+
const pkgPath = await findPackageJson(__dirname);
|
|
71
|
+
if (pkgPath) {
|
|
72
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
73
|
+
console.log(`marp-dev-preview version ${pkg.version}`);
|
|
74
|
+
} else {
|
|
75
|
+
console.error('Could not find package.json for version info.');
|
|
76
|
+
}
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (verbose) {
|
|
81
|
+
console.debug = console.log;
|
|
82
|
+
} else {
|
|
83
|
+
console.debug = () => { };
|
|
84
|
+
}
|
|
39
85
|
|
|
40
86
|
if (!markdownFile) {
|
|
41
87
|
console.error('Error: You must provide a path to a markdown file.');
|
|
@@ -77,8 +123,8 @@ async function initializeMarp() {
|
|
|
77
123
|
const themeFiles = await fs.readdir(themeDir);
|
|
78
124
|
for (const file of themeFiles) {
|
|
79
125
|
if (path.extname(file) === '.css') {
|
|
80
|
-
|
|
81
|
-
|
|
126
|
+
const css = await fs.readFile(path.join(themeDir, file), 'utf8');
|
|
127
|
+
marp.themeSet.add(css);
|
|
82
128
|
}
|
|
83
129
|
}
|
|
84
130
|
}
|
|
@@ -88,12 +134,7 @@ async function initializeMarp() {
|
|
|
88
134
|
async function renderMarp() {
|
|
89
135
|
const md = await fs.readFile(markdownFile, 'utf8');
|
|
90
136
|
const { html, css } = marp.render(md);
|
|
91
|
-
|
|
92
|
-
<!DOCTYPE html>
|
|
93
|
-
<html>
|
|
94
|
-
<head>
|
|
95
|
-
<style>
|
|
96
|
-
${css}
|
|
137
|
+
const customCss = `
|
|
97
138
|
svg[data-marpit-svg] {
|
|
98
139
|
margin-bottom:20px !important;
|
|
99
140
|
border: 1px solid gray;
|
|
@@ -134,136 +175,23 @@ async function renderMarp() {
|
|
|
134
175
|
display: none;
|
|
135
176
|
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
|
136
177
|
}
|
|
137
|
-
|
|
138
|
-
<script>
|
|
139
|
-
const ws = new WebSocket('ws://localhost:${port + 1}');
|
|
140
|
-
|
|
141
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
142
|
-
const slides = Array.from(document.querySelectorAll('section[id]'));
|
|
143
|
-
const commandPrompt = document.getElementById('command-prompt');
|
|
144
|
-
const helpBox = document.getElementById('help-box');
|
|
145
|
-
|
|
146
|
-
let lastKey = '';
|
|
147
|
-
let command = '';
|
|
148
|
-
let commandMode = false;
|
|
149
|
-
|
|
150
|
-
function goToSlide(slideNumber) {
|
|
151
|
-
if (!isNaN(slideNumber) && slideNumber > 0 && slideNumber <= slides.length) {
|
|
152
|
-
slides[slideNumber - 1].scrollIntoView({ behavior: 'smooth' });
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function findSlideByString(string) {
|
|
159
|
-
const lowerString = string.toLowerCase();
|
|
160
|
-
let found = false;
|
|
161
|
-
for (let i = 0; i < slides.length && !found; i++) {
|
|
162
|
-
if (slides[i].textContent.toLowerCase().includes(lowerString)) {
|
|
163
|
-
slides[i].scrollIntoView({ behavior: 'smooth' });
|
|
164
|
-
found = true;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (!found) {
|
|
168
|
-
console.error('No slide contains the string: ' + string);
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
ws.onmessage = (event) => {
|
|
176
|
-
if (event.data === 'reload') {
|
|
177
|
-
window.location.reload();
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
const data = JSON.parse(event.data);
|
|
182
|
-
if (data.command === 'goto' && data.slide) {
|
|
183
|
-
goToSlide(parseInt(data.slide, 10));
|
|
184
|
-
} else if (data.command === 'find' && data.string) {
|
|
185
|
-
findSlideByString(data.string);
|
|
186
|
-
}
|
|
187
|
-
} catch (e) {
|
|
188
|
-
console.error('Failed to parse WebSocket message:', e);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
function updatePrompt(text, isError = false) {
|
|
193
|
-
if (commandMode) {
|
|
194
|
-
commandPrompt.style.display = 'block';
|
|
195
|
-
commandPrompt.textContent = text;
|
|
196
|
-
commandPrompt.style.color = isError ? 'red' : 'white';
|
|
197
|
-
} else {
|
|
198
|
-
commandPrompt.style.display = 'none';
|
|
199
|
-
commandPrompt.style.color = 'white'; // Reset color when hidden
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
document.addEventListener('keydown', (e) => {
|
|
204
|
-
if (commandMode) {
|
|
205
|
-
if (e.key === 'Enter') {
|
|
206
|
-
const slideNumber = parseInt(command, 10);
|
|
207
|
-
if (goToSlide(slideNumber)) {
|
|
208
|
-
commandMode = false;
|
|
209
|
-
command = '';
|
|
210
|
-
updatePrompt(':' + command);
|
|
211
|
-
} else {
|
|
212
|
-
updatePrompt(\`Error: Slide not found.\`, true); // Pass message and error flag
|
|
213
|
-
setTimeout(() => {
|
|
214
|
-
commandMode = false;
|
|
215
|
-
command = '';
|
|
216
|
-
updatePrompt(':' + command); // Reset to normal prompt
|
|
217
|
-
}, 2000);
|
|
218
|
-
}
|
|
219
|
-
} else if (e.key === 'Backspace') {
|
|
220
|
-
command = command.slice(0, -1);
|
|
221
|
-
updatePrompt(':' + command);
|
|
222
|
-
} else if (e.key.length === 1 && !isNaN(parseInt(e.key,10))) {
|
|
223
|
-
command += e.key;
|
|
224
|
-
updatePrompt(':' + command);
|
|
225
|
-
} else if (e.key === 'Escape') {
|
|
226
|
-
commandMode = false;
|
|
227
|
-
command = '';
|
|
228
|
-
updatePrompt(':' + command);
|
|
229
|
-
}
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
178
|
+
`;
|
|
232
179
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
} else if (e.key === 'G') {
|
|
245
|
-
if (slides.length > 0) {
|
|
246
|
-
slides[slides.length - 1].scrollIntoView({ behavior: 'smooth' });
|
|
247
|
-
}
|
|
248
|
-
lastKey = '';
|
|
249
|
-
} else if (e.key === ':') {
|
|
250
|
-
commandMode = true;
|
|
251
|
-
command = '';
|
|
252
|
-
lastKey = '';
|
|
253
|
-
updatePrompt(':' + command);
|
|
254
|
-
} else if (e.key === '?') {
|
|
255
|
-
helpBox.style.display = helpBox.style.display === 'none' ? 'block' : 'none';
|
|
256
|
-
lastKey = ''; // Reset lastKey to prevent unintended 'gg'
|
|
257
|
-
} else {
|
|
258
|
-
lastKey = '';
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
</script>
|
|
263
|
-
<meta charset="UTF-8">
|
|
180
|
+
return `
|
|
181
|
+
<!DOCTYPE html>
|
|
182
|
+
<html>
|
|
183
|
+
<head>
|
|
184
|
+
<meta name="ws-port" content="${port + 1}">
|
|
185
|
+
<style id="marp-style">${css}</style>
|
|
186
|
+
<style id="custom-style">${customCss}</style>
|
|
187
|
+
<script src="https://unpkg.com/morphdom@2.7.0/dist/morphdom-umd.min.js"></script>
|
|
188
|
+
<script src="/client.js"></script>
|
|
189
|
+
<meta charset="UTF-8">
|
|
264
190
|
</head>
|
|
265
191
|
<body>
|
|
266
|
-
|
|
192
|
+
<div id="marp-container">
|
|
193
|
+
${html}
|
|
194
|
+
</div>
|
|
267
195
|
<div id="help-box">
|
|
268
196
|
<h3>Key Bindings</h3>
|
|
269
197
|
<table>
|
|
@@ -286,23 +214,43 @@ const server = http.createServer(async (req, res) => {
|
|
|
286
214
|
const html = await renderMarp();
|
|
287
215
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
288
216
|
res.end(html);
|
|
217
|
+
} else if (req.url === '/client.js') {
|
|
218
|
+
const clientJs = await fs.readFile(path.join(__dirname, 'client.js'), 'utf8');
|
|
219
|
+
res.writeHead(200, { 'Content-Type': 'text/javascript' });
|
|
220
|
+
res.end(clientJs);
|
|
221
|
+
} else if (req.url === '/api/reload' && req.method === 'POST') {
|
|
222
|
+
let body = '';
|
|
223
|
+
req.on('data', chunk => {
|
|
224
|
+
body += chunk.toString();
|
|
225
|
+
});
|
|
226
|
+
req.on('end', async () => {
|
|
227
|
+
console.debug("Reload request received");
|
|
228
|
+
const success = await reload(body);
|
|
229
|
+
if (success) {
|
|
230
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
231
|
+
res.end(JSON.stringify({ status: 'ok' }));
|
|
232
|
+
} else {
|
|
233
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
234
|
+
res.end(JSON.stringify({ status: 'error', message: 'Failed to render markdown' }));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
289
237
|
} else if (req.url === '/api/command' && req.method === 'POST') {
|
|
290
238
|
let body = '';
|
|
291
239
|
req.on('data', chunk => {
|
|
292
|
-
|
|
240
|
+
body += chunk.toString();
|
|
293
241
|
});
|
|
294
242
|
req.on('end', () => {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
243
|
+
try {
|
|
244
|
+
const command = JSON.parse(body);
|
|
245
|
+
for (const ws of wss.clients) {
|
|
246
|
+
ws.send(JSON.stringify(command));
|
|
247
|
+
}
|
|
248
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
249
|
+
res.end(JSON.stringify({ status: 'ok', command }));
|
|
250
|
+
} catch (e) {
|
|
251
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
252
|
+
res.end(JSON.stringify({ status: 'error', message: 'Invalid JSON' }));
|
|
253
|
+
}
|
|
306
254
|
});
|
|
307
255
|
} else {
|
|
308
256
|
const assetPath = path.join(markdownDir, req.url);
|
|
@@ -310,16 +258,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
310
258
|
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
311
259
|
|
|
312
260
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
261
|
+
const content = await fs.readFile(assetPath);
|
|
262
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
263
|
+
res.end(content);
|
|
316
264
|
} catch (error) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
265
|
+
if (error.code === 'ENOENT') {
|
|
266
|
+
res.writeHead(404);
|
|
267
|
+
res.end('Not Found');
|
|
268
|
+
} else {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
323
271
|
}
|
|
324
272
|
}
|
|
325
273
|
} catch (error) {
|
|
@@ -329,11 +277,28 @@ const server = http.createServer(async (req, res) => {
|
|
|
329
277
|
}
|
|
330
278
|
});
|
|
331
279
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
280
|
+
async function reload(markdown) {
|
|
281
|
+
try {
|
|
282
|
+
const { html, css } = marp.render(markdown);
|
|
283
|
+
const message = JSON.stringify({
|
|
284
|
+
type: 'update',
|
|
285
|
+
html: html,
|
|
286
|
+
css: css
|
|
287
|
+
});
|
|
288
|
+
for (const ws of wss.clients) {
|
|
289
|
+
ws.send(message);
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Error rendering or sending update:', error);
|
|
294
|
+
return false;
|
|
336
295
|
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
chokidar.watch(markdownFile).on('change', async () => {
|
|
299
|
+
console.log(`File ${markdownFile} changed, updating...`);
|
|
300
|
+
const md = await fs.readFile(markdownFile, 'utf8');
|
|
301
|
+
await reload(md);
|
|
337
302
|
});
|
|
338
303
|
|
|
339
304
|
initializeMarp().then(() => {
|
|
@@ -344,6 +309,6 @@ initializeMarp().then(() => {
|
|
|
344
309
|
}
|
|
345
310
|
});
|
|
346
311
|
}).catch(error => {
|
|
347
|
-
|
|
348
|
-
|
|
312
|
+
console.error("Failed to initialize Marp:", error);
|
|
313
|
+
process.exit(1);
|
|
349
314
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "marp-dev-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A CLI tool to preview Marp markdown files.",
|
|
5
5
|
"main": "marp-dev-preview.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"markdown-it-container": "^4.0.0",
|
|
30
30
|
"markdown-it-footnote": "^4.0.0",
|
|
31
31
|
"markdown-it-mark": "^4.0.0",
|
|
32
|
+
"morphdom": "^2.7.7",
|
|
32
33
|
"ws": "^8.18.3",
|
|
33
34
|
"yargs": "^18.0.0"
|
|
34
35
|
}
|