@markdstage/markdstage 3.3.0 → 3.4.0
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 +23 -12
- package/package.json +1 -1
- package/shared/README.md +7 -6
- package/shared/renderer/renderer.js +15 -2
- package/shared/runtime/deck-session.mjs +37 -6
- package/shared/runtime/presentation-server.mjs +368 -95
- package/src/cli.mjs +92 -19
- package/src/commands/present.mjs +133 -143
- package/src/deck.mjs +4 -0
- package/src/skills.mjs +12 -8
package/src/cli.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
exitCodeFor,
|
|
22
22
|
} from "./exit.mjs";
|
|
23
23
|
import { parsePageList } from "./deck.mjs";
|
|
24
|
-
import {
|
|
24
|
+
import { applicationCommand } from "./commands/present.mjs";
|
|
25
25
|
import { validateCommand, formatValidateReport } from "./commands/validate.mjs";
|
|
26
26
|
import { inspectCommand, formatInspectReport } from "./commands/inspect.mjs";
|
|
27
27
|
import { captureCommand, formatCaptureReport } from "./commands/capture.mjs";
|
|
@@ -36,8 +36,8 @@ import {
|
|
|
36
36
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
37
37
|
|
|
38
38
|
const COMMANDS = [
|
|
39
|
-
["present", "Open
|
|
40
|
-
["preview", "
|
|
39
|
+
["present", "Open the MarkdStage UI in presenter view."],
|
|
40
|
+
["preview", "Open the MarkdStage UI in slide view."],
|
|
41
41
|
["validate", "Check deck structure, Architecture DSL blocks, and themes."],
|
|
42
42
|
["inspect", "Report 1280x720 clipping diagnostics for a deck."],
|
|
43
43
|
["capture", "Write 1280x720 PNG files for selected or clipped slides."],
|
|
@@ -78,7 +78,14 @@ function usage(command) {
|
|
|
78
78
|
const lines = [
|
|
79
79
|
"MarkdStage — turn Markdown into 16:9 slides.",
|
|
80
80
|
"",
|
|
81
|
-
"Usage:
|
|
81
|
+
"Usage:",
|
|
82
|
+
" markdstage [options]",
|
|
83
|
+
" markdstage <file.md> [options]",
|
|
84
|
+
" markdstage <command> [options]",
|
|
85
|
+
"",
|
|
86
|
+
"Application:",
|
|
87
|
+
" With no file, open an empty UI and choose Markdown from the workspace.",
|
|
88
|
+
" With a Markdown file, open it in live slide view and refresh it on save.",
|
|
82
89
|
"",
|
|
83
90
|
"Commands:",
|
|
84
91
|
];
|
|
@@ -91,6 +98,7 @@ function usage(command) {
|
|
|
91
98
|
" --workspace <dir> Confine every read and write to this directory.",
|
|
92
99
|
" --theme <name> Override the deck theme.",
|
|
93
100
|
" --theme-file <path> Use a custom theme metadata file.",
|
|
101
|
+
" --no-open Serve the UI without launching a browser.",
|
|
94
102
|
" --json Print machine-readable JSON.",
|
|
95
103
|
" -h, --help Show help for a command.",
|
|
96
104
|
" -v, --version Print the CLI version.",
|
|
@@ -106,10 +114,10 @@ function usage(command) {
|
|
|
106
114
|
present: [
|
|
107
115
|
"Usage: markdstage present <file.md> [options]",
|
|
108
116
|
"",
|
|
109
|
-
"Opens
|
|
110
|
-
"
|
|
117
|
+
"Opens the full MarkdStage UI in presenter view.",
|
|
118
|
+
"Open Markdown, automatic refresh, editing, export, and audience controls remain available.",
|
|
111
119
|
"",
|
|
112
|
-
" --watch
|
|
120
|
+
" --watch Start with automatic refresh enabled.",
|
|
113
121
|
" --no-open Serve the presenter view without launching a browser.",
|
|
114
122
|
"",
|
|
115
123
|
"Presentation requires an installed Microsoft Edge, Google Chrome, or Chromium.",
|
|
@@ -117,11 +125,13 @@ function usage(command) {
|
|
|
117
125
|
preview: [
|
|
118
126
|
"Usage: markdstage preview <file.md> [options]",
|
|
119
127
|
"",
|
|
120
|
-
"
|
|
121
|
-
"
|
|
128
|
+
"Opens the full MarkdStage UI in slide view.",
|
|
129
|
+
"",
|
|
130
|
+
" --watch Start with automatic refresh enabled.",
|
|
131
|
+
" --no-open Serve the UI without launching a browser.",
|
|
122
132
|
"",
|
|
123
|
-
"
|
|
124
|
-
"
|
|
133
|
+
"Preview starts on the fixed 16:9 output surface. Use Output preview to switch",
|
|
134
|
+
"to the responsive layout. Architecture editing and export remain available.",
|
|
125
135
|
"",
|
|
126
136
|
"Preview requires an installed Microsoft Edge, Google Chrome, or Chromium.",
|
|
127
137
|
],
|
|
@@ -201,6 +211,10 @@ function deckOptions(file, values) {
|
|
|
201
211
|
};
|
|
202
212
|
}
|
|
203
213
|
|
|
214
|
+
function isMarkdownArgument(value) {
|
|
215
|
+
return typeof value === "string" && /\.(?:md|markdown)$/i.test(value);
|
|
216
|
+
}
|
|
217
|
+
|
|
204
218
|
export async function run(argv, io = {}) {
|
|
205
219
|
const out = io.out ?? ((text) => console.log(text));
|
|
206
220
|
const err = io.err ?? ((text) => console.error(text));
|
|
@@ -208,7 +222,7 @@ export async function run(argv, io = {}) {
|
|
|
208
222
|
|
|
209
223
|
const command = argv[0];
|
|
210
224
|
const rest = argv.slice(1);
|
|
211
|
-
if (command === "--help" || command === "-h"
|
|
225
|
+
if (command === "--help" || command === "-h") {
|
|
212
226
|
out(usage());
|
|
213
227
|
return EXIT_OK;
|
|
214
228
|
}
|
|
@@ -229,9 +243,63 @@ export async function run(argv, io = {}) {
|
|
|
229
243
|
out(await packageVersion());
|
|
230
244
|
return EXIT_OK;
|
|
231
245
|
}
|
|
232
|
-
if (command.startsWith("-")) {
|
|
233
|
-
|
|
234
|
-
|
|
246
|
+
if (command === undefined || isMarkdownArgument(command) || command.startsWith("-")) {
|
|
247
|
+
let values;
|
|
248
|
+
let positionals;
|
|
249
|
+
try {
|
|
250
|
+
({ values, positionals } = parseArgs({
|
|
251
|
+
args: argv,
|
|
252
|
+
options: {
|
|
253
|
+
...GLOBAL_OPTIONS,
|
|
254
|
+
"no-open": { type: "boolean" },
|
|
255
|
+
},
|
|
256
|
+
allowPositionals: true,
|
|
257
|
+
}));
|
|
258
|
+
} catch (error) {
|
|
259
|
+
err(`${error.message}\n\n${usage()}`);
|
|
260
|
+
return EXIT_USAGE;
|
|
261
|
+
}
|
|
262
|
+
if (values.help) {
|
|
263
|
+
out(usage());
|
|
264
|
+
return EXIT_OK;
|
|
265
|
+
}
|
|
266
|
+
if (values.version) {
|
|
267
|
+
out(await packageVersion());
|
|
268
|
+
return EXIT_OK;
|
|
269
|
+
}
|
|
270
|
+
if (positionals.length > 1) {
|
|
271
|
+
err(`Application mode accepts at most one Markdown file.\n\n${usage()}`);
|
|
272
|
+
return EXIT_USAGE;
|
|
273
|
+
}
|
|
274
|
+
const file = positionals[0];
|
|
275
|
+
if (file && !isMarkdownArgument(file)) {
|
|
276
|
+
err(`Unknown command: ${file}\n\n${usage()}`);
|
|
277
|
+
return EXIT_USAGE;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
const report = await applicationCommand(
|
|
281
|
+
{
|
|
282
|
+
...deckOptions(file, values),
|
|
283
|
+
live: Boolean(file),
|
|
284
|
+
open: io.open ?? !values["no-open"],
|
|
285
|
+
until: io.until,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
print: values.json ? () => {} : (message) => out(message),
|
|
289
|
+
status: (message, isError) => {
|
|
290
|
+
if (isError) err(message);
|
|
291
|
+
else if (!values.json) out(message);
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
if (values.json) json(report);
|
|
296
|
+
return EXIT_OK;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const code = exitCodeFor(error);
|
|
299
|
+
if (values.json) json(errorPayload(error));
|
|
300
|
+
else err(error?.message || String(error));
|
|
301
|
+
return code || EXIT_FAILURE;
|
|
302
|
+
}
|
|
235
303
|
}
|
|
236
304
|
if (!COMMAND_OPTIONS[command]) {
|
|
237
305
|
err(`Unknown command: ${command}\n\n${usage()}`);
|
|
@@ -264,8 +332,13 @@ export async function run(argv, io = {}) {
|
|
|
264
332
|
switch (command) {
|
|
265
333
|
case "preview": {
|
|
266
334
|
const file = requireFile(positionals, "preview");
|
|
267
|
-
const report = await
|
|
268
|
-
{
|
|
335
|
+
const report = await applicationCommand(
|
|
336
|
+
{
|
|
337
|
+
...deckOptions(file, values),
|
|
338
|
+
watch: values.watch,
|
|
339
|
+
open: io.open ?? !values["no-open"],
|
|
340
|
+
until: io.until,
|
|
341
|
+
},
|
|
269
342
|
{
|
|
270
343
|
print: values.json ? () => {} : (message) => out(message),
|
|
271
344
|
status: (message, isError) => {
|
|
@@ -279,11 +352,11 @@ export async function run(argv, io = {}) {
|
|
|
279
352
|
}
|
|
280
353
|
case "present": {
|
|
281
354
|
const file = requireFile(positionals, "present");
|
|
282
|
-
const report = await
|
|
355
|
+
const report = await applicationCommand(
|
|
283
356
|
{
|
|
284
357
|
...deckOptions(file, values),
|
|
285
358
|
watch: values.watch,
|
|
286
|
-
open: !values["no-open"],
|
|
359
|
+
open: io.open ?? !values["no-open"],
|
|
287
360
|
presenterView: true,
|
|
288
361
|
until: io.until,
|
|
289
362
|
},
|
package/src/commands/present.mjs
CHANGED
|
@@ -1,181 +1,171 @@
|
|
|
1
|
-
// Shared
|
|
1
|
+
// Shared browser application for the top-level, preview, and present commands.
|
|
2
2
|
|
|
3
|
-
import { mkdtemp,
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
-
import { pathToFileURL } from "node:url";
|
|
8
7
|
import {
|
|
9
8
|
MarkdStageError,
|
|
10
9
|
buildPresenterBrowserArgs,
|
|
11
10
|
findChromiumBrowser,
|
|
12
11
|
isProcessRunning,
|
|
13
|
-
sharedPath,
|
|
14
12
|
terminateProcessTree,
|
|
15
13
|
} from "../runtime.mjs";
|
|
16
14
|
import { withDeckServer } from "../deck.mjs";
|
|
17
15
|
|
|
18
|
-
async function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
session.watchError = error;
|
|
26
|
-
if (changed) server.broadcast();
|
|
27
|
-
};
|
|
28
|
-
const watcher = createMarkdownWatcher({
|
|
29
|
-
path: session.file,
|
|
30
|
-
onChange: async () => {
|
|
31
|
-
try {
|
|
32
|
-
if ((await readFile(session.file, "utf8")) === session.sourceMarkdown) {
|
|
33
|
-
setWatchState("watching");
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
// Keep the current slide, and keep the last valid deck when the file is
|
|
37
|
-
// saved in a broken intermediate state.
|
|
38
|
-
await session.load({ preserveIndex: true });
|
|
39
|
-
setWatchState("watching");
|
|
40
|
-
server.broadcast();
|
|
41
|
-
onStatus(`reloaded ${session.sourceName} (${session.slides.length} slides)`);
|
|
42
|
-
} catch (error) {
|
|
43
|
-
setWatchState("error", error?.code || "source_reload_failed");
|
|
44
|
-
onStatus(`reload failed, keeping the last valid deck: ${error?.message || error}`, true);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
onError: (error) => {
|
|
48
|
-
setWatchState("error", "watch_failed");
|
|
49
|
-
onStatus(`watch error: ${error?.message || error}`, true);
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
setWatchState("watching");
|
|
53
|
-
return watcher;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function presentCommand(options, io) {
|
|
16
|
+
export async function applicationCommand(options, io, dependencies = {}) {
|
|
17
|
+
const createTempDirectory = dependencies.mkdtemp ?? mkdtemp;
|
|
18
|
+
const findBrowser = dependencies.findChromiumBrowser ?? findChromiumBrowser;
|
|
19
|
+
const processIsRunning = dependencies.isProcessRunning ?? isProcessRunning;
|
|
20
|
+
const remove = dependencies.rm ?? rm;
|
|
21
|
+
const spawnBrowser = dependencies.spawn ?? spawn;
|
|
22
|
+
const terminate = dependencies.terminateProcessTree ?? terminateProcessTree;
|
|
57
23
|
let audienceProcess = null;
|
|
58
24
|
let audienceProfileDir = "";
|
|
59
25
|
let audienceUrl = "";
|
|
26
|
+
let audienceOperation = Promise.resolve();
|
|
27
|
+
|
|
28
|
+
const runAudienceOperation = (operation) => {
|
|
29
|
+
const result = audienceOperation.then(operation, operation);
|
|
30
|
+
audienceOperation = result.catch(() => {});
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
60
33
|
|
|
61
|
-
const
|
|
34
|
+
const closeAudienceNow = async () => {
|
|
62
35
|
const process = audienceProcess;
|
|
36
|
+
const profileDir = audienceProfileDir;
|
|
63
37
|
audienceProcess = null;
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
audienceProfileDir = "";
|
|
39
|
+
if (processIsRunning(process)) await terminate(process);
|
|
40
|
+
if (profileDir) {
|
|
41
|
+
await remove(profileDir, { recursive: true, force: true }).catch(() => {});
|
|
68
42
|
}
|
|
69
43
|
return { stopped: Boolean(process) };
|
|
70
44
|
};
|
|
71
45
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
audienceProfileDir = await mkdtemp(join(tmpdir(), "markdstage-audience-window-"));
|
|
86
|
-
audienceProcess = spawn(
|
|
87
|
-
browser,
|
|
88
|
-
buildPresenterBrowserArgs({
|
|
89
|
-
profileDir: audienceProfileDir,
|
|
90
|
-
presenterUrl: audienceUrl,
|
|
91
|
-
}),
|
|
92
|
-
{ windowsHide: false, stdio: "ignore" },
|
|
93
|
-
);
|
|
94
|
-
await new Promise((ready, reject) => {
|
|
95
|
-
audienceProcess.once("spawn", ready);
|
|
96
|
-
audienceProcess.once("error", reject);
|
|
97
|
-
});
|
|
98
|
-
return { alreadyRunning: false };
|
|
99
|
-
},
|
|
100
|
-
close: closeAudience,
|
|
46
|
+
const closeAudience = () => runAudienceOperation(closeAudienceNow);
|
|
47
|
+
|
|
48
|
+
const audience = {
|
|
49
|
+
isRunning: () => processIsRunning(audienceProcess),
|
|
50
|
+
open: () => runAudienceOperation(async () => {
|
|
51
|
+
if (processIsRunning(audienceProcess)) return { alreadyRunning: true };
|
|
52
|
+
await closeAudienceNow();
|
|
53
|
+
const browser = findBrowser();
|
|
54
|
+
if (!browser) {
|
|
55
|
+
throw new MarkdStageError(
|
|
56
|
+
"presenter_browser_not_found",
|
|
57
|
+
"Opening the audience view requires Microsoft Edge, Google Chrome, or Chromium.",
|
|
58
|
+
);
|
|
101
59
|
}
|
|
102
|
-
|
|
60
|
+
const profileDir = await createTempDirectory(
|
|
61
|
+
join(tmpdir(), "markdstage-audience-window-"),
|
|
62
|
+
);
|
|
63
|
+
const process = spawnBrowser(
|
|
64
|
+
browser,
|
|
65
|
+
buildPresenterBrowserArgs({
|
|
66
|
+
profileDir,
|
|
67
|
+
presenterUrl: audienceUrl,
|
|
68
|
+
}),
|
|
69
|
+
{ windowsHide: false, stdio: "ignore" },
|
|
70
|
+
);
|
|
71
|
+
try {
|
|
72
|
+
await new Promise((ready, reject) => {
|
|
73
|
+
process.once("spawn", ready);
|
|
74
|
+
process.once("error", reject);
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (processIsRunning(process)) await terminate(process);
|
|
78
|
+
await remove(profileDir, { recursive: true, force: true }).catch(() => {});
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
audienceProcess = process;
|
|
82
|
+
audienceProfileDir = profileDir;
|
|
83
|
+
return { alreadyRunning: false };
|
|
84
|
+
}),
|
|
85
|
+
close: closeAudience,
|
|
86
|
+
};
|
|
103
87
|
|
|
104
88
|
try {
|
|
105
|
-
return await withDeckServer(
|
|
106
|
-
|
|
89
|
+
return await withDeckServer(
|
|
90
|
+
{
|
|
91
|
+
...options,
|
|
92
|
+
application: true,
|
|
93
|
+
initialSourceMode: options.live || options.watch ? "live" : "snapshot",
|
|
94
|
+
presenter: audience,
|
|
95
|
+
},
|
|
96
|
+
async (session, server) => {
|
|
107
97
|
const url = new URL(server.url);
|
|
108
98
|
url.searchParams.set("present", "1");
|
|
109
99
|
audienceUrl = url.href;
|
|
110
|
-
}
|
|
111
|
-
const watcher = options.watch
|
|
112
|
-
? await createWatcher(session, server, {
|
|
113
|
-
onStatus: (message, isError) => io.status(message, isError),
|
|
114
|
-
})
|
|
115
|
-
: null;
|
|
116
100
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
101
|
+
let browserProcess = null;
|
|
102
|
+
let profileDir = "";
|
|
103
|
+
const browserUrl = new URL(server.url);
|
|
104
|
+
if (options.presenterView) browserUrl.searchParams.set("presenter", "1");
|
|
105
|
+
try {
|
|
106
|
+
if (options.open) {
|
|
107
|
+
const browser = findBrowser();
|
|
108
|
+
if (!browser) {
|
|
109
|
+
throw new MarkdStageError(
|
|
110
|
+
"presenter_browser_not_found",
|
|
111
|
+
"Opening MarkdStage requires Microsoft Edge, Google Chrome, or Chromium. Re-run with --no-open to serve the UI only.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
profileDir = await createTempDirectory(join(tmpdir(), "markdstage-app-window-"));
|
|
115
|
+
browserProcess = spawnBrowser(
|
|
116
|
+
browser,
|
|
117
|
+
buildPresenterBrowserArgs({ profileDir, presenterUrl: browserUrl.href }),
|
|
118
|
+
{ windowsHide: false, stdio: "ignore" },
|
|
128
119
|
);
|
|
120
|
+
await new Promise((ready, reject) => {
|
|
121
|
+
browserProcess.once("spawn", ready);
|
|
122
|
+
browserProcess.once("error", reject);
|
|
123
|
+
});
|
|
129
124
|
}
|
|
130
|
-
profileDir = await mkdtemp(join(tmpdir(), "markdstage-presenter-window-"));
|
|
131
|
-
browserProcess = spawn(
|
|
132
|
-
browser,
|
|
133
|
-
buildPresenterBrowserArgs({ profileDir, presenterUrl: browserUrl.href }),
|
|
134
|
-
{ windowsHide: false, stdio: "ignore" },
|
|
135
|
-
);
|
|
136
|
-
await new Promise((ready, reject) => {
|
|
137
|
-
browserProcess.once("spawn", ready);
|
|
138
|
-
browserProcess.once("error", reject);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
125
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
io.print(
|
|
151
|
-
|
|
152
|
-
|
|
126
|
+
const operation = options.presenterView
|
|
127
|
+
? "presenting"
|
|
128
|
+
: session.file
|
|
129
|
+
? "previewing"
|
|
130
|
+
: "ready in";
|
|
131
|
+
io.print(`MarkdStage is ${operation} ${session.sourceName || "the workspace"}`);
|
|
132
|
+
io.print(` slides: ${session.slides.length}`);
|
|
133
|
+
io.print(` theme: ${session.theme}`);
|
|
134
|
+
io.print(` workspace: ${resolve(session.workspaceRoot)}`);
|
|
135
|
+
io.print(` url: ${browserUrl.href}`);
|
|
136
|
+
if ((options.live || options.watch) && session.file) {
|
|
137
|
+
io.print(" watching: on (live reload is enabled)");
|
|
138
|
+
}
|
|
139
|
+
io.print("Press Ctrl+C to stop.");
|
|
153
140
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
141
|
+
await new Promise((done) => {
|
|
142
|
+
const stop = () => {
|
|
143
|
+
process.off("SIGINT", stop);
|
|
144
|
+
process.off("SIGTERM", stop);
|
|
145
|
+
done();
|
|
146
|
+
};
|
|
147
|
+
process.once("SIGINT", stop);
|
|
148
|
+
process.once("SIGTERM", stop);
|
|
149
|
+
if (browserProcess) browserProcess.once("close", stop);
|
|
150
|
+
if (options.until) options.until.then(stop, stop);
|
|
151
|
+
});
|
|
165
152
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
153
|
+
return {
|
|
154
|
+
ok: true,
|
|
155
|
+
url: browserUrl.href,
|
|
156
|
+
total: session.slides.length,
|
|
157
|
+
theme: session.theme,
|
|
158
|
+
sourceMode: server.sourceMode,
|
|
159
|
+
};
|
|
160
|
+
} finally {
|
|
161
|
+
await closeAudience();
|
|
162
|
+
if (processIsRunning(browserProcess)) await terminate(browserProcess);
|
|
163
|
+
if (profileDir) {
|
|
164
|
+
await remove(profileDir, { recursive: true, force: true }).catch(() => {});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
);
|
|
179
169
|
} finally {
|
|
180
170
|
await closeAudience();
|
|
181
171
|
}
|
package/src/deck.mjs
CHANGED
|
@@ -21,10 +21,14 @@ export async function withDeckServer(options, run) {
|
|
|
21
21
|
log: options.log,
|
|
22
22
|
});
|
|
23
23
|
const server = await startPresentationServer(session, {
|
|
24
|
+
application: options.application,
|
|
25
|
+
exporters: options.exporters,
|
|
26
|
+
initialSourceMode: options.initialSourceMode,
|
|
24
27
|
token,
|
|
25
28
|
onLog: options.log,
|
|
26
29
|
editable: options.watch === true,
|
|
27
30
|
presenter: options.presenter,
|
|
31
|
+
watcherFactory: options.watcherFactory,
|
|
28
32
|
});
|
|
29
33
|
try {
|
|
30
34
|
return await run(session, server);
|
package/src/skills.mjs
CHANGED
|
@@ -82,7 +82,7 @@ what the MarkdStage canvas and MarkdStage Desktop render.
|
|
|
82
82
|
\`markdstage validate slides.md --json\`. Review diagnostic codes, JSON Pointers,
|
|
83
83
|
and completeness, fix independent issues together, and preserve the same
|
|
84
84
|
validated content when presenting. Suggestions are never automatic repairs.
|
|
85
|
-
5. Use \`markdstage
|
|
85
|
+
5. Use \`markdstage slides.md\` for live source-backed authoring.
|
|
86
86
|
It reloads on save without losing the current slide and keeps the last valid
|
|
87
87
|
deck while a save is incomplete.
|
|
88
88
|
6. Check fixed 16:9 output with \`markdstage inspect slides.md --json\`. Use
|
|
@@ -96,11 +96,13 @@ what the MarkdStage canvas and MarkdStage Desktop render.
|
|
|
96
96
|
\`markdstage export slides.md --output slides.pdf\`, or
|
|
97
97
|
\`markdstage export slides.md --output slides.pptx\`.
|
|
98
98
|
|
|
99
|
-
The browser in \`
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
The browser in \`markdstage slides.md\` starts in viewing mode on the fixed 16:9
|
|
100
|
+
output surface. **Output preview** switches to the retained responsive layout.
|
|
101
|
+
The user can activate the pencil control to switch to the responsive layout and
|
|
102
|
+
move Architecture elements, then choose **Advanced edit** for the detailed
|
|
103
|
+
designer. Placement changes save immediately, while the detailed designer saves
|
|
104
|
+
only when the user selects **Save**. Automatic refresh can be toggled from the
|
|
105
|
+
same UI without disabling Architecture editing.
|
|
104
106
|
|
|
105
107
|
Never hand-write HTML or CSS for a slide. Fix layout problems by shortening the
|
|
106
108
|
content or by changing the layout in front matter. Prefer structured validation
|
|
@@ -110,8 +112,10 @@ and layout diagnostics over capturing every slide.
|
|
|
110
112
|
|
|
111
113
|
| Command | Purpose |
|
|
112
114
|
| --- | --- |
|
|
113
|
-
| \`markdstage
|
|
114
|
-
| \`markdstage
|
|
115
|
+
| \`markdstage\` | Open an empty Canvas-equivalent UI and choose Markdown from the workspace. |
|
|
116
|
+
| \`markdstage <file>\` | Open the full UI in live slide view with automatic refresh, editing, presenting, and UI export. |
|
|
117
|
+
| \`markdstage present <file> [--watch]\` | Open the same full UI in presenter view; \`--watch\` enables automatic refresh initially. |
|
|
118
|
+
| \`markdstage preview <file> [--watch]\` | Open the same full UI in slide view; \`--watch\` enables automatic refresh initially. |
|
|
115
119
|
| \`markdstage validate <file> [--json]\` | Check deck structure, Architecture DSL blocks, and themes. |
|
|
116
120
|
| \`markdstage inspect <file> [--json]\` | Report 1280x720 clipping diagnostics for the deck or one slide; use \`--fail-on-issues\` for quality gates. |
|
|
117
121
|
| \`markdstage capture <file> [--pages 2,4]\` | Write 1280x720 PNG files; without \`--pages\` only clipped slides are captured. |
|