@slidev/cli 52.16.0 → 52.17.1
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 +1 -1
- package/dist/{build-BtXN8hyb.mjs → build-CsQUCdtJ.mjs} +4 -4
- package/dist/cli.mjs +18 -9
- package/dist/{export-BHxAO7pf.mjs → export-_6QaMWg2.mjs} +35 -29
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +1 -1
- package/dist/{resolver-CZx9LFrt.mjs → resolver-BBT539iz.mjs} +4 -2
- package/dist/{serve-bnzg299W.mjs → serve-bAegrEOc.mjs} +752 -267
- package/dist/stdio-CjXw2-ks.mjs +24 -0
- package/package.json +26 -23
- package/skills/slidev/SKILL.md +1 -0
- package/skills/slidev/references/core-cli.md +8 -0
- package/skills/slidev/references/tool-mcp.md +48 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as resolveImportPath, c as toAtFS, i as resolveEntry, n as getRoots, o as resolveImportUrl, r as isInstalledGlobally, s as resolveSourceFiles, t as createResolver } from "./resolver-
|
|
1
|
+
import { a as resolveImportPath, c as toAtFS, i as resolveEntry, n as getRoots, o as resolveImportUrl, r as isInstalledGlobally, s as resolveSourceFiles, t as createResolver } from "./resolver-BBT539iz.mjs";
|
|
2
2
|
import { builtinModules } from "node:module";
|
|
3
|
-
import path, { basename, dirname, join, relative, resolve, win32 } from "node:path";
|
|
3
|
+
import path, { basename, dirname, isAbsolute, join, relative, resolve, win32 } from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { createServer, loadConfigFromFile, mergeConfig, parseSync } from "vite";
|
|
6
6
|
import MarkdownExit from "markdown-exit";
|
|
@@ -40,7 +40,11 @@ import lz from "lz-string";
|
|
|
40
40
|
import { encode } from "plantuml-encoder";
|
|
41
41
|
import { SourceMapConsumer } from "source-map-js";
|
|
42
42
|
import { fromAsyncCodeToHtml } from "@shikijs/markdown-it/async";
|
|
43
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
44
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
45
|
+
import { z } from "zod";
|
|
43
46
|
import ServerRef from "vite-plugin-vue-server-ref";
|
|
47
|
+
import { VitePWA } from "vite-plugin-pwa";
|
|
44
48
|
import UnoCSS from "unocss/vite";
|
|
45
49
|
import { mergeConfigs, presetIcons } from "unocss";
|
|
46
50
|
import Vue from "@vitejs/plugin-vue";
|
|
@@ -66,7 +70,172 @@ function MarkdownItLink(md) {
|
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
//#endregion
|
|
73
|
+
//#region node/virtual/deprecated.ts
|
|
74
|
+
/**
|
|
75
|
+
* Kept for backward compatibility, use #slidev/slides instead
|
|
76
|
+
*
|
|
77
|
+
* @deprecated
|
|
78
|
+
*/
|
|
79
|
+
const templateLegacyRoutes = {
|
|
80
|
+
id: "/@slidev/routes",
|
|
81
|
+
getContent() {
|
|
82
|
+
return [`export { slides } from '#slidev/slides'`, `console.warn('[slidev] #slidev/routes is deprecated, use #slidev/slides instead')`].join("\n");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Kept for backward compatibility, use #slidev/title-renderer instead
|
|
87
|
+
*
|
|
88
|
+
* @deprecated
|
|
89
|
+
*/
|
|
90
|
+
const templateLegacyTitles = {
|
|
91
|
+
id: "/@slidev/titles.md",
|
|
92
|
+
getContent() {
|
|
93
|
+
return `
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import TitleRenderer from '#slidev/title-renderer'
|
|
96
|
+
defineProps<{ no: number | string }>()
|
|
97
|
+
console.warn('/@slidev/titles.md is deprecated, import from #slidev/title-renderer instead')
|
|
98
|
+
<\/script>
|
|
99
|
+
|
|
100
|
+
<TitleRenderer :no="no" />
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region node/virtual/titles.ts
|
|
106
|
+
const templateTitleRendererMd = {
|
|
107
|
+
id: "/@slidev/title-renderer.md",
|
|
108
|
+
getContent({ data }) {
|
|
109
|
+
const lines = data.slides.map(({ title }, i) => `<template ${i === 0 ? "v-if" : "v-else-if"}="no === ${i + 1}">\n\n${title}\n\n</template>`);
|
|
110
|
+
lines.push(`<script setup lang="ts">`, `import { useSlideContext } from '@slidev/client/context.ts'`, `import { computed } from 'vue'`, `const props = defineProps<{ no?: number | string }>()`, `const { $page } = useSlideContext()`, `const no = computed(() => +(props.no ?? $page.value))`, `<\/script>`);
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const templateTitleRenderer = {
|
|
115
|
+
id: "/@slidev/title-renderer",
|
|
116
|
+
async getContent() {
|
|
117
|
+
return "export { default } from \"/@slidev/title-renderer.md\"";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region node/vite/common.ts
|
|
122
|
+
const regexSlideReqPath = /^\/__slidev\/slides\/(\d+)\.json$/;
|
|
123
|
+
const regexSlideFacadeId = /^\/@slidev\/slides\/(\d+)\/(md|frontmatter)($|\?)/;
|
|
124
|
+
const regexSlideSourceId = /__slidev_(\d+)\.(md|frontmatter)$/;
|
|
125
|
+
const templateInjectionMarker = "/* @slidev-injection */";
|
|
126
|
+
const templateImportContextUtils = `import { useSlideContext as _useSlideContext, frontmatterToProps as _frontmatterToProps } from "@slidev/client/context.ts"`;
|
|
127
|
+
const templateInitContext = `const { $slidev, $nav, $clicksContext, $clicks, $page, $renderContext, $frontmatter } = _useSlideContext()`;
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region node/vite/importGuard.ts
|
|
130
|
+
const virtualSlideMarkdownIds = /* @__PURE__ */ new Set([templateTitleRendererMd.id, templateLegacyTitles.id]);
|
|
131
|
+
function createSlideImportGuardPlugin() {
|
|
132
|
+
let config;
|
|
133
|
+
let allowRoots = [];
|
|
134
|
+
return {
|
|
135
|
+
name: "slidev:slide-import-guard",
|
|
136
|
+
configResolved(resolved) {
|
|
137
|
+
config = resolved;
|
|
138
|
+
allowRoots = config.server.fs.allow.map(normalizeFsPath);
|
|
139
|
+
},
|
|
140
|
+
async transform(code, id) {
|
|
141
|
+
if (!isSlideMarkdownId(id) || !config?.server.fs.strict) return null;
|
|
142
|
+
const importer = filePathFromId(id) ?? id;
|
|
143
|
+
await Promise.all(extractImportSources(code, id).map(async ({ value, start }) => {
|
|
144
|
+
const resolved = await this.resolve(value, importer, { skipSelf: true });
|
|
145
|
+
if (!resolved || resolved.external) return;
|
|
146
|
+
const filePath = filePathFromId(resolved.id);
|
|
147
|
+
if (!filePath) return;
|
|
148
|
+
const normalized = normalizeFsPath(filePath);
|
|
149
|
+
if (isAllowedFile(normalized, allowRoots)) return;
|
|
150
|
+
if (isBareImport(value) && isDependencyFile(normalized)) return;
|
|
151
|
+
this.error(`[slidev] Import "${value}" from slide Markdown resolves outside of Vite server.fs.allow: ${normalized}`, start);
|
|
152
|
+
}));
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function isSlideMarkdownId(id) {
|
|
158
|
+
const clean = cleanUrl$1(id);
|
|
159
|
+
return regexSlideSourceId.test(clean) || virtualSlideMarkdownIds.has(clean);
|
|
160
|
+
}
|
|
161
|
+
function filePathFromId(id) {
|
|
162
|
+
const clean = cleanUrl$1(id);
|
|
163
|
+
if (clean.startsWith("file://")) return fileURLToPath(clean);
|
|
164
|
+
if (clean.startsWith("/@fs/")) return clean.slice(4);
|
|
165
|
+
if (clean.startsWith("/@")) return null;
|
|
166
|
+
if (path.isAbsolute(clean)) return clean;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function isAllowedFile(filePath, allowRoots) {
|
|
170
|
+
return allowRoots.some((root) => isFileInRoot(root, filePath));
|
|
171
|
+
}
|
|
172
|
+
function extractImportSources(code, id) {
|
|
173
|
+
const result = parseSync(id, code);
|
|
174
|
+
const sources = [];
|
|
175
|
+
for (const item of result.module.staticImports) sources.push({
|
|
176
|
+
value: item.moduleRequest.value,
|
|
177
|
+
start: item.moduleRequest.start
|
|
178
|
+
});
|
|
179
|
+
for (const item of result.module.staticExports) for (const entry of item.entries) if (entry.moduleRequest) sources.push({
|
|
180
|
+
value: entry.moduleRequest.value,
|
|
181
|
+
start: entry.moduleRequest.start
|
|
182
|
+
});
|
|
183
|
+
for (const item of result.module.dynamicImports) {
|
|
184
|
+
const source = parseStringLiteral(code.slice(item.moduleRequest.start, item.moduleRequest.end));
|
|
185
|
+
if (source) sources.push({
|
|
186
|
+
value: source,
|
|
187
|
+
start: item.moduleRequest.start
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return sources;
|
|
191
|
+
}
|
|
192
|
+
function cleanUrl$1(id) {
|
|
193
|
+
return id.replace(/[?#].*$/, "");
|
|
194
|
+
}
|
|
195
|
+
function normalizeFsPath(filePath) {
|
|
196
|
+
const absolute = path.resolve(filePath);
|
|
197
|
+
if (!existsSync(absolute)) return normalizeMissingFsPath(absolute);
|
|
198
|
+
return realpathSync.native(absolute);
|
|
199
|
+
}
|
|
200
|
+
function normalizeMissingFsPath(filePath) {
|
|
201
|
+
const dir = path.dirname(filePath);
|
|
202
|
+
if (dir === filePath) return filePath;
|
|
203
|
+
if (existsSync(dir)) return path.join(realpathSync.native(dir), path.basename(filePath));
|
|
204
|
+
return path.join(normalizeMissingFsPath(dir), path.basename(filePath));
|
|
205
|
+
}
|
|
206
|
+
function isFileInRoot(root, filePath) {
|
|
207
|
+
const relative = path.relative(root, filePath);
|
|
208
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
209
|
+
}
|
|
210
|
+
function isDependencyFile(filePath) {
|
|
211
|
+
return filePath.split(/[\\/]/).includes("node_modules");
|
|
212
|
+
}
|
|
213
|
+
function isBareImport(source) {
|
|
214
|
+
return /^(?![a-z]:)[\w@](?!.*:\/\/)/i.test(source);
|
|
215
|
+
}
|
|
216
|
+
function parseStringLiteral(raw) {
|
|
217
|
+
const trimmed = raw.trim();
|
|
218
|
+
const quote = trimmed[0];
|
|
219
|
+
if (quote !== "\"" && quote !== "'" && quote !== "`") return null;
|
|
220
|
+
if (trimmed.at(-1) !== quote) return null;
|
|
221
|
+
if (quote === "`" && trimmed.includes("${")) return null;
|
|
222
|
+
try {
|
|
223
|
+
if (quote === "\"") return JSON.parse(trimmed);
|
|
224
|
+
return JSON.parse(`"${trimmed.slice(1, -1).replace(/"/g, "\\\"")}"`);
|
|
225
|
+
} catch {
|
|
226
|
+
return trimmed.slice(1, -1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
69
230
|
//#region node/utils.ts
|
|
231
|
+
/**
|
|
232
|
+
* Whether `filePath` resolves inside any of `roots` (no `..` escape). Shared
|
|
233
|
+
* containment predicate reused by the snippet (`<<<`) and `src:` deck-file
|
|
234
|
+
* reads, and by the Vite slide-import guard (`isAllowedFile`).
|
|
235
|
+
*/
|
|
236
|
+
function isPathInsideRoots(filePath, roots) {
|
|
237
|
+
return isAllowedFile(filePath, roots);
|
|
238
|
+
}
|
|
70
239
|
const RE_WHITESPACE_ONLY = /^\s*$/;
|
|
71
240
|
const RE_QUOTED_STRING = /^(['"])(.*)\1$/;
|
|
72
241
|
const RE_WHITESPACE = /\s+/g;
|
|
@@ -239,14 +408,6 @@ function createComponentsPlugin({ clientRoot, roots }, pluginOptions) {
|
|
|
239
408
|
});
|
|
240
409
|
}
|
|
241
410
|
//#endregion
|
|
242
|
-
//#region node/vite/common.ts
|
|
243
|
-
const regexSlideReqPath = /^\/__slidev\/slides\/(\d+)\.json$/;
|
|
244
|
-
const regexSlideFacadeId = /^\/@slidev\/slides\/(\d+)\/(md|frontmatter)($|\?)/;
|
|
245
|
-
const regexSlideSourceId = /__slidev_(\d+)\.(md|frontmatter)$/;
|
|
246
|
-
const templateInjectionMarker = "/* @slidev-injection */";
|
|
247
|
-
const templateImportContextUtils = `import { useSlideContext as _useSlideContext, frontmatterToProps as _frontmatterToProps } from "@slidev/client/context.ts"`;
|
|
248
|
-
const templateInitContext = `const { $slidev, $nav, $clicksContext, $clicks, $page, $renderContext, $frontmatter } = _useSlideContext()`;
|
|
249
|
-
//#endregion
|
|
250
411
|
//#region node/vite/contextInjection.ts
|
|
251
412
|
const RE_EXPORT_DEFAULT_OBJECT = /export\s+default\s+\{/;
|
|
252
413
|
const RE_INJECT_OPTION = /.*inject\s*:\s*([[{])/;
|
|
@@ -307,6 +468,7 @@ const RE_SLIDEV_CLIENT_SUBPATH = /^@slidev\/client\/(.*)/;
|
|
|
307
468
|
const RE_SLIDEV_VIRTUAL = /^#slidev\/(.*)/;
|
|
308
469
|
const RE_MONACO_EDITOR = /\/monaco-editor(?:-core)?\//;
|
|
309
470
|
const INCLUDE_GLOBAL = [
|
|
471
|
+
"@fix-webm-duration/fix",
|
|
310
472
|
"@typescript/ata",
|
|
311
473
|
"file-saver",
|
|
312
474
|
"lz-string",
|
|
@@ -462,170 +624,21 @@ function createHmrPatchPlugin() {
|
|
|
462
624
|
name: "slidev:hmr-patch",
|
|
463
625
|
transform: {
|
|
464
626
|
filter: { id: { include: regexSlideSourceId } },
|
|
465
|
-
handler(code, id) {
|
|
466
|
-
if (!id.match(regexSlideSourceId)) return;
|
|
467
|
-
return code.replace("if (_rerender_only)", "if (false)");
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
//#endregion
|
|
473
|
-
//#region node/vite/icons.ts
|
|
474
|
-
function createIconsPlugin(options, pluginOptions) {
|
|
475
|
-
return Icons({
|
|
476
|
-
defaultClass: "slidev-icon",
|
|
477
|
-
collectionsNodeResolvePath: options.utils.iconsResolvePath,
|
|
478
|
-
...pluginOptions.icons
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
//#endregion
|
|
482
|
-
//#region node/virtual/deprecated.ts
|
|
483
|
-
/**
|
|
484
|
-
* Kept for backward compatibility, use #slidev/slides instead
|
|
485
|
-
*
|
|
486
|
-
* @deprecated
|
|
487
|
-
*/
|
|
488
|
-
const templateLegacyRoutes = {
|
|
489
|
-
id: "/@slidev/routes",
|
|
490
|
-
getContent() {
|
|
491
|
-
return [`export { slides } from '#slidev/slides'`, `console.warn('[slidev] #slidev/routes is deprecated, use #slidev/slides instead')`].join("\n");
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
/**
|
|
495
|
-
* Kept for backward compatibility, use #slidev/title-renderer instead
|
|
496
|
-
*
|
|
497
|
-
* @deprecated
|
|
498
|
-
*/
|
|
499
|
-
const templateLegacyTitles = {
|
|
500
|
-
id: "/@slidev/titles.md",
|
|
501
|
-
getContent() {
|
|
502
|
-
return `
|
|
503
|
-
<script setup lang="ts">
|
|
504
|
-
import TitleRenderer from '#slidev/title-renderer'
|
|
505
|
-
defineProps<{ no: number | string }>()
|
|
506
|
-
console.warn('/@slidev/titles.md is deprecated, import from #slidev/title-renderer instead')
|
|
507
|
-
<\/script>
|
|
508
|
-
|
|
509
|
-
<TitleRenderer :no="no" />
|
|
510
|
-
`;
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region node/virtual/titles.ts
|
|
515
|
-
const templateTitleRendererMd = {
|
|
516
|
-
id: "/@slidev/title-renderer.md",
|
|
517
|
-
getContent({ data }) {
|
|
518
|
-
const lines = data.slides.map(({ title }, i) => `<template ${i === 0 ? "v-if" : "v-else-if"}="no === ${i + 1}">\n\n${title}\n\n</template>`);
|
|
519
|
-
lines.push(`<script setup lang="ts">`, `import { useSlideContext } from '@slidev/client/context.ts'`, `import { computed } from 'vue'`, `const props = defineProps<{ no?: number | string }>()`, `const { $page } = useSlideContext()`, `const no = computed(() => +(props.no ?? $page.value))`, `<\/script>`);
|
|
520
|
-
return lines.join("\n");
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
const templateTitleRenderer = {
|
|
524
|
-
id: "/@slidev/title-renderer",
|
|
525
|
-
async getContent() {
|
|
526
|
-
return "export { default } from \"/@slidev/title-renderer.md\"";
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
//#endregion
|
|
530
|
-
//#region node/vite/importGuard.ts
|
|
531
|
-
const virtualSlideMarkdownIds = new Set([templateTitleRendererMd.id, templateLegacyTitles.id]);
|
|
532
|
-
function createSlideImportGuardPlugin() {
|
|
533
|
-
let config;
|
|
534
|
-
let allowRoots = [];
|
|
535
|
-
return {
|
|
536
|
-
name: "slidev:slide-import-guard",
|
|
537
|
-
configResolved(resolved) {
|
|
538
|
-
config = resolved;
|
|
539
|
-
allowRoots = config.server.fs.allow.map(normalizeFsPath);
|
|
540
|
-
},
|
|
541
|
-
async transform(code, id) {
|
|
542
|
-
if (!isSlideMarkdownId(id) || !config?.server.fs.strict) return null;
|
|
543
|
-
const importer = filePathFromId(id) ?? id;
|
|
544
|
-
await Promise.all(extractImportSources(code, id).map(async ({ value, start }) => {
|
|
545
|
-
const resolved = await this.resolve(value, importer, { skipSelf: true });
|
|
546
|
-
if (!resolved || resolved.external) return;
|
|
547
|
-
const filePath = filePathFromId(resolved.id);
|
|
548
|
-
if (!filePath) return;
|
|
549
|
-
const normalized = normalizeFsPath(filePath);
|
|
550
|
-
if (isAllowedFile(normalized, allowRoots)) return;
|
|
551
|
-
if (isBareImport(value) && isDependencyFile(normalized)) return;
|
|
552
|
-
this.error(`[slidev] Import "${value}" from slide Markdown resolves outside of Vite server.fs.allow: ${normalized}`, start);
|
|
553
|
-
}));
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
function isSlideMarkdownId(id) {
|
|
559
|
-
const clean = cleanUrl$1(id);
|
|
560
|
-
return regexSlideSourceId.test(clean) || virtualSlideMarkdownIds.has(clean);
|
|
561
|
-
}
|
|
562
|
-
function filePathFromId(id) {
|
|
563
|
-
const clean = cleanUrl$1(id);
|
|
564
|
-
if (clean.startsWith("file://")) return fileURLToPath(clean);
|
|
565
|
-
if (clean.startsWith("/@fs/")) return clean.slice(4);
|
|
566
|
-
if (clean.startsWith("/@")) return null;
|
|
567
|
-
if (path.isAbsolute(clean)) return clean;
|
|
568
|
-
return null;
|
|
569
|
-
}
|
|
570
|
-
function isAllowedFile(filePath, allowRoots) {
|
|
571
|
-
return allowRoots.some((root) => isFileInRoot(root, filePath));
|
|
572
|
-
}
|
|
573
|
-
function extractImportSources(code, id) {
|
|
574
|
-
const result = parseSync(id, code);
|
|
575
|
-
const sources = [];
|
|
576
|
-
for (const item of result.module.staticImports) sources.push({
|
|
577
|
-
value: item.moduleRequest.value,
|
|
578
|
-
start: item.moduleRequest.start
|
|
579
|
-
});
|
|
580
|
-
for (const item of result.module.staticExports) for (const entry of item.entries) if (entry.moduleRequest) sources.push({
|
|
581
|
-
value: entry.moduleRequest.value,
|
|
582
|
-
start: entry.moduleRequest.start
|
|
583
|
-
});
|
|
584
|
-
for (const item of result.module.dynamicImports) {
|
|
585
|
-
const source = parseStringLiteral(code.slice(item.moduleRequest.start, item.moduleRequest.end));
|
|
586
|
-
if (source) sources.push({
|
|
587
|
-
value: source,
|
|
588
|
-
start: item.moduleRequest.start
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
return sources;
|
|
592
|
-
}
|
|
593
|
-
function cleanUrl$1(id) {
|
|
594
|
-
return id.replace(/[?#].*$/, "");
|
|
595
|
-
}
|
|
596
|
-
function normalizeFsPath(filePath) {
|
|
597
|
-
const absolute = path.resolve(filePath);
|
|
598
|
-
if (!existsSync(absolute)) return normalizeMissingFsPath(absolute);
|
|
599
|
-
return realpathSync.native(absolute);
|
|
600
|
-
}
|
|
601
|
-
function normalizeMissingFsPath(filePath) {
|
|
602
|
-
const dir = path.dirname(filePath);
|
|
603
|
-
if (dir === filePath) return filePath;
|
|
604
|
-
if (existsSync(dir)) return path.join(realpathSync.native(dir), path.basename(filePath));
|
|
605
|
-
return path.join(normalizeMissingFsPath(dir), path.basename(filePath));
|
|
606
|
-
}
|
|
607
|
-
function isFileInRoot(root, filePath) {
|
|
608
|
-
const relative = path.relative(root, filePath);
|
|
609
|
-
return relative === "" || !!relative && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
610
|
-
}
|
|
611
|
-
function isDependencyFile(filePath) {
|
|
612
|
-
return filePath.split(/[\\/]/).includes("node_modules");
|
|
613
|
-
}
|
|
614
|
-
function isBareImport(source) {
|
|
615
|
-
return /^(?![a-z]:)[\w@](?!.*:\/\/)/i.test(source);
|
|
616
|
-
}
|
|
617
|
-
function parseStringLiteral(raw) {
|
|
618
|
-
const trimmed = raw.trim();
|
|
619
|
-
const quote = trimmed[0];
|
|
620
|
-
if (quote !== "\"" && quote !== "'" && quote !== "`") return null;
|
|
621
|
-
if (trimmed.at(-1) !== quote) return null;
|
|
622
|
-
if (quote === "`" && trimmed.includes("${")) return null;
|
|
623
|
-
try {
|
|
624
|
-
if (quote === "\"") return JSON.parse(trimmed);
|
|
625
|
-
return JSON.parse(`"${trimmed.slice(1, -1).replace(/"/g, "\\\"")}"`);
|
|
626
|
-
} catch {
|
|
627
|
-
return trimmed.slice(1, -1);
|
|
628
|
-
}
|
|
627
|
+
handler(code, id) {
|
|
628
|
+
if (!id.match(regexSlideSourceId)) return;
|
|
629
|
+
return code.replace("if (_rerender_only)", "if (false)");
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region node/vite/icons.ts
|
|
636
|
+
function createIconsPlugin(options, pluginOptions) {
|
|
637
|
+
return Icons({
|
|
638
|
+
defaultClass: "slidev-icon",
|
|
639
|
+
collectionsNodeResolvePath: options.utils.iconsResolvePath,
|
|
640
|
+
...pluginOptions.icons
|
|
641
|
+
});
|
|
629
642
|
}
|
|
630
643
|
//#endregion
|
|
631
644
|
//#region node/vite/inspect.ts
|
|
@@ -685,7 +698,7 @@ function createLayoutWrapperPlugin({ data, utils }) {
|
|
|
685
698
|
}
|
|
686
699
|
//#endregion
|
|
687
700
|
//#region package.json
|
|
688
|
-
var version = "52.
|
|
701
|
+
var version = "52.17.1";
|
|
689
702
|
//#endregion
|
|
690
703
|
//#region node/integrations/addons.ts
|
|
691
704
|
async function resolveAddons(addonsInConfig) {
|
|
@@ -901,7 +914,7 @@ function resolveShikiOptions(options) {
|
|
|
901
914
|
if (mergedOptions.themes) {
|
|
902
915
|
for (const theme of Object.values(mergedOptions.themes)) if (typeof theme === "object" && theme?.name) themeInput[theme.name] = theme;
|
|
903
916
|
}
|
|
904
|
-
const languageNames = new Set([
|
|
917
|
+
const languageNames = /* @__PURE__ */ new Set([
|
|
905
918
|
"markdown",
|
|
906
919
|
"vue",
|
|
907
920
|
"javascript",
|
|
@@ -981,22 +994,31 @@ const debug = createDebug("slidev:options");
|
|
|
981
994
|
async function resolveOptions(entryOptions, mode) {
|
|
982
995
|
const entry = await resolveEntry(entryOptions.entry);
|
|
983
996
|
const rootsInfo = await getRoots(entry);
|
|
984
|
-
const
|
|
997
|
+
const initialLoaded = await parser.load({
|
|
985
998
|
userRoot: rootsInfo.userRoot,
|
|
986
|
-
roots: [rootsInfo.userRoot]
|
|
999
|
+
roots: [rootsInfo.userRoot],
|
|
1000
|
+
allowedRoots: uniq([rootsInfo.userWorkspaceRoot, rootsInfo.userRoot])
|
|
987
1001
|
}, entry, void 0, mode);
|
|
988
|
-
let themeRaw = entryOptions.theme ||
|
|
1002
|
+
let themeRaw = entryOptions.theme || initialLoaded.headmatter.theme;
|
|
989
1003
|
themeRaw = themeRaw === null ? "none" : themeRaw || "default";
|
|
990
1004
|
const [theme, themeRoot] = await resolveTheme(themeRaw, entry);
|
|
991
1005
|
const themeRoots = themeRoot ? [themeRoot] : [];
|
|
992
1006
|
const themeMeta = themeRoot ? await getThemeMeta(theme, themeRoot) : void 0;
|
|
993
|
-
const
|
|
994
|
-
const addonRoots = await resolveAddons(
|
|
1007
|
+
const initialConfig = parser.resolveConfig(initialLoaded.headmatter, themeMeta, entryOptions.entry);
|
|
1008
|
+
const addonRoots = await resolveAddons(initialConfig.addons);
|
|
995
1009
|
const roots = uniq([
|
|
996
1010
|
...themeRoots,
|
|
997
1011
|
...addonRoots,
|
|
998
1012
|
rootsInfo.userRoot
|
|
999
1013
|
]);
|
|
1014
|
+
const loaded = await parser.load({
|
|
1015
|
+
userRoot: rootsInfo.userRoot,
|
|
1016
|
+
roots,
|
|
1017
|
+
allowedRoots: uniq([rootsInfo.userWorkspaceRoot, ...roots])
|
|
1018
|
+
}, entry, void 0, mode);
|
|
1019
|
+
const config = parser.resolveConfig(loaded.headmatter, themeMeta, entryOptions.entry);
|
|
1020
|
+
config.theme = themeRaw;
|
|
1021
|
+
config.addons = initialConfig.addons;
|
|
1000
1022
|
if (entryOptions.download) config.download ||= entryOptions.download;
|
|
1001
1023
|
if (entryOptions.routerMode) config.routerMode = entryOptions.routerMode;
|
|
1002
1024
|
debug({
|
|
@@ -1071,6 +1093,7 @@ function getDefine(options) {
|
|
|
1071
1093
|
__DEV__: options.mode === "dev",
|
|
1072
1094
|
__SLIDEV_CLIENT_ROOT__: toAtFS(options.clientRoot),
|
|
1073
1095
|
__SLIDEV_HASH_ROUTE__: options.data.config.routerMode === "hash",
|
|
1096
|
+
__SLIDEV_MEMORY_ROUTE__: options.data.config.routerMode === "memory" && options.mode !== "export",
|
|
1074
1097
|
__SLIDEV_FEATURE_DRAWINGS__: matchMode(options.data.config.drawings.enabled),
|
|
1075
1098
|
__SLIDEV_FEATURE_EDITOR__: options.mode === "dev" && options.data.config.editor !== false,
|
|
1076
1099
|
__SLIDEV_FEATURE_DRAWINGS_PERSIST__: !!options.data.config.drawings.persist,
|
|
@@ -1083,6 +1106,7 @@ function getDefine(options) {
|
|
|
1083
1106
|
].includes(options.data.config.download),
|
|
1084
1107
|
__SLIDEV_FEATURE_BROWSER_EXPORTER__: matchMode(options.data.config.browserExporter),
|
|
1085
1108
|
__SLIDEV_FEATURE_WAKE_LOCK__: matchMode(options.data.config.wakeLock),
|
|
1109
|
+
__SLIDEV_FEATURE_PWA__: matchMode(options.data.config.pwa),
|
|
1086
1110
|
__SLIDEV_HAS_SERVER__: options.mode !== "build"
|
|
1087
1111
|
}, (v, k) => [v, JSON.stringify(k)]);
|
|
1088
1112
|
}
|
|
@@ -1658,6 +1682,7 @@ function createSlidesLoader(options, serverOptions) {
|
|
|
1658
1682
|
frontmatter: void 0,
|
|
1659
1683
|
source: void 0,
|
|
1660
1684
|
importChain: void 0,
|
|
1685
|
+
images: slide.images ?? slide.source?.images,
|
|
1661
1686
|
...mode === "build" ? {
|
|
1662
1687
|
raw: "",
|
|
1663
1688
|
content: "",
|
|
@@ -2035,7 +2060,12 @@ function createMonacoWriterPlugin({ userRoot }) {
|
|
|
2035
2060
|
console.error(`[Slidev] Unauthorized file write: ${file}`);
|
|
2036
2061
|
return;
|
|
2037
2062
|
}
|
|
2038
|
-
const filepath = path.
|
|
2063
|
+
const filepath = path.resolve(userRoot, file);
|
|
2064
|
+
const rel = path.relative(userRoot, filepath);
|
|
2065
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
2066
|
+
console.error(`[slidev] Refusing monaco write outside project root: ${file}`);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2039
2069
|
console.log("[Slidev] Writing file:", filepath);
|
|
2040
2070
|
await fs$1.writeFile(filepath, content, "utf-8");
|
|
2041
2071
|
}
|
|
@@ -2120,7 +2150,7 @@ function findRegion(lines, regionName) {
|
|
|
2120
2150
|
return null;
|
|
2121
2151
|
}
|
|
2122
2152
|
const RE_SNIPPET_IMPORT = /^<<<[ \t]*(\S.*?)(#[\w-]+)?[ \t]*(?:[ \t](\S+?))?[ \t]*(\{.*)?$/;
|
|
2123
|
-
function resolveSnippetImport(lineText, userRoot, slide) {
|
|
2153
|
+
function resolveSnippetImport(lineText, userRoot, slide, allowedRoots = [userRoot]) {
|
|
2124
2154
|
const match = lineText.trimStart().match(RE_SNIPPET_IMPORT);
|
|
2125
2155
|
if (!match) return null;
|
|
2126
2156
|
let [, filepath = "", regionName = "", lang = "", meta = ""] = match;
|
|
@@ -2128,6 +2158,7 @@ function resolveSnippetImport(lineText, userRoot, slide) {
|
|
|
2128
2158
|
const src = slash(filepath.startsWith("@/") ? path.resolve(userRoot, filepath.slice(2)) : path.resolve(dir, filepath));
|
|
2129
2159
|
lang = lang.trim() || path.extname(filepath).slice(1);
|
|
2130
2160
|
meta = meta.trim();
|
|
2161
|
+
if (!isPathInsideRoots(src, allowedRoots)) throw new Error(`Code snippet path escapes the project root: ${src}`);
|
|
2131
2162
|
if (!(fs.existsSync(src) && fs.statSync(src).isFile())) throw new Error(`Code snippet path not found: ${src}`);
|
|
2132
2163
|
let content = fs.readFileSync(src, "utf8");
|
|
2133
2164
|
if (regionName) {
|
|
@@ -2143,7 +2174,12 @@ function resolveSnippetImport(lineText, userRoot, slide) {
|
|
|
2143
2174
|
src
|
|
2144
2175
|
};
|
|
2145
2176
|
}
|
|
2146
|
-
function MarkdownItSnippet(md, { userRoot, data: { watchFiles, slides } }) {
|
|
2177
|
+
function MarkdownItSnippet(md, { userRoot, userWorkspaceRoot, roots, data: { watchFiles, slides } }) {
|
|
2178
|
+
const allowedRoots = [...new Set([
|
|
2179
|
+
userWorkspaceRoot,
|
|
2180
|
+
userRoot,
|
|
2181
|
+
...roots ?? []
|
|
2182
|
+
].filter(Boolean))];
|
|
2147
2183
|
md.block.ruler.before("fence", "snippet_import", (state, startLine, _endLine, silent) => {
|
|
2148
2184
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
2149
2185
|
const max = state.eMarks[startLine];
|
|
@@ -2156,7 +2192,7 @@ function MarkdownItSnippet(md, { userRoot, data: { watchFiles, slides } }) {
|
|
|
2156
2192
|
console.warn(yellow(`[markdown-it-snippet] Snippet syntax is not supported in ${state.env.id || "unknown source"}. Skipped.`));
|
|
2157
2193
|
return false;
|
|
2158
2194
|
}
|
|
2159
|
-
const snippet = resolveSnippetImport(lineText, userRoot, slide);
|
|
2195
|
+
const snippet = resolveSnippetImport(lineText, userRoot, slide, allowedRoots);
|
|
2160
2196
|
if (!snippet) return false;
|
|
2161
2197
|
const { content, filepath, src } = snippet;
|
|
2162
2198
|
let { lang, meta } = snippet;
|
|
@@ -2579,6 +2615,505 @@ async function createMarkdownPlugin(options, { markdown: mdOptions }) {
|
|
|
2579
2615
|
});
|
|
2580
2616
|
}
|
|
2581
2617
|
//#endregion
|
|
2618
|
+
//#region node/mcp/operations.ts
|
|
2619
|
+
/**
|
|
2620
|
+
* Resolve a rendered slide (1-based, as displayed in the presentation) or
|
|
2621
|
+
* throw a descriptive error.
|
|
2622
|
+
*/
|
|
2623
|
+
function resolveSlide(data, no) {
|
|
2624
|
+
const slide = data.slides[no - 1];
|
|
2625
|
+
if (!slide) throw new Error(`Slide ${no} does not exist. The deck has ${data.slides.length} slides (1-${data.slides.length}).`);
|
|
2626
|
+
return slide;
|
|
2627
|
+
}
|
|
2628
|
+
function getMarkdown(data, source) {
|
|
2629
|
+
const md = data.markdownFiles[source.filepath];
|
|
2630
|
+
if (!md) throw new Error(`Markdown file not loaded: ${source.filepath}`);
|
|
2631
|
+
return md;
|
|
2632
|
+
}
|
|
2633
|
+
function assertNotEntryHeadmatter(data, source, action) {
|
|
2634
|
+
if (source.filepath === data.entry.filepath && data.entry.slides.indexOf(source) === 0) throw new Error(`Cannot ${action} the first slide of the entry file: its frontmatter is the deck headmatter (global configuration). Edit its content with the update tool instead, or operate on the following slides.`);
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Apply a `SlidePatch` to the slide source and save the markdown file.
|
|
2638
|
+
*
|
|
2639
|
+
* Note this only mutates the *source* slide (`slide.source`), not the
|
|
2640
|
+
* rendered `SlideInfo`, so a running dev server will pick up the change from
|
|
2641
|
+
* disk like an external edit and push HMR updates to connected clients.
|
|
2642
|
+
*/
|
|
2643
|
+
async function applySlidePatch(data, no, patch) {
|
|
2644
|
+
const slide = resolveSlide(data, no);
|
|
2645
|
+
const source = slide.source;
|
|
2646
|
+
if (patch.content != null) source.content = patch.content;
|
|
2647
|
+
if (patch.frontmatterRaw != null) if (patch.frontmatterRaw.trim() === "") source.frontmatterDoc = source.frontmatterStyle = void 0;
|
|
2648
|
+
else {
|
|
2649
|
+
const parsed = YAML.parseDocument(patch.frontmatterRaw);
|
|
2650
|
+
if (parsed.errors.length) throw new Error(`Invalid YAML frontmatter: ${parsed.errors.map((e) => e.message).join("; ")}`);
|
|
2651
|
+
source.frontmatterDoc = parsed;
|
|
2652
|
+
}
|
|
2653
|
+
if (patch.note != null) source.note = patch.note;
|
|
2654
|
+
if (patch.frontmatter) updateFrontmatterPatch(source, patch.frontmatter);
|
|
2655
|
+
parser.prettifySlide(source);
|
|
2656
|
+
return {
|
|
2657
|
+
slide,
|
|
2658
|
+
fileContent: await parser.save(getMarkdown(data, source))
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Insert a new slide after an existing one, in the same markdown file.
|
|
2663
|
+
*/
|
|
2664
|
+
async function insertSlide(data, options) {
|
|
2665
|
+
const anchor = resolveSlide(data, options.after);
|
|
2666
|
+
const md = getMarkdown(data, anchor.source);
|
|
2667
|
+
const frontmatter = options.frontmatter ?? {};
|
|
2668
|
+
const doc = Object.keys(frontmatter).length > 0 ? new YAML.Document(frontmatter) : void 0;
|
|
2669
|
+
const source = {
|
|
2670
|
+
filepath: anchor.source.filepath,
|
|
2671
|
+
index: 0,
|
|
2672
|
+
start: 0,
|
|
2673
|
+
contentStart: 0,
|
|
2674
|
+
end: 0,
|
|
2675
|
+
raw: "",
|
|
2676
|
+
revision: "",
|
|
2677
|
+
content: options.content,
|
|
2678
|
+
contentRaw: options.content,
|
|
2679
|
+
frontmatter,
|
|
2680
|
+
frontmatterDoc: doc,
|
|
2681
|
+
frontmatterStyle: doc ? "frontmatter" : void 0,
|
|
2682
|
+
frontmatterRaw: doc?.toString(),
|
|
2683
|
+
note: options.note?.trim() || void 0
|
|
2684
|
+
};
|
|
2685
|
+
parser.prettifySlide(source);
|
|
2686
|
+
const anchorIdx = md.slides.indexOf(anchor.source);
|
|
2687
|
+
md.slides.splice(anchorIdx + 1, 0, source);
|
|
2688
|
+
const fileContent = await parser.save(md);
|
|
2689
|
+
return {
|
|
2690
|
+
source,
|
|
2691
|
+
filepath: md.filepath,
|
|
2692
|
+
fileContent
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Remove a slide from its source markdown file.
|
|
2697
|
+
*/
|
|
2698
|
+
async function removeSlide(data, no) {
|
|
2699
|
+
const slide = resolveSlide(data, no);
|
|
2700
|
+
assertNotEntryHeadmatter(data, slide.source, "remove");
|
|
2701
|
+
const md = getMarkdown(data, slide.source);
|
|
2702
|
+
const idx = md.slides.indexOf(slide.source);
|
|
2703
|
+
if (idx < 0) throw new Error(`Slide ${no} is out of sync with its source file. Try again.`);
|
|
2704
|
+
md.slides.splice(idx, 1);
|
|
2705
|
+
const fileContent = await parser.save(md);
|
|
2706
|
+
return {
|
|
2707
|
+
removed: slide,
|
|
2708
|
+
filepath: md.filepath,
|
|
2709
|
+
fileContent
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Move a slide before or after another slide within the same markdown file.
|
|
2714
|
+
*/
|
|
2715
|
+
async function moveSlide(data, options) {
|
|
2716
|
+
const { from, before, after } = options;
|
|
2717
|
+
if (before == null === (after == null)) throw new Error("Specify exactly one of `before` or `after`.");
|
|
2718
|
+
const anchorNo = before ?? after;
|
|
2719
|
+
if (anchorNo === from) throw new Error("The `before`/`after` anchor must be a different slide than `from`.");
|
|
2720
|
+
const slide = resolveSlide(data, from);
|
|
2721
|
+
const anchor = resolveSlide(data, anchorNo);
|
|
2722
|
+
if (slide.source.filepath !== anchor.source.filepath) throw new Error(`Cannot move a slide across markdown files: slide ${from} is in "${slide.source.filepath}" but slide ${anchorNo} is in "${anchor.source.filepath}" (imported with \`src:\`). Move it within its own file, or edit the \`src:\` imports in the entry file manually.`);
|
|
2723
|
+
assertNotEntryHeadmatter(data, slide.source, "move");
|
|
2724
|
+
if (before != null) assertNotEntryHeadmatter(data, anchor.source, "insert a slide before");
|
|
2725
|
+
const md = getMarkdown(data, slide.source);
|
|
2726
|
+
const fromIdx = md.slides.indexOf(slide.source);
|
|
2727
|
+
if (fromIdx < 0) throw new Error(`Slide ${from} is out of sync with its source file. Try again.`);
|
|
2728
|
+
md.slides.splice(fromIdx, 1);
|
|
2729
|
+
const anchorIdx = md.slides.indexOf(anchor.source);
|
|
2730
|
+
if (anchorIdx < 0) throw new Error(`Slide ${anchorNo} is out of sync with its source file. Try again.`);
|
|
2731
|
+
md.slides.splice(before != null ? anchorIdx : anchorIdx + 1, 0, slide.source);
|
|
2732
|
+
const fileContent = await parser.save(md);
|
|
2733
|
+
return {
|
|
2734
|
+
moved: slide,
|
|
2735
|
+
anchor,
|
|
2736
|
+
filepath: md.filepath,
|
|
2737
|
+
fileContent
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
//#endregion
|
|
2741
|
+
//#region node/mcp/server.ts
|
|
2742
|
+
function result(data) {
|
|
2743
|
+
return { content: [{
|
|
2744
|
+
type: "text",
|
|
2745
|
+
text: typeof data === "string" ? data : JSON.stringify(data, null, 2)
|
|
2746
|
+
}] };
|
|
2747
|
+
}
|
|
2748
|
+
function slideSummary(slide) {
|
|
2749
|
+
return {
|
|
2750
|
+
no: slide.index + 1,
|
|
2751
|
+
title: slide.title ?? null,
|
|
2752
|
+
...slide.frontmatter.layout ? { layout: slide.frontmatter.layout } : {},
|
|
2753
|
+
file: slide.source.filepath,
|
|
2754
|
+
hasNote: !!slide.source.note,
|
|
2755
|
+
...slide.importChain?.length ? { importedBySrcDirective: true } : {}
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
const noSchema = z.number().int().min(1).describe("Slide number (1-based, as displayed in the presentation)");
|
|
2759
|
+
const frontmatterSchema = z.record(z.any()).optional().describe("Slide frontmatter (YAML headmatter of the slide) as an object, e.g. { \"layout\": \"two-cols\" }");
|
|
2760
|
+
/**
|
|
2761
|
+
* Create the Slidev MCP server exposing tools for agents to inspect, edit,
|
|
2762
|
+
* and (with a running dev server) navigate a slides deck.
|
|
2763
|
+
*
|
|
2764
|
+
* The same tool set is served over the dev server HTTP endpoint (`/__mcp`)
|
|
2765
|
+
* and the `slidev mcp` stdio command.
|
|
2766
|
+
*/
|
|
2767
|
+
function createSlidevMcpServer(ctx) {
|
|
2768
|
+
const server = new McpServer({
|
|
2769
|
+
name: "slidev",
|
|
2770
|
+
version: ctx.version
|
|
2771
|
+
}, { instructions: [
|
|
2772
|
+
"Tools for working with a Slidev (https://sli.dev) slides deck.",
|
|
2773
|
+
"A deck is a Markdown file where slides are separated by `---`; each slide can have YAML frontmatter, Markdown/Vue content, and a speaker note (trailing HTML comment).",
|
|
2774
|
+
"Slides are addressed by their rendered 1-based number, matching the slide numbers shown in the presentation.",
|
|
2775
|
+
"After editing tools run, a running dev server hot-reloads the presentation automatically."
|
|
2776
|
+
].join("\n") });
|
|
2777
|
+
server.registerTool("slidev-get-info", {
|
|
2778
|
+
title: "Get deck info",
|
|
2779
|
+
description: "Get an overview of the Slidev deck: entry file, title, slide count, markdown files, and (when a dev server is running) the server URL and current position of the live presentation.",
|
|
2780
|
+
annotations: { readOnlyHint: true }
|
|
2781
|
+
}, async () => {
|
|
2782
|
+
const data = await ctx.getData();
|
|
2783
|
+
const nav = ctx.nav?.getState();
|
|
2784
|
+
return result({
|
|
2785
|
+
slidevVersion: ctx.version,
|
|
2786
|
+
entry: ctx.entry,
|
|
2787
|
+
title: data.headmatter.title ?? data.slides[0]?.title ?? null,
|
|
2788
|
+
theme: data.config?.theme ?? data.headmatter.theme ?? null,
|
|
2789
|
+
totalSlides: data.slides.length,
|
|
2790
|
+
markdownFiles: Object.keys(data.markdownFiles),
|
|
2791
|
+
...ctx.getServerUrl?.() ? { server: {
|
|
2792
|
+
url: ctx.getServerUrl(),
|
|
2793
|
+
currentPage: nav?.page || null,
|
|
2794
|
+
currentClicks: nav?.page ? nav.clicks : null
|
|
2795
|
+
} } : {}
|
|
2796
|
+
});
|
|
2797
|
+
});
|
|
2798
|
+
server.registerTool("slidev-list-slides", {
|
|
2799
|
+
title: "List slides",
|
|
2800
|
+
description: "List all slides of the deck with their number, title, layout, and source file. Slides hidden with `hide`/`disabled` frontmatter are not included.",
|
|
2801
|
+
annotations: { readOnlyHint: true }
|
|
2802
|
+
}, async () => {
|
|
2803
|
+
return result((await ctx.getData()).slides.map(slideSummary));
|
|
2804
|
+
});
|
|
2805
|
+
server.registerTool("slidev-get-slide", {
|
|
2806
|
+
title: "Get slide",
|
|
2807
|
+
description: "Get the full source of one slide: frontmatter, Markdown content, and speaker note.",
|
|
2808
|
+
inputSchema: { no: noSchema },
|
|
2809
|
+
annotations: { readOnlyHint: true }
|
|
2810
|
+
}, async ({ no }) => {
|
|
2811
|
+
const slide = resolveSlide(await ctx.getData(), no);
|
|
2812
|
+
return result({
|
|
2813
|
+
...slideSummary(slide),
|
|
2814
|
+
frontmatter: slide.source.frontmatter,
|
|
2815
|
+
content: slide.source.content.trim(),
|
|
2816
|
+
note: slide.source.note ?? null,
|
|
2817
|
+
...slide.importChain?.length ? { importedBy: slide.importChain.map((s) => `${s.filepath}#${s.index + 1}`) } : {}
|
|
2818
|
+
});
|
|
2819
|
+
});
|
|
2820
|
+
server.registerTool("slidev-update-slide", {
|
|
2821
|
+
title: "Update slide",
|
|
2822
|
+
description: "Update the content, speaker note, and/or frontmatter of a slide. Only the provided fields are changed. Pass an empty string to clear the content or note. In `frontmatter`, only the given keys are patched; pass `null` as a value to delete that key.",
|
|
2823
|
+
inputSchema: {
|
|
2824
|
+
no: noSchema,
|
|
2825
|
+
content: z.string().optional().describe("New Markdown content of the slide (without frontmatter and note)"),
|
|
2826
|
+
note: z.string().optional().describe("New speaker note (Markdown, stored as a trailing HTML comment)"),
|
|
2827
|
+
frontmatter: frontmatterSchema
|
|
2828
|
+
}
|
|
2829
|
+
}, async ({ no, content, note, frontmatter }) => {
|
|
2830
|
+
if (content == null && note == null && frontmatter == null) throw new Error("Nothing to update: provide at least one of `content`, `note`, or `frontmatter`.");
|
|
2831
|
+
const { slide } = await applySlidePatch(await ctx.getData(), no, {
|
|
2832
|
+
content,
|
|
2833
|
+
note,
|
|
2834
|
+
frontmatter
|
|
2835
|
+
});
|
|
2836
|
+
return result(`Updated slide ${no} in ${slide.source.filepath}.`);
|
|
2837
|
+
});
|
|
2838
|
+
server.registerTool("slidev-insert-slide", {
|
|
2839
|
+
title: "Insert slide",
|
|
2840
|
+
description: "Insert a new slide after an existing slide (into the same markdown file). To add a slide at the very end, pass the last slide number.",
|
|
2841
|
+
inputSchema: {
|
|
2842
|
+
after: z.number().int().min(1).describe("Slide number (1-based) after which the new slide is inserted"),
|
|
2843
|
+
content: z.string().describe("Markdown content of the new slide"),
|
|
2844
|
+
frontmatter: frontmatterSchema,
|
|
2845
|
+
note: z.string().optional().describe("Speaker note of the new slide")
|
|
2846
|
+
}
|
|
2847
|
+
}, async ({ after, content, frontmatter, note }) => {
|
|
2848
|
+
const { filepath } = await insertSlide(await ctx.getData(), {
|
|
2849
|
+
after,
|
|
2850
|
+
content,
|
|
2851
|
+
frontmatter,
|
|
2852
|
+
note
|
|
2853
|
+
});
|
|
2854
|
+
return result(`Inserted a new slide after slide ${after} in ${filepath}. Slide numbers after it have shifted; list the slides again if needed.`);
|
|
2855
|
+
});
|
|
2856
|
+
server.registerTool("slidev-remove-slide", {
|
|
2857
|
+
title: "Remove slide",
|
|
2858
|
+
description: "Remove a slide from the deck (deletes it from its source markdown file).",
|
|
2859
|
+
inputSchema: { no: noSchema },
|
|
2860
|
+
annotations: { destructiveHint: true }
|
|
2861
|
+
}, async ({ no }) => {
|
|
2862
|
+
const { removed, filepath } = await removeSlide(await ctx.getData(), no);
|
|
2863
|
+
return result(`Removed slide ${no}${removed.title ? ` ("${removed.title}")` : ""} from ${filepath}. Slide numbers after it have shifted; list the slides again if needed.`);
|
|
2864
|
+
});
|
|
2865
|
+
server.registerTool("slidev-move-slide", {
|
|
2866
|
+
title: "Move slide",
|
|
2867
|
+
description: "Move a slide before or after another slide to reorder the deck. Both slides must be in the same markdown file. To swap two adjacent slides, move one after the other.",
|
|
2868
|
+
inputSchema: {
|
|
2869
|
+
from: z.number().int().min(1).describe("Slide number (1-based) of the slide to move"),
|
|
2870
|
+
before: z.number().int().min(1).optional().describe("Move the slide right before this slide number"),
|
|
2871
|
+
after: z.number().int().min(1).optional().describe("Move the slide right after this slide number")
|
|
2872
|
+
}
|
|
2873
|
+
}, async ({ from, before, after }) => {
|
|
2874
|
+
const { filepath } = await moveSlide(await ctx.getData(), {
|
|
2875
|
+
from,
|
|
2876
|
+
before,
|
|
2877
|
+
after
|
|
2878
|
+
});
|
|
2879
|
+
return result(`Moved slide ${from} ${before != null ? `before slide ${before}` : `after slide ${after}`} in ${filepath}. Slide numbers have shifted; list the slides again if needed.`);
|
|
2880
|
+
});
|
|
2881
|
+
if (ctx.nav) {
|
|
2882
|
+
const nav = ctx.nav;
|
|
2883
|
+
server.registerTool("slidev-goto-slide", {
|
|
2884
|
+
title: "Go to slide",
|
|
2885
|
+
description: "Navigate the live presentation (all connected browsers) to a given slide, e.g. to visually verify a slide after editing it.",
|
|
2886
|
+
inputSchema: {
|
|
2887
|
+
no: noSchema,
|
|
2888
|
+
clicks: z.number().int().min(0).optional().describe("Click animation step to reveal (defaults to 0)")
|
|
2889
|
+
},
|
|
2890
|
+
annotations: { idempotentHint: true }
|
|
2891
|
+
}, async ({ no, clicks }) => {
|
|
2892
|
+
resolveSlide(await ctx.getData(), no);
|
|
2893
|
+
nav.go(no, clicks ?? 0);
|
|
2894
|
+
return result(`Navigated the presentation to slide ${no}${clicks ? ` (click ${clicks})` : ""}.`);
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
return server;
|
|
2898
|
+
}
|
|
2899
|
+
//#endregion
|
|
2900
|
+
//#region node/integrations/drawings.ts
|
|
2901
|
+
/**
|
|
2902
|
+
* Whether `key` is a safe bare slide id that can be joined onto `dir` without
|
|
2903
|
+
* escaping it. `loadDrawings` only ever produces numeric keys, so writes
|
|
2904
|
+
* should only ever accept numeric keys too.
|
|
2905
|
+
*/
|
|
2906
|
+
function isSafeDrawingKey(dir, key) {
|
|
2907
|
+
if (!/^\d+$/.test(key)) return false;
|
|
2908
|
+
const rel = relative(dir, join(dir, `${key}.svg`));
|
|
2909
|
+
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
2910
|
+
}
|
|
2911
|
+
function resolveDrawingsDir(options) {
|
|
2912
|
+
return options.data.config.drawings.persist ? resolve(dirname(options.entry), options.data.config.drawings.persist) : void 0;
|
|
2913
|
+
}
|
|
2914
|
+
async function loadDrawings(options) {
|
|
2915
|
+
const dir = resolveDrawingsDir(options);
|
|
2916
|
+
if (!dir || !existsSync(dir)) return {};
|
|
2917
|
+
const files = await fg("*.svg", {
|
|
2918
|
+
onlyFiles: true,
|
|
2919
|
+
cwd: dir,
|
|
2920
|
+
absolute: true,
|
|
2921
|
+
suppressErrors: true
|
|
2922
|
+
});
|
|
2923
|
+
const obj = {};
|
|
2924
|
+
await Promise.all(files.map(async (path) => {
|
|
2925
|
+
const num = +basename(path, ".svg");
|
|
2926
|
+
if (Number.isNaN(num)) return;
|
|
2927
|
+
const lines = (await fs$1.readFile(path, "utf8")).split(/\n/g);
|
|
2928
|
+
obj[num.toString()] = lines.slice(1, -1).join("\n");
|
|
2929
|
+
}));
|
|
2930
|
+
return obj;
|
|
2931
|
+
}
|
|
2932
|
+
async function writeDrawings(options, drawing) {
|
|
2933
|
+
const dir = resolveDrawingsDir(options);
|
|
2934
|
+
if (!dir) return;
|
|
2935
|
+
const width = options.data.config.canvasWidth;
|
|
2936
|
+
const SVG_HEAD = `<svg width="${width}" height="${Math.round(width / options.data.config.aspectRatio)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`;
|
|
2937
|
+
await fs$1.mkdir(dir, { recursive: true });
|
|
2938
|
+
return Promise.all(Object.entries(drawing).map(async ([key, value]) => {
|
|
2939
|
+
if (!value) return;
|
|
2940
|
+
if (!isSafeDrawingKey(dir, key)) {
|
|
2941
|
+
console.warn(`[slidev] Ignoring drawing with unsafe key: ${key}`);
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
const svg = `${SVG_HEAD}\n${value}\n</svg>`;
|
|
2945
|
+
await fs$1.writeFile(join(dir, `${key}.svg`), svg, "utf-8");
|
|
2946
|
+
}));
|
|
2947
|
+
}
|
|
2948
|
+
//#endregion
|
|
2949
|
+
//#region node/integrations/snapshots.ts
|
|
2950
|
+
function resolveSnapshotsDir(options) {
|
|
2951
|
+
return resolve(dirname(options.entry), ".slidev/snapshots");
|
|
2952
|
+
}
|
|
2953
|
+
async function loadSnapshots(options) {
|
|
2954
|
+
const dir = resolveSnapshotsDir(options);
|
|
2955
|
+
const file = join(dir, "snapshots.json");
|
|
2956
|
+
if (!dir || !existsSync(file)) return {};
|
|
2957
|
+
return JSON.parse(await fs$1.readFile(file, "utf8"));
|
|
2958
|
+
}
|
|
2959
|
+
async function writeSnapshots(options, data) {
|
|
2960
|
+
const dir = resolveSnapshotsDir(options);
|
|
2961
|
+
if (!dir) return;
|
|
2962
|
+
await fs$1.mkdir(dir, { recursive: true });
|
|
2963
|
+
await fs$1.writeFile(join(dir, "snapshots.json"), JSON.stringify(data, null, 2), "utf-8");
|
|
2964
|
+
}
|
|
2965
|
+
//#endregion
|
|
2966
|
+
//#region node/vite/serverRef.ts
|
|
2967
|
+
const serverRefStates = /* @__PURE__ */ new WeakMap();
|
|
2968
|
+
/**
|
|
2969
|
+
* Access the server-side state shared with the clients via
|
|
2970
|
+
* `vite-plugin-vue-server-ref` (e.g. `nav` holds the current position of the
|
|
2971
|
+
* live presentation).
|
|
2972
|
+
*/
|
|
2973
|
+
function getServerRefState(options) {
|
|
2974
|
+
return serverRefStates.get(options);
|
|
2975
|
+
}
|
|
2976
|
+
async function createServerRefPlugin(options, pluginOptions) {
|
|
2977
|
+
const state = {
|
|
2978
|
+
sync: false,
|
|
2979
|
+
nav: {
|
|
2980
|
+
page: 0,
|
|
2981
|
+
clicks: 0,
|
|
2982
|
+
timer: {
|
|
2983
|
+
status: "stopped",
|
|
2984
|
+
slides: {},
|
|
2985
|
+
startedAt: 0,
|
|
2986
|
+
pausedAt: 0
|
|
2987
|
+
}
|
|
2988
|
+
},
|
|
2989
|
+
drawings: await loadDrawings(options),
|
|
2990
|
+
snapshots: await loadSnapshots(options),
|
|
2991
|
+
...pluginOptions.serverRef?.state
|
|
2992
|
+
};
|
|
2993
|
+
serverRefStates.set(options, state);
|
|
2994
|
+
return ServerRef({
|
|
2995
|
+
debug: false,
|
|
2996
|
+
state,
|
|
2997
|
+
onChanged(key, data, patch, timestamp) {
|
|
2998
|
+
pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp);
|
|
2999
|
+
if (options.data.config.drawings.persist && key === "drawings") writeDrawings(options, patch ?? data);
|
|
3000
|
+
if (key === "snapshots") writeSnapshots(options, data);
|
|
3001
|
+
}
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
const SERVER_REF_WS_EVENT = "vue-server-ref";
|
|
3005
|
+
const SERVER_REF_NAV_MODULE_IDS = ["/@server-reactive/nav", "/@server-ref/nav"];
|
|
3006
|
+
/**
|
|
3007
|
+
* Serves the Slidev MCP (Model Context Protocol) server on the dev server at
|
|
3008
|
+
* `/__mcp` (streamable HTTP transport), so AI agents can inspect, edit, and
|
|
3009
|
+
* navigate the slides. Can be disabled with `mcp: false` in the headmatter.
|
|
3010
|
+
*/
|
|
3011
|
+
function createMcpPlugin(options) {
|
|
3012
|
+
return {
|
|
3013
|
+
name: "slidev:mcp",
|
|
3014
|
+
apply: "serve",
|
|
3015
|
+
configureServer(server) {
|
|
3016
|
+
if (options.mode !== "dev") return;
|
|
3017
|
+
const ctx = {
|
|
3018
|
+
version,
|
|
3019
|
+
entry: options.entry,
|
|
3020
|
+
getData: () => options.data,
|
|
3021
|
+
getServerUrl: () => server.resolvedUrls?.local[0],
|
|
3022
|
+
nav: {
|
|
3023
|
+
getState: () => {
|
|
3024
|
+
const nav = getServerRefState(options)?.nav;
|
|
3025
|
+
return {
|
|
3026
|
+
page: nav?.page ?? 0,
|
|
3027
|
+
clicks: nav?.clicks ?? 0
|
|
3028
|
+
};
|
|
3029
|
+
},
|
|
3030
|
+
go: (page, clicks) => navigateClients(server, options, page, clicks)
|
|
3031
|
+
}
|
|
3032
|
+
};
|
|
3033
|
+
server.middlewares.use(async (req, res, next) => {
|
|
3034
|
+
if (req.url?.split("?")[0]?.replace(/\/$/, "") !== "/__mcp") return next();
|
|
3035
|
+
if (options.data.config.mcp === false) {
|
|
3036
|
+
res.statusCode = 403;
|
|
3037
|
+
res.setHeader("Content-Type", "application/json");
|
|
3038
|
+
res.end(JSON.stringify({
|
|
3039
|
+
jsonrpc: "2.0",
|
|
3040
|
+
error: {
|
|
3041
|
+
code: -32e3,
|
|
3042
|
+
message: "The Slidev MCP server is disabled (`mcp: false` in the headmatter)"
|
|
3043
|
+
},
|
|
3044
|
+
id: null
|
|
3045
|
+
}));
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
try {
|
|
3049
|
+
const mcp = createSlidevMcpServer(ctx);
|
|
3050
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
3051
|
+
res.on("close", () => {
|
|
3052
|
+
transport.close();
|
|
3053
|
+
mcp.close();
|
|
3054
|
+
});
|
|
3055
|
+
await mcp.connect(transport);
|
|
3056
|
+
await transport.handleRequest(req, res);
|
|
3057
|
+
} catch (e) {
|
|
3058
|
+
console.error("[slidev] Error handling MCP request:", e);
|
|
3059
|
+
if (!res.headersSent) {
|
|
3060
|
+
res.statusCode = 500;
|
|
3061
|
+
res.setHeader("Content-Type", "application/json");
|
|
3062
|
+
res.end(JSON.stringify({
|
|
3063
|
+
jsonrpc: "2.0",
|
|
3064
|
+
error: {
|
|
3065
|
+
code: -32603,
|
|
3066
|
+
message: "Internal server error"
|
|
3067
|
+
},
|
|
3068
|
+
id: null
|
|
3069
|
+
}));
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Drive all connected clients to the given slide by patching the shared
|
|
3078
|
+
* `nav` state, mimicking what `vite-plugin-vue-server-ref` does when a
|
|
3079
|
+
* presenter or remote-control client navigates.
|
|
3080
|
+
*/
|
|
3081
|
+
function navigateClients(server, options, page, clicks) {
|
|
3082
|
+
const time = Date.now();
|
|
3083
|
+
const nav = getServerRefState(options)?.nav;
|
|
3084
|
+
if (nav) {
|
|
3085
|
+
nav.page = page;
|
|
3086
|
+
nav.clicks = clicks;
|
|
3087
|
+
nav.lastUpdate = {
|
|
3088
|
+
id: "slidev-mcp",
|
|
3089
|
+
type: "presenter",
|
|
3090
|
+
time
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
for (const id of SERVER_REF_NAV_MODULE_IDS) {
|
|
3094
|
+
const module = server.moduleGraph.getModuleById(id);
|
|
3095
|
+
if (module) server.moduleGraph.invalidateModule(module);
|
|
3096
|
+
}
|
|
3097
|
+
for (const type of ["presenter", "viewer"]) server.hot.send({
|
|
3098
|
+
type: "custom",
|
|
3099
|
+
event: SERVER_REF_WS_EVENT,
|
|
3100
|
+
data: {
|
|
3101
|
+
key: "nav",
|
|
3102
|
+
source: "slidev-mcp",
|
|
3103
|
+
patch: {
|
|
3104
|
+
page,
|
|
3105
|
+
clicks,
|
|
3106
|
+
lastUpdate: {
|
|
3107
|
+
id: "slidev-mcp",
|
|
3108
|
+
type,
|
|
3109
|
+
time
|
|
3110
|
+
}
|
|
3111
|
+
},
|
|
3112
|
+
timestamp: time
|
|
3113
|
+
}
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
//#endregion
|
|
2582
3117
|
//#region node/vite/monacoTypes.ts
|
|
2583
3118
|
function createMonacoTypesLoader({ userRoot, utils }) {
|
|
2584
3119
|
return {
|
|
@@ -2648,6 +3183,34 @@ function createPatchMonacoSourceMapPlugin() {
|
|
|
2648
3183
|
};
|
|
2649
3184
|
}
|
|
2650
3185
|
//#endregion
|
|
3186
|
+
//#region node/vite/pwa.ts
|
|
3187
|
+
function createPWAPlugin(options) {
|
|
3188
|
+
const matchMode = (mode) => mode === true || mode === options.mode;
|
|
3189
|
+
const enabled = matchMode(options.data.config.pwa);
|
|
3190
|
+
const base = options.base ?? "/";
|
|
3191
|
+
const title = options.data.config.title || "Slidev";
|
|
3192
|
+
return VitePWA({
|
|
3193
|
+
disable: !enabled,
|
|
3194
|
+
registerType: "autoUpdate",
|
|
3195
|
+
injectRegister: null,
|
|
3196
|
+
base,
|
|
3197
|
+
workbox: {
|
|
3198
|
+
maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
|
|
3199
|
+
globPatterns: ["**/*.{js,css,html,woff,woff2,ttf,png,jpg,jpeg,svg,gif,webp,avif,ico,mp4,webm,ogv,mov,m4v,mp3,wav,ogg,oga,m4a,aac,flac,opus,weba}"],
|
|
3200
|
+
navigateFallback: "index.html",
|
|
3201
|
+
navigateFallbackDenylist: [/^\/(presenter|notes|overview|print|export)/]
|
|
3202
|
+
},
|
|
3203
|
+
manifest: {
|
|
3204
|
+
name: title,
|
|
3205
|
+
short_name: "Slidev",
|
|
3206
|
+
start_url: base,
|
|
3207
|
+
display: "fullscreen",
|
|
3208
|
+
theme_color: "#121212",
|
|
3209
|
+
background_color: "#121212"
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
//#endregion
|
|
2651
3214
|
//#region node/vite/remoteAssets.ts
|
|
2652
3215
|
async function createRemoteAssetsPlugin({ data: { config }, mode }, pluginOptions) {
|
|
2653
3216
|
if (!(config.remoteAssets === true || config.remoteAssets === mode)) return;
|
|
@@ -2667,86 +3230,6 @@ async function createRemoteAssetsPlugin({ data: { config }, mode }, pluginOption
|
|
|
2667
3230
|
});
|
|
2668
3231
|
}
|
|
2669
3232
|
//#endregion
|
|
2670
|
-
//#region node/integrations/drawings.ts
|
|
2671
|
-
function resolveDrawingsDir(options) {
|
|
2672
|
-
return options.data.config.drawings.persist ? resolve(dirname(options.entry), options.data.config.drawings.persist) : void 0;
|
|
2673
|
-
}
|
|
2674
|
-
async function loadDrawings(options) {
|
|
2675
|
-
const dir = resolveDrawingsDir(options);
|
|
2676
|
-
if (!dir || !existsSync(dir)) return {};
|
|
2677
|
-
const files = await fg("*.svg", {
|
|
2678
|
-
onlyFiles: true,
|
|
2679
|
-
cwd: dir,
|
|
2680
|
-
absolute: true,
|
|
2681
|
-
suppressErrors: true
|
|
2682
|
-
});
|
|
2683
|
-
const obj = {};
|
|
2684
|
-
await Promise.all(files.map(async (path) => {
|
|
2685
|
-
const num = +basename(path, ".svg");
|
|
2686
|
-
if (Number.isNaN(num)) return;
|
|
2687
|
-
const lines = (await fs$1.readFile(path, "utf8")).split(/\n/g);
|
|
2688
|
-
obj[num.toString()] = lines.slice(1, -1).join("\n");
|
|
2689
|
-
}));
|
|
2690
|
-
return obj;
|
|
2691
|
-
}
|
|
2692
|
-
async function writeDrawings(options, drawing) {
|
|
2693
|
-
const dir = resolveDrawingsDir(options);
|
|
2694
|
-
if (!dir) return;
|
|
2695
|
-
const width = options.data.config.canvasWidth;
|
|
2696
|
-
const SVG_HEAD = `<svg width="${width}" height="${Math.round(width / options.data.config.aspectRatio)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`;
|
|
2697
|
-
await fs$1.mkdir(dir, { recursive: true });
|
|
2698
|
-
return Promise.all(Object.entries(drawing).map(async ([key, value]) => {
|
|
2699
|
-
if (!value) return;
|
|
2700
|
-
const svg = `${SVG_HEAD}\n${value}\n</svg>`;
|
|
2701
|
-
await fs$1.writeFile(join(dir, `${key}.svg`), svg, "utf-8");
|
|
2702
|
-
}));
|
|
2703
|
-
}
|
|
2704
|
-
//#endregion
|
|
2705
|
-
//#region node/integrations/snapshots.ts
|
|
2706
|
-
function resolveSnapshotsDir(options) {
|
|
2707
|
-
return resolve(dirname(options.entry), ".slidev/snapshots");
|
|
2708
|
-
}
|
|
2709
|
-
async function loadSnapshots(options) {
|
|
2710
|
-
const dir = resolveSnapshotsDir(options);
|
|
2711
|
-
const file = join(dir, "snapshots.json");
|
|
2712
|
-
if (!dir || !existsSync(file)) return {};
|
|
2713
|
-
return JSON.parse(await fs$1.readFile(file, "utf8"));
|
|
2714
|
-
}
|
|
2715
|
-
async function writeSnapshots(options, data) {
|
|
2716
|
-
const dir = resolveSnapshotsDir(options);
|
|
2717
|
-
if (!dir) return;
|
|
2718
|
-
await fs$1.mkdir(dir, { recursive: true });
|
|
2719
|
-
await fs$1.writeFile(join(dir, "snapshots.json"), JSON.stringify(data, null, 2), "utf-8");
|
|
2720
|
-
}
|
|
2721
|
-
//#endregion
|
|
2722
|
-
//#region node/vite/serverRef.ts
|
|
2723
|
-
async function createServerRefPlugin(options, pluginOptions) {
|
|
2724
|
-
return ServerRef({
|
|
2725
|
-
debug: false,
|
|
2726
|
-
state: {
|
|
2727
|
-
sync: false,
|
|
2728
|
-
nav: {
|
|
2729
|
-
page: 0,
|
|
2730
|
-
clicks: 0,
|
|
2731
|
-
timer: {
|
|
2732
|
-
status: "stopped",
|
|
2733
|
-
slides: {},
|
|
2734
|
-
startedAt: 0,
|
|
2735
|
-
pausedAt: 0
|
|
2736
|
-
}
|
|
2737
|
-
},
|
|
2738
|
-
drawings: await loadDrawings(options),
|
|
2739
|
-
snapshots: await loadSnapshots(options),
|
|
2740
|
-
...pluginOptions.serverRef?.state
|
|
2741
|
-
},
|
|
2742
|
-
onChanged(key, data, patch, timestamp) {
|
|
2743
|
-
pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp);
|
|
2744
|
-
if (options.data.config.drawings.persist && key === "drawings") writeDrawings(options, patch ?? data);
|
|
2745
|
-
if (key === "snapshots") writeSnapshots(options, data);
|
|
2746
|
-
}
|
|
2747
|
-
});
|
|
2748
|
-
}
|
|
2749
|
-
//#endregion
|
|
2750
3233
|
//#region node/vite/staticCopy.ts
|
|
2751
3234
|
async function createStaticCopyPlugin({ themeRoots, addonRoots }, pluginOptions) {
|
|
2752
3235
|
const publicDirs = [...themeRoots, ...addonRoots].map((i) => join(i, "public")).filter(existsSync);
|
|
@@ -2805,7 +3288,7 @@ const RE_VUE_QUERY_VUE = /\.vue\?vue/;
|
|
|
2805
3288
|
const RE_VUE_QUERY_V = /\.vue\?v=/;
|
|
2806
3289
|
const RE_MD_EXT = /\.md$/;
|
|
2807
3290
|
const RE_MD_QUERY_VUE = /\.md\?vue/;
|
|
2808
|
-
const customElements = new Set([
|
|
3291
|
+
const customElements = /* @__PURE__ */ new Set([
|
|
2809
3292
|
"annotation",
|
|
2810
3293
|
"math",
|
|
2811
3294
|
"menclose",
|
|
@@ -2863,6 +3346,7 @@ async function createVuePlugin(_options, pluginOptions) {
|
|
|
2863
3346
|
function ViteSlidevPlugin(options, pluginOptions = {}, serverOptions = {}) {
|
|
2864
3347
|
return Promise.all([
|
|
2865
3348
|
createSlidesLoader(options, serverOptions),
|
|
3349
|
+
createMcpPlugin(options),
|
|
2866
3350
|
createMarkdownPlugin(options, pluginOptions),
|
|
2867
3351
|
createLayoutWrapperPlugin(options),
|
|
2868
3352
|
createContextInjectionPlugin(),
|
|
@@ -2881,6 +3365,7 @@ function ViteSlidevPlugin(options, pluginOptions = {}, serverOptions = {}) {
|
|
|
2881
3365
|
createStaticCopyPlugin(options, pluginOptions),
|
|
2882
3366
|
createInspectPlugin(options, pluginOptions),
|
|
2883
3367
|
createPatchMonacoSourceMapPlugin(),
|
|
3368
|
+
createPWAPlugin(options),
|
|
2884
3369
|
setupVitePlugins(options)
|
|
2885
3370
|
]);
|
|
2886
3371
|
}
|
|
@@ -2920,4 +3405,4 @@ async function createServer$1(options, viteConfig = {}, serverOptions) {
|
|
|
2920
3405
|
return await createServer(await resolveViteConfigs(options, { optimizeDeps: { entries: [join(options.clientRoot, "main.ts")] } }, viteConfig, "serve", serverOptions));
|
|
2921
3406
|
}
|
|
2922
3407
|
//#endregion
|
|
2923
|
-
export {
|
|
3408
|
+
export { createDataUtils as a, getThemeMeta as c, loadSetups as d, updateFrontmatterPatch as f, createSlidevMcpServer as i, resolveTheme as l, resolveViteConfigs as n, resolveOptions as o, ViteSlidevPlugin as r, parser as s, createServer$1 as t, version as u };
|