2694 lines
91 KiB
JavaScript
2694 lines
91 KiB
JavaScript
|
|
import {
|
||
|
|
createResolver,
|
||
|
|
getRoots,
|
||
|
|
isInstalledGlobally,
|
||
|
|
resolveEntry,
|
||
|
|
resolveImportPath,
|
||
|
|
resolveImportUrl,
|
||
|
|
toAtFS
|
||
|
|
} from "./chunk-UNQ5DBLZ.js";
|
||
|
|
|
||
|
|
// package.json
|
||
|
|
var version = "0.50.0";
|
||
|
|
|
||
|
|
// node/integrations/themes.ts
|
||
|
|
import { join } from "node:path";
|
||
|
|
import fs from "fs-extra";
|
||
|
|
import { satisfies } from "semver";
|
||
|
|
var officialThemes = {
|
||
|
|
"none": "",
|
||
|
|
"default": "@slidev/theme-default",
|
||
|
|
"seriph": "@slidev/theme-seriph",
|
||
|
|
"apple-basic": "@slidev/theme-apple-basic",
|
||
|
|
"shibainu": "@slidev/theme-shibainu",
|
||
|
|
"bricks": "@slidev/theme-bricks"
|
||
|
|
};
|
||
|
|
var resolveTheme = createResolver("theme", officialThemes);
|
||
|
|
async function getThemeMeta(name, root) {
|
||
|
|
const path4 = join(root, "package.json");
|
||
|
|
if (!fs.existsSync(path4))
|
||
|
|
return {};
|
||
|
|
const { slidev = {}, engines = {} } = await fs.readJSON(path4);
|
||
|
|
if (engines.slidev && !satisfies(version, engines.slidev, { includePrerelease: true }))
|
||
|
|
throw new Error(`[slidev] theme "${name}" requires Slidev version range "${engines.slidev}" but found "${version}"`);
|
||
|
|
return slidev;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/parser.ts
|
||
|
|
import * as parser from "@slidev/parser/fs";
|
||
|
|
|
||
|
|
// node/options.ts
|
||
|
|
import path3 from "node:path";
|
||
|
|
import { objectMap as objectMap2, uniq as uniq5 } from "@antfu/utils";
|
||
|
|
import Debug from "debug";
|
||
|
|
import fg4 from "fast-glob";
|
||
|
|
import mm from "micromatch";
|
||
|
|
|
||
|
|
// node/integrations/addons.ts
|
||
|
|
import { resolve } from "node:path";
|
||
|
|
import fs2 from "fs-extra";
|
||
|
|
import { satisfies as satisfies2 } from "semver";
|
||
|
|
async function resolveAddons(addonsInConfig) {
|
||
|
|
const { userRoot, userPkgJson } = await getRoots();
|
||
|
|
const resolved = [];
|
||
|
|
const resolveAddonNameAndRoot = createResolver("addon", {});
|
||
|
|
async function resolveAddon(name, parent) {
|
||
|
|
const [, pkgRoot] = await resolveAddonNameAndRoot(name, parent);
|
||
|
|
if (!pkgRoot)
|
||
|
|
return;
|
||
|
|
resolved.push(pkgRoot);
|
||
|
|
const { slidev = {}, engines = {} } = await fs2.readJSON(resolve(pkgRoot, "package.json"));
|
||
|
|
if (engines.slidev && !satisfies2(version, engines.slidev, { includePrerelease: true }))
|
||
|
|
throw new Error(`[slidev] addon "${name}" requires Slidev version range "${engines.slidev}" but found "${version}"`);
|
||
|
|
if (Array.isArray(slidev.addons))
|
||
|
|
await Promise.all(slidev.addons.map((addon) => resolveAddon(addon, pkgRoot)));
|
||
|
|
}
|
||
|
|
if (Array.isArray(addonsInConfig))
|
||
|
|
await Promise.all(addonsInConfig.map((addon) => resolveAddon(addon, userRoot)));
|
||
|
|
if (Array.isArray(userPkgJson.slidev?.addons))
|
||
|
|
await Promise.all(userPkgJson.slidev.addons.map((addon) => resolveAddon(addon, userRoot)));
|
||
|
|
return resolved;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/setups/indexHtml.ts
|
||
|
|
import { existsSync as existsSync8, readFileSync as readFileSync2 } from "node:fs";
|
||
|
|
import { join as join13 } from "node:path";
|
||
|
|
import { slash as slash4 } from "@antfu/utils";
|
||
|
|
import { white, yellow as yellow2 } from "kolorist";
|
||
|
|
import { escapeHtml } from "markdown-it/lib/common/utils.mjs";
|
||
|
|
|
||
|
|
// node/commands/shared.ts
|
||
|
|
import { existsSync as existsSync7 } from "node:fs";
|
||
|
|
import { join as join12 } from "node:path";
|
||
|
|
import MarkdownIt from "markdown-it";
|
||
|
|
import { loadConfigFromFile, mergeConfig as mergeConfig2 } from "vite";
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-link.ts
|
||
|
|
function MarkdownItLink(md) {
|
||
|
|
const defaultRender = md.renderer.rules.link_open ?? ((tokens, idx, options, _env, self) => self.renderToken(tokens, idx, options));
|
||
|
|
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
|
||
|
|
const token = tokens[idx];
|
||
|
|
const hrefIndex = token.attrIndex("href");
|
||
|
|
const attr = token.attrs?.[hrefIndex];
|
||
|
|
const href = attr?.[1] ?? "";
|
||
|
|
if ("./#".includes(href[0]) || /^\d+$/.test(href)) {
|
||
|
|
token.tag = "Link";
|
||
|
|
attr[0] = "to";
|
||
|
|
for (let i = idx + 1; i < tokens.length; i++) {
|
||
|
|
if (tokens[i].type === "link_close") {
|
||
|
|
tokens[i].tag = "Link";
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (token.attrGet("target") == null) {
|
||
|
|
token.attrPush(["target", "_blank"]);
|
||
|
|
}
|
||
|
|
return defaultRender(tokens, idx, options, env, self);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/utils.ts
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
import { createJiti } from "jiti";
|
||
|
|
import YAML from "yaml";
|
||
|
|
var jiti;
|
||
|
|
function loadModule(absolutePath) {
|
||
|
|
jiti ??= createJiti(fileURLToPath(import.meta.url));
|
||
|
|
return jiti.import(absolutePath);
|
||
|
|
}
|
||
|
|
function stringifyMarkdownTokens(tokens) {
|
||
|
|
return tokens.map((token) => token.children?.filter((t) => ["text", "code_inline"].includes(t.type) && !t.content.match(/^\s*$/)).map((t) => t.content.trim()).join(" ")).filter(Boolean).join(" ");
|
||
|
|
}
|
||
|
|
function generateGoogleFontsUrl(options) {
|
||
|
|
const weights = options.weights.flatMap((i) => options.italic ? [`0,${i}`, `1,${i}`] : [`${i}`]).sort().join(";");
|
||
|
|
const fonts = options.webfonts.map((i) => `family=${i.replace(/^(['"])(.*)\1$/, "$1").replace(/\s+/g, "+")}:${options.italic ? "ital," : ""}wght@${weights}`).join("&");
|
||
|
|
return `https://fonts.googleapis.com/css2?${fonts}&display=swap`;
|
||
|
|
}
|
||
|
|
function updateFrontmatterPatch(slide, frontmatter) {
|
||
|
|
const source = slide.source;
|
||
|
|
let doc = source.frontmatterDoc;
|
||
|
|
if (!doc) {
|
||
|
|
source.frontmatterStyle = "frontmatter";
|
||
|
|
source.frontmatterDoc = doc = new YAML.Document({});
|
||
|
|
}
|
||
|
|
for (const [key, value] of Object.entries(frontmatter)) {
|
||
|
|
slide.frontmatter[key] = source.frontmatter[key] = value;
|
||
|
|
if (value == null) {
|
||
|
|
doc.delete(key);
|
||
|
|
} else {
|
||
|
|
const valueNode = doc.createNode(value);
|
||
|
|
let found = false;
|
||
|
|
YAML.visit(doc.contents, {
|
||
|
|
Pair(_key, node, path4) {
|
||
|
|
if (path4.length === 1 && YAML.isScalar(node.key) && node.key.value === key) {
|
||
|
|
node.value = valueNode;
|
||
|
|
found = true;
|
||
|
|
return YAML.visit.BREAK;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (!found) {
|
||
|
|
if (!YAML.isMap(doc.contents))
|
||
|
|
doc.contents = doc.createNode({});
|
||
|
|
doc.contents.add(
|
||
|
|
doc.createPair(key, valueNode)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function getBodyJson(req) {
|
||
|
|
return new Promise((resolve9, reject) => {
|
||
|
|
let body = "";
|
||
|
|
req.on("data", (chunk) => body += chunk);
|
||
|
|
req.on("error", reject);
|
||
|
|
req.on("end", () => {
|
||
|
|
try {
|
||
|
|
resolve9(JSON.parse(body) || {});
|
||
|
|
} catch (e) {
|
||
|
|
reject(e);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/compilerFlagsVue.ts
|
||
|
|
import { objectEntries } from "@antfu/utils";
|
||
|
|
function createVueCompilerFlagsPlugin(options) {
|
||
|
|
const define = objectEntries(options.utils.define);
|
||
|
|
return {
|
||
|
|
name: "slidev:flags",
|
||
|
|
enforce: "pre",
|
||
|
|
transform(code, id) {
|
||
|
|
if (!id.match(/\.vue($|\?)/) && !id.includes("?vue&"))
|
||
|
|
return;
|
||
|
|
const original = code;
|
||
|
|
define.forEach(([from, to]) => {
|
||
|
|
code = code.replaceAll(from, to);
|
||
|
|
});
|
||
|
|
if (original !== code)
|
||
|
|
return code;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/components.ts
|
||
|
|
import { join as join2 } from "node:path";
|
||
|
|
import IconsResolver from "unplugin-icons/resolver";
|
||
|
|
import Components from "unplugin-vue-components/vite";
|
||
|
|
function createComponentsPlugin({ clientRoot, roots }, pluginOptions) {
|
||
|
|
return Components({
|
||
|
|
extensions: ["vue", "md", "js", "ts", "jsx", "tsx"],
|
||
|
|
dirs: [
|
||
|
|
join2(clientRoot, "builtin"),
|
||
|
|
...roots.map((i) => join2(i, "components"))
|
||
|
|
],
|
||
|
|
include: [/\.vue$/, /\.vue\?vue/, /\.vue\?v=/, /\.md$/, /\.md\?vue/],
|
||
|
|
exclude: [],
|
||
|
|
resolvers: [
|
||
|
|
IconsResolver({
|
||
|
|
prefix: "",
|
||
|
|
customCollections: Object.keys(pluginOptions.icons?.customCollections || [])
|
||
|
|
})
|
||
|
|
],
|
||
|
|
dts: false,
|
||
|
|
...pluginOptions.components
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/common.ts
|
||
|
|
var regexSlideReqPath = /^\/__slidev\/slides\/(\d+)\.json$/;
|
||
|
|
var regexSlideFacadeId = /^\/@slidev\/slides\/(\d+)\/(md|frontmatter)($|\?)/;
|
||
|
|
var regexSlideSourceId = /__slidev_(\d+)\.(md|frontmatter)$/;
|
||
|
|
var templateInjectionMarker = "/* @slidev-injection */";
|
||
|
|
var templateImportContextUtils = `import { useSlideContext as _useSlideContext, frontmatterToProps as _frontmatterToProps } from "@slidev/client/context.ts"`;
|
||
|
|
var templateInitContext = `const { $slidev, $nav, $clicksContext, $clicks, $page, $renderContext, $frontmatter } = _useSlideContext()`;
|
||
|
|
|
||
|
|
// node/vite/contextInjection.ts
|
||
|
|
function createContextInjectionPlugin() {
|
||
|
|
return {
|
||
|
|
name: "slidev:context-injection",
|
||
|
|
async transform(code, id) {
|
||
|
|
if (!id.endsWith(".vue") || id.includes("/@slidev/client/") || id.includes("/packages/client/"))
|
||
|
|
return;
|
||
|
|
if (code.includes(templateInjectionMarker) || code.includes("useSlideContext()"))
|
||
|
|
return code;
|
||
|
|
const imports = [
|
||
|
|
templateImportContextUtils,
|
||
|
|
templateInitContext,
|
||
|
|
templateInjectionMarker
|
||
|
|
];
|
||
|
|
const matchScript = code.match(/<script((?!setup).)*(setup)?.*>/);
|
||
|
|
if (matchScript && matchScript[2]) {
|
||
|
|
return code.replace(/(<script.*>)/g, `$1
|
||
|
|
${imports.join("\n")}
|
||
|
|
`);
|
||
|
|
} else if (matchScript && !matchScript[2]) {
|
||
|
|
const matchExport = code.match(/export\s+default\s+\{/);
|
||
|
|
if (matchExport) {
|
||
|
|
const exportIndex = (matchExport.index || 0) + matchExport[0].length;
|
||
|
|
let component = code.slice(exportIndex);
|
||
|
|
component = component.slice(0, component.indexOf("</script>"));
|
||
|
|
const scriptIndex = (matchScript.index || 0) + matchScript[0].length;
|
||
|
|
const provideImport = '\nimport { injectionSlidevContext } from "@slidev/client/constants.ts"\n';
|
||
|
|
code = `${code.slice(0, scriptIndex)}${provideImport}${code.slice(scriptIndex)}`;
|
||
|
|
let injectIndex = exportIndex + provideImport.length;
|
||
|
|
let injectObject = "$slidev: { from: injectionSlidevContext },";
|
||
|
|
const matchInject = component.match(/.*inject\s*:\s*([[{])/);
|
||
|
|
if (matchInject) {
|
||
|
|
injectIndex += (matchInject.index || 0) + matchInject[0].length;
|
||
|
|
if (matchInject[1] === "[") {
|
||
|
|
let injects = component.slice((matchInject.index || 0) + matchInject[0].length);
|
||
|
|
const injectEndIndex = injects.indexOf("]");
|
||
|
|
injects = injects.slice(0, injectEndIndex);
|
||
|
|
injectObject += injects.split(",").map((inject) => `${inject}: {from: ${inject}}`).join(",");
|
||
|
|
return `${code.slice(0, injectIndex - 1)}{
|
||
|
|
${injectObject}
|
||
|
|
}${code.slice(injectIndex + injectEndIndex + 1)}`;
|
||
|
|
} else {
|
||
|
|
return `${code.slice(0, injectIndex)}
|
||
|
|
${injectObject}
|
||
|
|
${code.slice(injectIndex)}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return `${code.slice(0, injectIndex)}
|
||
|
|
inject: { ${injectObject} },
|
||
|
|
${code.slice(injectIndex)}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return `<script setup>
|
||
|
|
${imports.join("\n")}
|
||
|
|
</script>
|
||
|
|
${code}`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/extendConfig.ts
|
||
|
|
import { join as join3 } from "node:path";
|
||
|
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
||
|
|
import { slash, uniq } from "@antfu/utils";
|
||
|
|
import { createResolve } from "mlly";
|
||
|
|
import { mergeConfig } from "vite";
|
||
|
|
var INCLUDE_GLOBAL = [
|
||
|
|
"@typescript/ata",
|
||
|
|
"file-saver",
|
||
|
|
"lz-string",
|
||
|
|
"prettier",
|
||
|
|
"recordrtc",
|
||
|
|
"typescript",
|
||
|
|
"yaml",
|
||
|
|
"html-to-image"
|
||
|
|
];
|
||
|
|
var INCLUDE_LOCAL = INCLUDE_GLOBAL.map((i) => `@slidev/cli > @slidev/client > ${i}`);
|
||
|
|
var EXCLUDE_GLOBAL = [
|
||
|
|
"@antfu/utils",
|
||
|
|
"@shikijs/monaco",
|
||
|
|
"@shikijs/vitepress-twoslash/client",
|
||
|
|
"@slidev/client",
|
||
|
|
"@slidev/client/constants",
|
||
|
|
"@slidev/client/context",
|
||
|
|
"@slidev/client/logic/dark",
|
||
|
|
"@slidev/parser",
|
||
|
|
"@slidev/parser/core",
|
||
|
|
"@slidev/rough-notation",
|
||
|
|
"@slidev/types",
|
||
|
|
"@unhead/vue",
|
||
|
|
"@unocss/reset",
|
||
|
|
"@vueuse/core",
|
||
|
|
"@vueuse/math",
|
||
|
|
"@vueuse/motion",
|
||
|
|
"@vueuse/shared",
|
||
|
|
"drauu",
|
||
|
|
"floating-vue",
|
||
|
|
"fuse.js",
|
||
|
|
"mermaid",
|
||
|
|
"monaco-editor",
|
||
|
|
"shiki-magic-move/vue",
|
||
|
|
"shiki",
|
||
|
|
"shiki/core",
|
||
|
|
"vue-demi",
|
||
|
|
"vue-router",
|
||
|
|
"vue"
|
||
|
|
];
|
||
|
|
var EXCLUDE_LOCAL = EXCLUDE_GLOBAL;
|
||
|
|
var ASYNC_MODULES = [
|
||
|
|
"file-saver",
|
||
|
|
"vue",
|
||
|
|
"@vue"
|
||
|
|
];
|
||
|
|
function createConfigPlugin(options) {
|
||
|
|
const resolveClientDep = createResolve({
|
||
|
|
// Same as Vite's default resolve conditions
|
||
|
|
conditions: ["import", "module", "browser", "default", options.mode === "build" ? "production" : "development"],
|
||
|
|
url: pathToFileURL(options.clientRoot)
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
name: "slidev:config",
|
||
|
|
async config(config) {
|
||
|
|
const injection = {
|
||
|
|
define: options.utils.define,
|
||
|
|
resolve: {
|
||
|
|
alias: [
|
||
|
|
{
|
||
|
|
find: /^@slidev\/client$/,
|
||
|
|
replacement: `${toAtFS(options.clientRoot)}/index.ts`
|
||
|
|
},
|
||
|
|
{
|
||
|
|
find: /^@slidev\/client\/(.*)/,
|
||
|
|
replacement: `${toAtFS(options.clientRoot)}/$1`
|
||
|
|
},
|
||
|
|
{
|
||
|
|
find: /^#slidev\/(.*)/,
|
||
|
|
replacement: "/@slidev/$1"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
find: "vue",
|
||
|
|
replacement: await resolveImportPath("vue/dist/vue.esm-bundler.js", true)
|
||
|
|
},
|
||
|
|
...isInstalledGlobally.value ? await Promise.all(INCLUDE_GLOBAL.map(async (dep) => ({
|
||
|
|
find: dep,
|
||
|
|
replacement: fileURLToPath2(await resolveClientDep(dep))
|
||
|
|
}))) : []
|
||
|
|
],
|
||
|
|
dedupe: ["vue"]
|
||
|
|
},
|
||
|
|
optimizeDeps: isInstalledGlobally.value ? {
|
||
|
|
exclude: EXCLUDE_GLOBAL,
|
||
|
|
include: INCLUDE_GLOBAL
|
||
|
|
} : {
|
||
|
|
// We need to specify the full deps path for non-hoisted modules
|
||
|
|
exclude: EXCLUDE_LOCAL,
|
||
|
|
include: INCLUDE_LOCAL
|
||
|
|
},
|
||
|
|
css: {
|
||
|
|
postcss: {
|
||
|
|
plugins: [
|
||
|
|
await import("postcss-nested").then((r) => (r.default || r)())
|
||
|
|
]
|
||
|
|
}
|
||
|
|
},
|
||
|
|
server: {
|
||
|
|
fs: {
|
||
|
|
strict: true,
|
||
|
|
allow: uniq([
|
||
|
|
options.userWorkspaceRoot,
|
||
|
|
options.clientRoot,
|
||
|
|
// Special case for PNPM global installation
|
||
|
|
isInstalledGlobally.value ? slash(options.cliRoot).replace(/\/\.pnpm\/.*$/gi, "") : options.cliRoot,
|
||
|
|
...options.roots
|
||
|
|
])
|
||
|
|
}
|
||
|
|
},
|
||
|
|
publicDir: join3(options.userRoot, "public"),
|
||
|
|
build: {
|
||
|
|
rollupOptions: {
|
||
|
|
output: {
|
||
|
|
chunkFileNames(chunkInfo) {
|
||
|
|
const DEFAULT = "assets/[name]-[hash].js";
|
||
|
|
if (chunkInfo.name.includes("/"))
|
||
|
|
return DEFAULT;
|
||
|
|
if (chunkInfo.moduleIds.filter((i) => isSlidevClient(i)).length > chunkInfo.moduleIds.length * 0.6)
|
||
|
|
return "assets/slidev/[name]-[hash].js";
|
||
|
|
if (chunkInfo.moduleIds.filter((i) => i.match(/\/monaco-editor(-core)?\//)).length > chunkInfo.moduleIds.length * 0.6)
|
||
|
|
return "assets/monaco/[name]-[hash].js";
|
||
|
|
return DEFAULT;
|
||
|
|
},
|
||
|
|
manualChunks(id) {
|
||
|
|
if (id.startsWith("/@slidev-monaco-types/") || id.includes("/@slidev/monaco-types") || id.endsWith("?monaco-types&raw"))
|
||
|
|
return "monaco/bundled-types";
|
||
|
|
if (id.includes("/shiki/") || id.includes("/@shikijs/"))
|
||
|
|
return `modules/shiki`;
|
||
|
|
if (id.startsWith("~icons/"))
|
||
|
|
return "modules/unplugin-icons";
|
||
|
|
const matchedAsyncModule = ASYNC_MODULES.find((i) => id.includes(`/node_modules/${i}`));
|
||
|
|
if (matchedAsyncModule)
|
||
|
|
return `modules/${matchedAsyncModule.replace("@", "").replace("/", "-")}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
cacheDir: isInstalledGlobally.value ? join3(options.cliRoot, "node_modules/.vite") : void 0
|
||
|
|
};
|
||
|
|
function isSlidevClient(id) {
|
||
|
|
return id.includes("/@slidev/") || id.includes("/slidev/packages/client/") || id.includes("/@vueuse/");
|
||
|
|
}
|
||
|
|
return mergeConfig(injection, config);
|
||
|
|
},
|
||
|
|
configureServer(server) {
|
||
|
|
return () => {
|
||
|
|
server.middlewares.use(async (req, res, next) => {
|
||
|
|
if (req.url === "/index.html") {
|
||
|
|
res.setHeader("Content-Type", "text/html");
|
||
|
|
res.statusCode = 200;
|
||
|
|
res.end(options.utils.indexHtml);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
next();
|
||
|
|
});
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/hmrPatch.ts
|
||
|
|
function createHmrPatchPlugin() {
|
||
|
|
return {
|
||
|
|
name: "slidev:hmr-patch",
|
||
|
|
transform(code, id) {
|
||
|
|
if (!id.match(regexSlideSourceId))
|
||
|
|
return;
|
||
|
|
return code.replace("if (_rerender_only)", "if (false)");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/icons.ts
|
||
|
|
import Icons from "unplugin-icons/vite";
|
||
|
|
function createIconsPlugin(options, pluginOptions) {
|
||
|
|
return Icons({
|
||
|
|
defaultClass: "slidev-icon",
|
||
|
|
collectionsNodeResolvePath: options.utils.iconsResolvePath,
|
||
|
|
...pluginOptions.icons
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/inspect.ts
|
||
|
|
async function createInspectPlugin(options, pluginOptions) {
|
||
|
|
if (!options.inspect)
|
||
|
|
return;
|
||
|
|
const { default: PluginInspect } = await import("vite-plugin-inspect");
|
||
|
|
return PluginInspect({
|
||
|
|
dev: true,
|
||
|
|
build: true,
|
||
|
|
...pluginOptions.inspect
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/layoutWrapper.ts
|
||
|
|
import { bold, gray, red, yellow } from "kolorist";
|
||
|
|
function createLayoutWrapperPlugin({ data, utils }) {
|
||
|
|
return {
|
||
|
|
name: "slidev:layout-wrapper",
|
||
|
|
async transform(code, id) {
|
||
|
|
const match = id.match(regexSlideSourceId);
|
||
|
|
if (!match)
|
||
|
|
return;
|
||
|
|
const [, no, type] = match;
|
||
|
|
if (type !== "md")
|
||
|
|
return;
|
||
|
|
const index = +no - 1;
|
||
|
|
const layouts = utils.getLayouts();
|
||
|
|
const rawLayoutName = data.slides[index]?.frontmatter?.layout ?? data.slides[0]?.frontmatter?.defaults?.layout;
|
||
|
|
let layoutName = rawLayoutName || (index === 0 ? "cover" : "default");
|
||
|
|
if (!layouts[layoutName]) {
|
||
|
|
console.error(red(`
|
||
|
|
Unknown layout "${bold(layoutName)}".${yellow(" Available layouts are:")}`) + Object.keys(layouts).map((i, idx) => (idx % 3 === 0 ? "\n " : "") + gray(i.padEnd(15, " "))).join(" "));
|
||
|
|
console.error();
|
||
|
|
layoutName = "default";
|
||
|
|
}
|
||
|
|
const setupTag = code.match(/^<script setup.*>/m);
|
||
|
|
if (!setupTag)
|
||
|
|
throw new Error(`[Slidev] Internal error: <script setup> block not found in slide ${index + 1}.`);
|
||
|
|
const templatePart = code.slice(0, setupTag.index);
|
||
|
|
const scriptPart = code.slice(setupTag.index);
|
||
|
|
const bodyStart = templatePart.indexOf("<template>") + 10;
|
||
|
|
const bodyEnd = templatePart.lastIndexOf("</template>");
|
||
|
|
let body = code.slice(bodyStart, bodyEnd).trim();
|
||
|
|
if (body.startsWith("<div>") && body.endsWith("</div>"))
|
||
|
|
body = body.slice(5, -6);
|
||
|
|
return [
|
||
|
|
templatePart.slice(0, bodyStart),
|
||
|
|
`<InjectedLayout v-bind="_frontmatterToProps($frontmatter,${index})">
|
||
|
|
${body}
|
||
|
|
</InjectedLayout>`,
|
||
|
|
templatePart.slice(bodyEnd),
|
||
|
|
scriptPart.slice(0, setupTag[0].length),
|
||
|
|
`import InjectedLayout from "${toAtFS(layouts[layoutName])}"`,
|
||
|
|
templateImportContextUtils,
|
||
|
|
templateInitContext,
|
||
|
|
"$clicksContext.setup()",
|
||
|
|
templateInjectionMarker,
|
||
|
|
scriptPart.slice(setupTag[0].length)
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/loaders.ts
|
||
|
|
import { notNullish, range } from "@antfu/utils";
|
||
|
|
import * as parser2 from "@slidev/parser/fs";
|
||
|
|
import equal from "fast-deep-equal";
|
||
|
|
import YAML2 from "yaml";
|
||
|
|
|
||
|
|
// node/virtual/configs.ts
|
||
|
|
import { isString } from "@antfu/utils";
|
||
|
|
var templateConfigs = {
|
||
|
|
id: "/@slidev/configs",
|
||
|
|
getContent({ data, remote }) {
|
||
|
|
const config = {
|
||
|
|
...data.config,
|
||
|
|
remote,
|
||
|
|
slidesTitle: getSlideTitle(data)
|
||
|
|
};
|
||
|
|
if (isString(config.info))
|
||
|
|
config.info = sharedMd.render(config.info);
|
||
|
|
return `export default ${JSON.stringify(config)}`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/deprecated.ts
|
||
|
|
var templateLegacyRoutes = {
|
||
|
|
id: "/@slidev/routes",
|
||
|
|
getContent() {
|
||
|
|
return [
|
||
|
|
`export { slides } from '#slidev/slides'`,
|
||
|
|
`console.warn('[slidev] #slidev/routes is deprecated, use #slidev/slides instead')`
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var templateLegacyTitles = {
|
||
|
|
id: "/@slidev/titles.md",
|
||
|
|
getContent() {
|
||
|
|
return `
|
||
|
|
<script setup lang="ts">
|
||
|
|
import TitleRenderer from '#slidev/title-renderer'
|
||
|
|
defineProps<{ no: number | string }>()
|
||
|
|
console.warn('/@slidev/titles.md is deprecated, import from #slidev/title-renderer instead')
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<TitleRenderer :no="no" />
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/global-layers.ts
|
||
|
|
import { existsSync } from "node:fs";
|
||
|
|
import { join as join4 } from "node:path";
|
||
|
|
var templateGlobalLayers = {
|
||
|
|
id: `/@slidev/global-layers`,
|
||
|
|
getContent({ roots }) {
|
||
|
|
const imports = [];
|
||
|
|
let n = 0;
|
||
|
|
function getComponent(names) {
|
||
|
|
const components = roots.flatMap((root) => names.map((name) => join4(root, name))).filter((i) => existsSync(i));
|
||
|
|
imports.push(components.map((path4, i) => `import __n${n}_${i} from '${toAtFS(path4)}'`).join("\n"));
|
||
|
|
const render = components.map((_, i) => `h(__n${n}_${i})`).join(",");
|
||
|
|
n++;
|
||
|
|
return `{ render: () => [${render}] }`;
|
||
|
|
}
|
||
|
|
const globalTop = getComponent(["global.vue", "global-top.vue", "GlobalTop.vue"]);
|
||
|
|
const globalBottom = getComponent(["global-bottom.vue", "GlobalBottom.vue"]);
|
||
|
|
const slideTop = getComponent(["slide-top.vue", "SlideTop.vue"]);
|
||
|
|
const slideBottom = getComponent(["slide-bottom.vue", "SlideBottom.vue"]);
|
||
|
|
return [
|
||
|
|
imports.join("\n"),
|
||
|
|
`import { h } from 'vue'`,
|
||
|
|
`export const GlobalTop = ${globalTop}`,
|
||
|
|
`export const GlobalBottom = ${globalBottom}`,
|
||
|
|
`export const SlideTop = ${slideTop}`,
|
||
|
|
`export const SlideBottom = ${slideBottom}`
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/layouts.ts
|
||
|
|
import { objectMap } from "@antfu/utils";
|
||
|
|
var templateLayouts = {
|
||
|
|
id: "/@slidev/layouts",
|
||
|
|
getContent({ utils }) {
|
||
|
|
const imports = [];
|
||
|
|
const layouts = objectMap(
|
||
|
|
utils.getLayouts(),
|
||
|
|
(k, v) => {
|
||
|
|
imports.push(`import __layout_${k} from "${toAtFS(v)}"`);
|
||
|
|
return [k, `__layout_${k}`];
|
||
|
|
}
|
||
|
|
);
|
||
|
|
return [
|
||
|
|
imports.join("\n"),
|
||
|
|
`export default {
|
||
|
|
${Object.entries(layouts).map(([k, v]) => `"${k}": ${v}`).join(",\n")}
|
||
|
|
}`
|
||
|
|
].join("\n\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/monaco-deps.ts
|
||
|
|
import { resolve as resolve2 } from "node:path";
|
||
|
|
import { uniq as uniq2 } from "@antfu/utils";
|
||
|
|
var templateMonacoRunDeps = {
|
||
|
|
id: "/@slidev/monaco-run-deps",
|
||
|
|
async getContent({ userRoot, data }) {
|
||
|
|
if (!data.features.monaco)
|
||
|
|
return "";
|
||
|
|
const deps = uniq2(data.features.monaco.deps.concat(data.config.monacoTypesAdditionalPackages));
|
||
|
|
const importerPath = resolve2(userRoot, "./snippets/__importer__.ts");
|
||
|
|
let result = "";
|
||
|
|
for (let i = 0; i < deps.length; i++) {
|
||
|
|
const specifier = deps[i];
|
||
|
|
const resolved = await this.resolve(specifier, importerPath);
|
||
|
|
if (!resolved)
|
||
|
|
continue;
|
||
|
|
result += `import * as vendored${i} from ${JSON.stringify(resolved.id)}
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
result += "export default {\n";
|
||
|
|
for (let i = 0; i < deps.length; i++)
|
||
|
|
result += `${JSON.stringify(deps[i])}: vendored${i},
|
||
|
|
`;
|
||
|
|
result += "}\n";
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/monaco-types.ts
|
||
|
|
import { builtinModules } from "node:module";
|
||
|
|
import { join as join5, resolve as resolve3 } from "node:path";
|
||
|
|
import { uniq as uniq3 } from "@antfu/utils";
|
||
|
|
import fg from "fast-glob";
|
||
|
|
var templateMonacoTypes = {
|
||
|
|
id: "/@slidev/monaco-types",
|
||
|
|
getContent: async ({ userRoot, data, utils }) => {
|
||
|
|
if (!data.features.monaco)
|
||
|
|
return "";
|
||
|
|
const typesRoot = join5(userRoot, "snippets");
|
||
|
|
const files = await fg(["**/*.ts", "**/*.mts", "**/*.cts"], { cwd: typesRoot });
|
||
|
|
let result = 'import { addFile } from "@slidev/client/setup/monaco.ts"\n';
|
||
|
|
for (const file of files) {
|
||
|
|
const url = `${toAtFS(resolve3(typesRoot, file))}?monaco-types&raw`;
|
||
|
|
result += `addFile(() => import(${JSON.stringify(url)}), ${JSON.stringify(file)})
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
function mapModuleNameToModule(moduleSpecifier) {
|
||
|
|
if (moduleSpecifier.startsWith("node:"))
|
||
|
|
return "node";
|
||
|
|
if (builtinModules.includes(moduleSpecifier))
|
||
|
|
return "node";
|
||
|
|
const mainPackageName = moduleSpecifier.split("/")[0];
|
||
|
|
if (builtinModules.includes(mainPackageName) && !mainPackageName.startsWith("@"))
|
||
|
|
return "node";
|
||
|
|
const [a = "", b = ""] = moduleSpecifier.split("/");
|
||
|
|
const moduleName = a.startsWith("@") ? `${a}/${b}` : a;
|
||
|
|
return moduleName;
|
||
|
|
}
|
||
|
|
let deps = [...data.config.monacoTypesAdditionalPackages];
|
||
|
|
if (data.config.monacoTypesSource === "local")
|
||
|
|
deps.push(...data.features.monaco.types);
|
||
|
|
deps = uniq3(deps.map((specifier) => {
|
||
|
|
if (specifier[0] === ".")
|
||
|
|
return "";
|
||
|
|
return mapModuleNameToModule(specifier);
|
||
|
|
}).filter(Boolean));
|
||
|
|
deps = deps.filter((pkg) => !utils.isMonacoTypesIgnored(pkg));
|
||
|
|
for (const pkg of deps) {
|
||
|
|
result += `import(${JSON.stringify(`/@slidev-monaco-types/resolve?${new URLSearchParams({ pkg })}`)})
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/nav-controls.ts
|
||
|
|
import { existsSync as existsSync2 } from "node:fs";
|
||
|
|
import { join as join6 } from "node:path";
|
||
|
|
var templateNavControls = {
|
||
|
|
id: "/@slidev/custom-nav-controls",
|
||
|
|
getContent({ roots }) {
|
||
|
|
const components = roots.flatMap((root) => {
|
||
|
|
return [
|
||
|
|
join6(root, "custom-nav-controls.vue"),
|
||
|
|
join6(root, "CustomNavControls.vue")
|
||
|
|
];
|
||
|
|
}).filter((i) => existsSync2(i));
|
||
|
|
const imports = components.map((i, idx) => `import __n${idx} from '${toAtFS(i)}'`).join("\n");
|
||
|
|
const render = components.map((i, idx) => `h(__n${idx})`).join(",");
|
||
|
|
return `${imports}
|
||
|
|
import { h } from 'vue'
|
||
|
|
export default {
|
||
|
|
render: () => [${render}],
|
||
|
|
}`;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/setups.ts
|
||
|
|
import { existsSync as existsSync3 } from "node:fs";
|
||
|
|
import { join as join7 } from "node:path";
|
||
|
|
function createSetupTemplate(name) {
|
||
|
|
return {
|
||
|
|
id: `/@slidev/setups/${name}`,
|
||
|
|
getContent({ roots }) {
|
||
|
|
const setups = roots.flatMap((i) => {
|
||
|
|
const path4 = join7(i, "setup", name);
|
||
|
|
return [".ts", ".mts", ".js", ".mjs"].map((ext) => path4 + ext);
|
||
|
|
}).filter((i) => existsSync3(i));
|
||
|
|
const imports = [];
|
||
|
|
setups.forEach((path4, idx) => {
|
||
|
|
imports.push(`import __n${idx} from '${toAtFS(path4)}'`);
|
||
|
|
});
|
||
|
|
imports.push(`export default [${setups.map((_, idx) => `__n${idx}`).join(",")}]`);
|
||
|
|
return imports.join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
var setupModules = ["shiki", "code-runners", "monaco", "mermaid", "main", "root", "routes", "shortcuts", "context-menu"];
|
||
|
|
var templateSetups = setupModules.map(createSetupTemplate);
|
||
|
|
|
||
|
|
// node/virtual/shiki.ts
|
||
|
|
import { uniq as uniq4 } from "@antfu/utils";
|
||
|
|
var templateShiki = {
|
||
|
|
id: "/@slidev/shiki",
|
||
|
|
getContent: async ({ utils }) => {
|
||
|
|
const options = utils.shikiOptions;
|
||
|
|
const langs = await resolveLangs(options.langs || ["markdown", "vue", "javascript", "typescript", "html", "css"]);
|
||
|
|
const resolvedThemeOptions = "themes" in options ? {
|
||
|
|
themes: Object.fromEntries(await Promise.all(
|
||
|
|
Object.entries(options.themes).map(async ([name, value]) => [name, await resolveTheme2(value)])
|
||
|
|
))
|
||
|
|
} : {
|
||
|
|
theme: await resolveTheme2(options.theme || "vitesse-dark")
|
||
|
|
};
|
||
|
|
const themes = resolvedThemeOptions.themes ? Object.values(resolvedThemeOptions.themes) : [resolvedThemeOptions.theme];
|
||
|
|
const themeOptionsNames = resolvedThemeOptions.themes ? { themes: Object.fromEntries(Object.entries(resolvedThemeOptions.themes).map(([name, value]) => [name, typeof value === "string" ? value : value.name])) } : { theme: typeof resolvedThemeOptions.theme === "string" ? resolvedThemeOptions.theme : resolvedThemeOptions.theme.name };
|
||
|
|
async function normalizeGetter(p) {
|
||
|
|
const r = typeof p === "function" ? p() : p;
|
||
|
|
return r.default || r;
|
||
|
|
}
|
||
|
|
async function resolveLangs(langs2) {
|
||
|
|
const awaited = await Promise.all(langs2.map((lang) => normalizeGetter(lang)));
|
||
|
|
return uniq4(awaited.flat());
|
||
|
|
}
|
||
|
|
async function resolveTheme2(theme) {
|
||
|
|
return typeof theme === "string" ? theme : await normalizeGetter(theme);
|
||
|
|
}
|
||
|
|
const langsInit = await Promise.all(
|
||
|
|
langs.map(async (lang) => typeof lang === "string" ? `import('${await resolveImportUrl(`shiki/langs/${lang}.mjs`)}')` : JSON.stringify(lang))
|
||
|
|
);
|
||
|
|
const themesInit = await Promise.all(themes.map(async (theme) => typeof theme === "string" ? `import('${await resolveImportUrl(`shiki/themes/${theme}.mjs`)}')` : JSON.stringify(theme)));
|
||
|
|
const langNames = langs.flatMap((lang) => typeof lang === "string" ? lang : lang.name);
|
||
|
|
const lines = [];
|
||
|
|
lines.push(
|
||
|
|
`import { createHighlighterCore } from "${await resolveImportUrl("shiki/core")}"`,
|
||
|
|
`export { shikiToMonaco } from "${await resolveImportUrl("@shikijs/monaco")}"`,
|
||
|
|
`export const languages = ${JSON.stringify(langNames)}`,
|
||
|
|
`export const themes = ${JSON.stringify(themeOptionsNames.themes || themeOptionsNames.theme)}`,
|
||
|
|
"export const shiki = createHighlighterCore({",
|
||
|
|
` themes: [${themesInit.join(",")}],`,
|
||
|
|
` langs: [${langsInit.join(",")}],`,
|
||
|
|
` loadWasm: import('${await resolveImportUrl("shiki/wasm")}'),`,
|
||
|
|
"})",
|
||
|
|
"let highlight",
|
||
|
|
"export async function getHighlighter() {",
|
||
|
|
" if (highlight) return highlight",
|
||
|
|
" const highlighter = await shiki",
|
||
|
|
" highlight = (code, lang, options) => highlighter.codeToHtml(code, {",
|
||
|
|
" lang,",
|
||
|
|
` theme: ${JSON.stringify(themeOptionsNames.theme)},`,
|
||
|
|
` themes: ${JSON.stringify(themeOptionsNames.themes)},`,
|
||
|
|
" defaultColor: false,",
|
||
|
|
" ...options,",
|
||
|
|
" })",
|
||
|
|
" return highlight",
|
||
|
|
"}"
|
||
|
|
);
|
||
|
|
return lines.join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/slides.ts
|
||
|
|
var VIRTUAL_SLIDE_PREFIX = "/@slidev/slides/";
|
||
|
|
var templateSlides = {
|
||
|
|
id: "/@slidev/slides",
|
||
|
|
getContent({ data, utils }) {
|
||
|
|
const layouts = utils.getLayouts();
|
||
|
|
const statements = [
|
||
|
|
`import { defineAsyncComponent, shallowRef } from 'vue'`,
|
||
|
|
`import SlideError from '${layouts.error}'`,
|
||
|
|
`import SlideLoading from '@slidev/client/internals/SlideLoading.vue'`,
|
||
|
|
`const componentsCache = new Array(${data.slides.length})`,
|
||
|
|
`const getAsyncComponent = (idx, loader) => defineAsyncComponent({`,
|
||
|
|
` loader,`,
|
||
|
|
` delay: 300,`,
|
||
|
|
` loadingComponent: SlideLoading,`,
|
||
|
|
` errorComponent: SlideError,`,
|
||
|
|
` onError: e => console.error('Failed to load slide ' + (idx + 1), e) `,
|
||
|
|
`})`
|
||
|
|
];
|
||
|
|
const slides = data.slides.map((_, idx) => {
|
||
|
|
const no = idx + 1;
|
||
|
|
statements.push(
|
||
|
|
`import { meta as f${no} } from '${VIRTUAL_SLIDE_PREFIX}${no}/frontmatter'`,
|
||
|
|
// For some unknown reason, import error won't be caught by the error component. Catch it here.
|
||
|
|
`const load${no} = async () => {`,
|
||
|
|
` try { return componentsCache[${idx}] ??= await import('${VIRTUAL_SLIDE_PREFIX}${no}/md') }`,
|
||
|
|
` catch (e) { console.error('slide failed to load', e); return SlideError }`,
|
||
|
|
`}`
|
||
|
|
);
|
||
|
|
return `{ no: ${no}, meta: f${no}, load: load${no}, component: getAsyncComponent(${idx}, load${no}) }`;
|
||
|
|
});
|
||
|
|
return [
|
||
|
|
...statements,
|
||
|
|
`const data = [
|
||
|
|
${slides.join(",\n")}
|
||
|
|
]`,
|
||
|
|
`if (import.meta.hot) {`,
|
||
|
|
` import.meta.hot.data.slides ??= shallowRef()`,
|
||
|
|
` import.meta.hot.data.slides.value = data`,
|
||
|
|
` import.meta.hot.dispose(() => componentsCache.length = 0)`,
|
||
|
|
` import.meta.hot.accept()`,
|
||
|
|
`}`,
|
||
|
|
`export const slides = import.meta.hot ? import.meta.hot.data.slides : shallowRef(data)`
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/styles.ts
|
||
|
|
import { existsSync as existsSync4 } from "node:fs";
|
||
|
|
import { join as join8 } from "node:path";
|
||
|
|
var templateStyle = {
|
||
|
|
id: "/@slidev/styles",
|
||
|
|
async getContent({ data, clientRoot, roots }) {
|
||
|
|
function resolveUrlOfClient(name) {
|
||
|
|
return toAtFS(join8(clientRoot, name));
|
||
|
|
}
|
||
|
|
const imports = [
|
||
|
|
`import "${resolveUrlOfClient("styles/vars.css")}"`,
|
||
|
|
`import "${resolveUrlOfClient("styles/index.css")}"`,
|
||
|
|
`import "${resolveUrlOfClient("styles/code.css")}"`,
|
||
|
|
`import "${resolveUrlOfClient("styles/katex.css")}"`,
|
||
|
|
`import "${resolveUrlOfClient("styles/transitions.css")}"`
|
||
|
|
];
|
||
|
|
for (const root of roots) {
|
||
|
|
const styles = [
|
||
|
|
join8(root, "styles", "index.ts"),
|
||
|
|
join8(root, "styles", "index.js"),
|
||
|
|
join8(root, "styles", "index.css"),
|
||
|
|
join8(root, "styles.css"),
|
||
|
|
join8(root, "style.css")
|
||
|
|
];
|
||
|
|
for (const style of styles) {
|
||
|
|
if (existsSync4(style)) {
|
||
|
|
imports.push(`import "${toAtFS(style)}"`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (data.features.katex)
|
||
|
|
imports.push(`import "${await resolveImportUrl("katex/dist/katex.min.css")}"`);
|
||
|
|
if (data.config.highlighter === "shiki") {
|
||
|
|
imports.push(
|
||
|
|
`import "${await resolveImportUrl("@shikijs/vitepress-twoslash/style.css")}"`,
|
||
|
|
`import "${resolveUrlOfClient("styles/shiki-twoslash.css")}"`,
|
||
|
|
`import "${await resolveImportUrl("shiki-magic-move/style.css")}"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
imports.unshift(
|
||
|
|
`import "${await resolveImportUrl("@unocss/reset/tailwind.css")}"`,
|
||
|
|
'import "uno:preflights.css"',
|
||
|
|
'import "uno:typography.css"',
|
||
|
|
'import "uno:shortcuts.css"'
|
||
|
|
);
|
||
|
|
imports.push('import "uno.css"');
|
||
|
|
return imports.join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/titles.ts
|
||
|
|
var templateTitleRendererMd = {
|
||
|
|
id: "/@slidev/title-renderer.md",
|
||
|
|
getContent({ data }) {
|
||
|
|
const lines = data.slides.map(({ title }, i) => `<template ${i === 0 ? "v-if" : "v-else-if"}="no === ${i + 1}">
|
||
|
|
|
||
|
|
${title}
|
||
|
|
|
||
|
|
</template>`);
|
||
|
|
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>`
|
||
|
|
);
|
||
|
|
return lines.join("\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var templateTitleRenderer = {
|
||
|
|
id: "/@slidev/title-renderer",
|
||
|
|
async getContent() {
|
||
|
|
return 'export { default } from "/@slidev/title-renderer.md"';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// node/virtual/index.ts
|
||
|
|
var templates = [
|
||
|
|
templateShiki,
|
||
|
|
templateMonacoTypes,
|
||
|
|
templateMonacoRunDeps,
|
||
|
|
templateConfigs,
|
||
|
|
templateStyle,
|
||
|
|
templateGlobalLayers,
|
||
|
|
templateNavControls,
|
||
|
|
templateSlides,
|
||
|
|
templateLayouts,
|
||
|
|
templateTitleRenderer,
|
||
|
|
templateTitleRendererMd,
|
||
|
|
...templateSetups,
|
||
|
|
// Deprecated
|
||
|
|
templateLegacyRoutes,
|
||
|
|
templateLegacyTitles
|
||
|
|
];
|
||
|
|
|
||
|
|
// node/vite/loaders.ts
|
||
|
|
function renderNote(text = "") {
|
||
|
|
let clickCount = 0;
|
||
|
|
const html = sharedMd.render(
|
||
|
|
text.replace(/\[click(?::(\d+))?\]/gi, (_, count = 1) => {
|
||
|
|
clickCount += Number(count);
|
||
|
|
return `<span class="slidev-note-click-mark" data-clicks="${clickCount}"></span>`;
|
||
|
|
})
|
||
|
|
);
|
||
|
|
return html;
|
||
|
|
}
|
||
|
|
function withRenderedNote(data) {
|
||
|
|
return {
|
||
|
|
...data,
|
||
|
|
noteHTML: renderNote(data?.note)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function createSlidesLoader(options, serverOptions) {
|
||
|
|
const { data, mode, utils } = options;
|
||
|
|
const hmrSlidesIndexes = /* @__PURE__ */ new Set();
|
||
|
|
let server;
|
||
|
|
let skipHmr = null;
|
||
|
|
let sourceIds = resolveSourceIds(data);
|
||
|
|
function resolveSourceIds(data2) {
|
||
|
|
const ids = {
|
||
|
|
md: [],
|
||
|
|
frontmatter: []
|
||
|
|
};
|
||
|
|
for (const type of ["md", "frontmatter"]) {
|
||
|
|
for (let i = 0; i < data2.slides.length; i++) {
|
||
|
|
ids[type].push(`${data2.slides[i].source.filepath}__slidev_${i + 1}.${type}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ids;
|
||
|
|
}
|
||
|
|
function updateServerWatcher() {
|
||
|
|
if (!server)
|
||
|
|
return;
|
||
|
|
server.watcher.add(Object.keys(data.watchFiles));
|
||
|
|
}
|
||
|
|
function getFrontmatter(pageNo) {
|
||
|
|
return {
|
||
|
|
...data.headmatter?.defaults || {},
|
||
|
|
...data.slides[pageNo]?.frontmatter || {}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
name: "slidev:loader",
|
||
|
|
enforce: "pre",
|
||
|
|
configureServer(_server) {
|
||
|
|
server = _server;
|
||
|
|
updateServerWatcher();
|
||
|
|
server.middlewares.use(async (req, res, next) => {
|
||
|
|
const match = req.url?.match(regexSlideReqPath);
|
||
|
|
if (!match)
|
||
|
|
return next();
|
||
|
|
const [, no] = match;
|
||
|
|
const idx = Number.parseInt(no) - 1;
|
||
|
|
if (req.method === "GET") {
|
||
|
|
res.write(JSON.stringify(withRenderedNote(data.slides[idx])));
|
||
|
|
return res.end();
|
||
|
|
} else if (req.method === "POST") {
|
||
|
|
const body = await getBodyJson(req);
|
||
|
|
const slide = data.slides[idx];
|
||
|
|
if (body.content && body.content !== slide.source.content)
|
||
|
|
hmrSlidesIndexes.add(idx);
|
||
|
|
if (body.content)
|
||
|
|
slide.content = slide.source.content = body.content;
|
||
|
|
if (body.frontmatterRaw != null) {
|
||
|
|
if (body.frontmatterRaw.trim() === "") {
|
||
|
|
slide.source.frontmatterDoc = slide.source.frontmatterStyle = void 0;
|
||
|
|
} else {
|
||
|
|
const parsed = YAML2.parseDocument(body.frontmatterRaw);
|
||
|
|
if (parsed.errors.length)
|
||
|
|
console.error("ERROR when saving frontmatter", parsed.errors);
|
||
|
|
else
|
||
|
|
slide.source.frontmatterDoc = parsed;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (body.note)
|
||
|
|
slide.note = slide.source.note = body.note;
|
||
|
|
if (body.frontmatter)
|
||
|
|
updateFrontmatterPatch(slide, body.frontmatter);
|
||
|
|
parser2.prettifySlide(slide.source);
|
||
|
|
const fileContent = await parser2.save(data.markdownFiles[slide.source.filepath]);
|
||
|
|
if (body.skipHmr) {
|
||
|
|
skipHmr = {
|
||
|
|
filePath: slide.source.filepath,
|
||
|
|
fileContent
|
||
|
|
};
|
||
|
|
server?.moduleGraph.invalidateModule(
|
||
|
|
server.moduleGraph.getModuleById(sourceIds.md[idx])
|
||
|
|
);
|
||
|
|
if (body.frontmatter) {
|
||
|
|
server?.moduleGraph.invalidateModule(
|
||
|
|
server.moduleGraph.getModuleById(sourceIds.frontmatter[idx])
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
res.statusCode = 200;
|
||
|
|
res.write(JSON.stringify(withRenderedNote(slide)));
|
||
|
|
return res.end();
|
||
|
|
}
|
||
|
|
next();
|
||
|
|
});
|
||
|
|
},
|
||
|
|
async handleHotUpdate(ctx) {
|
||
|
|
const forceChangedSlides = data.watchFiles[ctx.file];
|
||
|
|
if (!forceChangedSlides)
|
||
|
|
return;
|
||
|
|
for (const index of forceChangedSlides) {
|
||
|
|
hmrSlidesIndexes.add(index);
|
||
|
|
}
|
||
|
|
const newData = await serverOptions.loadData?.({
|
||
|
|
[ctx.file]: await ctx.read()
|
||
|
|
});
|
||
|
|
if (!newData)
|
||
|
|
return [];
|
||
|
|
if (skipHmr && newData.markdownFiles[skipHmr.filePath]?.raw === skipHmr.fileContent) {
|
||
|
|
skipHmr = null;
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
const moduleIds = /* @__PURE__ */ new Set();
|
||
|
|
const newSourceIds = resolveSourceIds(newData);
|
||
|
|
for (const type of ["md", "frontmatter"]) {
|
||
|
|
const old = sourceIds[type];
|
||
|
|
const newIds = newSourceIds[type];
|
||
|
|
for (let i = 0; i < newIds.length; i++) {
|
||
|
|
if (old[i] !== newIds[i]) {
|
||
|
|
moduleIds.add(`${VIRTUAL_SLIDE_PREFIX}${i + 1}/${type}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sourceIds = newSourceIds;
|
||
|
|
if (data.slides.length !== newData.slides.length) {
|
||
|
|
moduleIds.add(templateSlides.id);
|
||
|
|
}
|
||
|
|
if (!equal(data.headmatter.defaults, newData.headmatter.defaults)) {
|
||
|
|
moduleIds.add(templateSlides.id);
|
||
|
|
range(data.slides.length).map((i) => hmrSlidesIndexes.add(i));
|
||
|
|
}
|
||
|
|
if (!equal(data.config, newData.config))
|
||
|
|
moduleIds.add(templateConfigs.id);
|
||
|
|
if (!equal(data.features, newData.features)) {
|
||
|
|
setTimeout(() => {
|
||
|
|
ctx.server.hot.send({ type: "full-reload" });
|
||
|
|
}, 1);
|
||
|
|
}
|
||
|
|
const length = Math.min(data.slides.length, newData.slides.length);
|
||
|
|
for (let i = 0; i < length; i++) {
|
||
|
|
const a = data.slides[i];
|
||
|
|
const b = newData.slides[i];
|
||
|
|
if (!hmrSlidesIndexes.has(i) && a.content.trim() === b.content.trim() && a.title?.trim() === b.title?.trim() && equal(a.frontmatter, b.frontmatter)) {
|
||
|
|
if (a.note !== b.note) {
|
||
|
|
ctx.server.hot.send(
|
||
|
|
"slidev:update-note",
|
||
|
|
{
|
||
|
|
no: i + 1,
|
||
|
|
note: b.note || "",
|
||
|
|
noteHTML: renderNote(b.note || "")
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
ctx.server.hot.send(
|
||
|
|
"slidev:update-slide",
|
||
|
|
{
|
||
|
|
no: i + 1,
|
||
|
|
data: withRenderedNote(newData.slides[i])
|
||
|
|
}
|
||
|
|
);
|
||
|
|
hmrSlidesIndexes.add(i);
|
||
|
|
}
|
||
|
|
Object.assign(data, newData);
|
||
|
|
Object.assign(utils, createDataUtils(options));
|
||
|
|
if (hmrSlidesIndexes.size > 0)
|
||
|
|
moduleIds.add(templateTitleRendererMd.id);
|
||
|
|
const vueModules = Array.from(hmrSlidesIndexes).flatMap((idx) => {
|
||
|
|
const frontmatter = ctx.server.moduleGraph.getModuleById(sourceIds.frontmatter[idx]);
|
||
|
|
const main = ctx.server.moduleGraph.getModuleById(sourceIds.md[idx]);
|
||
|
|
const styles = main ? [...main.clientImportedModules].find((m) => m.id?.includes(`&type=style`)) : void 0;
|
||
|
|
return [
|
||
|
|
frontmatter,
|
||
|
|
main,
|
||
|
|
styles
|
||
|
|
];
|
||
|
|
});
|
||
|
|
hmrSlidesIndexes.clear();
|
||
|
|
const moduleEntries = [
|
||
|
|
...ctx.modules.filter((i) => i.id === templateMonacoRunDeps.id || i.id === templateMonacoTypes.id),
|
||
|
|
...vueModules,
|
||
|
|
...Array.from(moduleIds).map((id) => ctx.server.moduleGraph.getModuleById(id))
|
||
|
|
].filter(notNullish).filter((i) => !i.id?.startsWith("/@id/@vite-icons"));
|
||
|
|
updateServerWatcher();
|
||
|
|
return moduleEntries;
|
||
|
|
},
|
||
|
|
resolveId: {
|
||
|
|
order: "pre",
|
||
|
|
handler(id) {
|
||
|
|
if (id.startsWith("/@slidev/") || id.includes("__slidev_"))
|
||
|
|
return id;
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
async load(id) {
|
||
|
|
const template = templates.find((i) => i.id === id);
|
||
|
|
if (template) {
|
||
|
|
return {
|
||
|
|
code: await template.getContent.call(this, options),
|
||
|
|
map: { mappings: "" }
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const matchFacade = id.match(regexSlideFacadeId);
|
||
|
|
if (matchFacade) {
|
||
|
|
const [, no, type] = matchFacade;
|
||
|
|
const idx = +no - 1;
|
||
|
|
const sourceId = JSON.stringify(sourceIds[type][idx]);
|
||
|
|
return [
|
||
|
|
`export * from ${sourceId}`,
|
||
|
|
`export { default } from ${sourceId}`
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
const matchSource = id.match(regexSlideSourceId);
|
||
|
|
if (matchSource) {
|
||
|
|
const [, no, type] = matchSource;
|
||
|
|
const idx = +no - 1;
|
||
|
|
const slide = data.slides[idx];
|
||
|
|
if (!slide)
|
||
|
|
return;
|
||
|
|
if (type === "md") {
|
||
|
|
return {
|
||
|
|
code: slide.content,
|
||
|
|
map: { mappings: "" }
|
||
|
|
};
|
||
|
|
} else if (type === "frontmatter") {
|
||
|
|
const slideBase = {
|
||
|
|
...withRenderedNote(slide),
|
||
|
|
frontmatter: void 0,
|
||
|
|
source: void 0,
|
||
|
|
importChain: void 0,
|
||
|
|
// remove raw content in build, optimize the bundle size
|
||
|
|
...mode === "build" ? { raw: "", content: "", note: "" } : {}
|
||
|
|
};
|
||
|
|
const fontmatter = getFrontmatter(idx);
|
||
|
|
return {
|
||
|
|
code: [
|
||
|
|
"// @unocss-include",
|
||
|
|
'import { computed, reactive, shallowReactive } from "vue"',
|
||
|
|
`export const frontmatterData = ${JSON.stringify(fontmatter)}`,
|
||
|
|
// handle HMR, update frontmatter with update
|
||
|
|
"if (import.meta.hot) {",
|
||
|
|
" const firstLoad = !import.meta.hot.data.frontmatter",
|
||
|
|
" import.meta.hot.data.frontmatter ??= reactive(frontmatterData)",
|
||
|
|
" import.meta.hot.accept(({ frontmatterData: update }) => {",
|
||
|
|
" if (firstLoad) return",
|
||
|
|
" const frontmatter = import.meta.hot.data.frontmatter",
|
||
|
|
" Object.keys(frontmatter).forEach(key => {",
|
||
|
|
" if (!(key in update)) delete frontmatter[key]",
|
||
|
|
" })",
|
||
|
|
" Object.assign(frontmatter, update)",
|
||
|
|
" })",
|
||
|
|
"}",
|
||
|
|
"export const frontmatter = import.meta.hot ? import.meta.hot.data.frontmatter : reactive(frontmatterData)",
|
||
|
|
"export default frontmatter",
|
||
|
|
"export const meta = shallowReactive({",
|
||
|
|
" get layout(){ return frontmatter.layout },",
|
||
|
|
" get transition(){ return frontmatter.transition },",
|
||
|
|
" get class(){ return frontmatter.class },",
|
||
|
|
" get clicks(){ return frontmatter.clicks },",
|
||
|
|
" get name(){ return frontmatter.name },",
|
||
|
|
" get preload(){ return frontmatter.preload },",
|
||
|
|
// No need to be reactive, as it's only used once after reload
|
||
|
|
" slide: {",
|
||
|
|
` ...(${JSON.stringify(slideBase)}),`,
|
||
|
|
` frontmatter,`,
|
||
|
|
` filepath: ${JSON.stringify(mode === "dev" ? slide.source.filepath : "")},`,
|
||
|
|
` start: ${JSON.stringify(slide.source.start)},`,
|
||
|
|
` id: ${idx},`,
|
||
|
|
` no: ${no},`,
|
||
|
|
" },",
|
||
|
|
" __clicksContext: null,",
|
||
|
|
" __preloaded: false,",
|
||
|
|
"})"
|
||
|
|
].join("\n"),
|
||
|
|
map: { mappings: "" }
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (data.markdownFiles[id])
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/markdown.ts
|
||
|
|
import MagicString from "magic-string-stack";
|
||
|
|
import Markdown from "unplugin-vue-markdown/vite";
|
||
|
|
|
||
|
|
// ../../node_modules/.pnpm/@hedgedoc+markdown-it-plugins@2.1.4_patch_hash=tuyuxytl56b2vxulpkzt2wf4o4_markdown-it@14.1.0/node_modules/@hedgedoc/markdown-it-plugins/dist/esm/image-size/specialCharacters.js
|
||
|
|
var SpecialCharacters;
|
||
|
|
(function(SpecialCharacters2) {
|
||
|
|
SpecialCharacters2[SpecialCharacters2["EXCLAMATION_MARK"] = 33] = "EXCLAMATION_MARK";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["OPENING_BRACKET"] = 91] = "OPENING_BRACKET";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["OPENING_PARENTHESIS"] = 40] = "OPENING_PARENTHESIS";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["WHITESPACE"] = 32] = "WHITESPACE";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["NEW_LINE"] = 10] = "NEW_LINE";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["EQUALS"] = 61] = "EQUALS";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["LOWER_CASE_X"] = 120] = "LOWER_CASE_X";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["NUMBER_ZERO"] = 48] = "NUMBER_ZERO";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["NUMBER_NINE"] = 57] = "NUMBER_NINE";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["PERCENTAGE"] = 37] = "PERCENTAGE";
|
||
|
|
SpecialCharacters2[SpecialCharacters2["CLOSING_PARENTHESIS"] = 41] = "CLOSING_PARENTHESIS";
|
||
|
|
})(SpecialCharacters || (SpecialCharacters = {}));
|
||
|
|
|
||
|
|
// ../../node_modules/.pnpm/@hedgedoc+markdown-it-plugins@2.1.4_patch_hash=tuyuxytl56b2vxulpkzt2wf4o4_markdown-it@14.1.0/node_modules/@hedgedoc/markdown-it-plugins/dist/esm/task-lists/index.js
|
||
|
|
import Token from "markdown-it/lib/token.mjs";
|
||
|
|
var checkboxRegex = /^ *\[([\sx])] /i;
|
||
|
|
function taskLists(md, options = { enabled: false, label: false, lineNumber: false }) {
|
||
|
|
md.core.ruler.after("inline", "task-lists", (state) => processToken(state, options));
|
||
|
|
md.renderer.rules.taskListItemCheckbox = (tokens) => {
|
||
|
|
const token = tokens[0];
|
||
|
|
const checkedAttribute = token.attrGet("checked") ? 'checked="" ' : "";
|
||
|
|
const disabledAttribute = token.attrGet("disabled") ? 'disabled="" ' : "";
|
||
|
|
const line = token.attrGet("line");
|
||
|
|
const idAttribute = `id="${token.attrGet("id")}" `;
|
||
|
|
const dataLineAttribute = line && options.lineNumber ? `data-line="${line}" ` : "";
|
||
|
|
return `<input class="task-list-item-checkbox" type="checkbox" ${checkedAttribute}${disabledAttribute}${dataLineAttribute}${idAttribute}/>`;
|
||
|
|
};
|
||
|
|
md.renderer.rules.taskListItemLabel_close = () => {
|
||
|
|
return "</label>";
|
||
|
|
};
|
||
|
|
md.renderer.rules.taskListItemLabel_open = (tokens) => {
|
||
|
|
const token = tokens[0];
|
||
|
|
const id = token.attrGet("id");
|
||
|
|
return `<label for="${id}">`;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function processToken(state, options) {
|
||
|
|
const allTokens = state.tokens;
|
||
|
|
for (let i = 2; i < allTokens.length; i++) {
|
||
|
|
if (!isTodoItem(allTokens, i)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
todoify(allTokens[i], options);
|
||
|
|
allTokens[i - 2].attrJoin("class", `task-list-item ${options.enabled ? " enabled" : ""}`);
|
||
|
|
const parentToken = findParentToken(allTokens, i - 2);
|
||
|
|
if (parentToken) {
|
||
|
|
const classes = parentToken.attrGet("class") ?? "";
|
||
|
|
if (!classes.match(/(^| )contains-task-list/)) {
|
||
|
|
parentToken.attrJoin("class", "contains-task-list");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
function findParentToken(tokens, index) {
|
||
|
|
const targetLevel = tokens[index].level - 1;
|
||
|
|
for (let currentTokenIndex = index - 1; currentTokenIndex >= 0; currentTokenIndex--) {
|
||
|
|
if (tokens[currentTokenIndex].level === targetLevel) {
|
||
|
|
return tokens[currentTokenIndex];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return void 0;
|
||
|
|
}
|
||
|
|
function isTodoItem(tokens, index) {
|
||
|
|
return isInline(tokens[index]) && isParagraph(tokens[index - 1]) && isListItem(tokens[index - 2]) && startsWithTodoMarkdown(tokens[index]);
|
||
|
|
}
|
||
|
|
function todoify(token, options) {
|
||
|
|
if (token.children == null) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const id = generateIdForToken(token);
|
||
|
|
token.children.splice(0, 0, createCheckboxToken(token, options.enabled, id));
|
||
|
|
token.children[1].content = token.children[1].content.replace(checkboxRegex, "");
|
||
|
|
if (options.label) {
|
||
|
|
token.children.splice(1, 0, createLabelBeginToken(id));
|
||
|
|
token.children.push(createLabelEndToken());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function generateIdForToken(token) {
|
||
|
|
if (token.map) {
|
||
|
|
return `task-item-${token.map[0]}`;
|
||
|
|
} else {
|
||
|
|
return `task-item-${Math.ceil(Math.random() * (1e4 * 1e3) - 1e3)}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function createCheckboxToken(token, enabled, id) {
|
||
|
|
const checkbox = new Token("taskListItemCheckbox", "", 0);
|
||
|
|
if (!enabled) {
|
||
|
|
checkbox.attrSet("disabled", "true");
|
||
|
|
}
|
||
|
|
if (token.map) {
|
||
|
|
checkbox.attrSet("line", token.map[0].toString());
|
||
|
|
}
|
||
|
|
checkbox.attrSet("id", id);
|
||
|
|
const checkboxRegexResult = checkboxRegex.exec(token.content);
|
||
|
|
const isChecked = checkboxRegexResult?.[1].toLowerCase() === "x";
|
||
|
|
if (isChecked) {
|
||
|
|
checkbox.attrSet("checked", "true");
|
||
|
|
}
|
||
|
|
return checkbox;
|
||
|
|
}
|
||
|
|
function createLabelBeginToken(id) {
|
||
|
|
const labelBeginToken = new Token("taskListItemLabel_open", "", 1);
|
||
|
|
labelBeginToken.attrSet("id", id);
|
||
|
|
return labelBeginToken;
|
||
|
|
}
|
||
|
|
function createLabelEndToken() {
|
||
|
|
return new Token("taskListItemLabel_close", "", -1);
|
||
|
|
}
|
||
|
|
function isInline(token) {
|
||
|
|
return token.type === "inline";
|
||
|
|
}
|
||
|
|
function isParagraph(token) {
|
||
|
|
return token.type === "paragraph_open";
|
||
|
|
}
|
||
|
|
function isListItem(token) {
|
||
|
|
return token.type === "list_item_open";
|
||
|
|
}
|
||
|
|
function startsWithTodoMarkdown(token) {
|
||
|
|
return checkboxRegex.test(token.content);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/index.ts
|
||
|
|
import MarkdownItFootnote from "markdown-it-footnote";
|
||
|
|
import MarkdownItMdc from "markdown-it-mdc";
|
||
|
|
|
||
|
|
// node/setups/load.ts
|
||
|
|
import { resolve as resolve4 } from "node:path";
|
||
|
|
import { deepMergeWithArray } from "@antfu/utils";
|
||
|
|
import fs3 from "fs-extra";
|
||
|
|
async function loadSetups(roots, filename, args, extraLoader) {
|
||
|
|
const returns = [];
|
||
|
|
for (const root of roots) {
|
||
|
|
const path4 = resolve4(root, "setup", filename);
|
||
|
|
if (fs3.existsSync(path4)) {
|
||
|
|
const { default: setup } = await loadModule(path4);
|
||
|
|
const ret = await setup(...args);
|
||
|
|
if (ret)
|
||
|
|
returns.push(ret);
|
||
|
|
}
|
||
|
|
if (extraLoader)
|
||
|
|
returns.push(...await extraLoader(root));
|
||
|
|
}
|
||
|
|
return returns;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/setups/katex.ts
|
||
|
|
async function setupKatex(roots) {
|
||
|
|
const options = await loadSetups(roots, "katex.ts", []);
|
||
|
|
return Object.assign(
|
||
|
|
{ strict: false },
|
||
|
|
...options
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-escape-code.ts
|
||
|
|
function MarkdownItEscapeInlineCode(md) {
|
||
|
|
const codeInline = md.renderer.rules.code_inline;
|
||
|
|
md.renderer.rules.code_inline = (tokens, idx, options, env, self) => {
|
||
|
|
const result = codeInline(tokens, idx, options, env, self);
|
||
|
|
return result.replace(/^<code/, "<code v-pre");
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-katex.ts
|
||
|
|
import katex from "katex";
|
||
|
|
function isValidDelim(state, pos) {
|
||
|
|
const max = state.posMax;
|
||
|
|
let can_open = true;
|
||
|
|
let can_close = true;
|
||
|
|
const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
|
||
|
|
const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
|
||
|
|
if (prevChar === 32 || prevChar === 9 || /* \t */
|
||
|
|
nextChar >= 48 && nextChar <= 57)
|
||
|
|
can_close = false;
|
||
|
|
if (nextChar === 32 || nextChar === 9)
|
||
|
|
can_open = false;
|
||
|
|
return {
|
||
|
|
can_open,
|
||
|
|
can_close
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function math_inline(state, silent) {
|
||
|
|
let match, token, res, pos;
|
||
|
|
if (state.src[state.pos] !== "$")
|
||
|
|
return false;
|
||
|
|
res = isValidDelim(state, state.pos);
|
||
|
|
if (!res.can_open) {
|
||
|
|
if (!silent)
|
||
|
|
state.pending += "$";
|
||
|
|
state.pos += 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
const start = state.pos + 1;
|
||
|
|
match = start;
|
||
|
|
while ((match = state.src.indexOf("$", match)) !== -1) {
|
||
|
|
pos = match - 1;
|
||
|
|
while (state.src[pos] === "\\") pos -= 1;
|
||
|
|
if ((match - pos) % 2 === 1)
|
||
|
|
break;
|
||
|
|
match += 1;
|
||
|
|
}
|
||
|
|
if (match === -1) {
|
||
|
|
if (!silent)
|
||
|
|
state.pending += "$";
|
||
|
|
state.pos = start;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (match - start === 0) {
|
||
|
|
if (!silent)
|
||
|
|
state.pending += "$$";
|
||
|
|
state.pos = start + 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
res = isValidDelim(state, match);
|
||
|
|
if (!res.can_close) {
|
||
|
|
if (!silent)
|
||
|
|
state.pending += "$";
|
||
|
|
state.pos = start;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (!silent) {
|
||
|
|
token = state.push("math_inline", "math", 0);
|
||
|
|
token.markup = "$";
|
||
|
|
token.content = state.src.slice(start, match);
|
||
|
|
}
|
||
|
|
state.pos = match + 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
function math_block(state, start, end, silent) {
|
||
|
|
let firstLine;
|
||
|
|
let lastLine;
|
||
|
|
let next;
|
||
|
|
let lastPos;
|
||
|
|
let found = false;
|
||
|
|
let pos = state.bMarks[start] + state.tShift[start];
|
||
|
|
let max = state.eMarks[start];
|
||
|
|
if (pos + 2 > max)
|
||
|
|
return false;
|
||
|
|
if (state.src.slice(pos, pos + 2) !== "$$")
|
||
|
|
return false;
|
||
|
|
pos += 2;
|
||
|
|
firstLine = state.src.slice(pos, max);
|
||
|
|
if (silent)
|
||
|
|
return true;
|
||
|
|
if (firstLine.trim().slice(-2) === "$$") {
|
||
|
|
firstLine = firstLine.trim().slice(0, -2);
|
||
|
|
found = true;
|
||
|
|
}
|
||
|
|
for (next = start; !found; ) {
|
||
|
|
next++;
|
||
|
|
if (next >= end)
|
||
|
|
break;
|
||
|
|
pos = state.bMarks[next] + state.tShift[next];
|
||
|
|
max = state.eMarks[next];
|
||
|
|
if (pos < max && state.tShift[next] < state.blkIndent) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (state.src.slice(pos, max).trim().slice(-2) === "$$") {
|
||
|
|
lastPos = state.src.slice(0, max).lastIndexOf("$$");
|
||
|
|
lastLine = state.src.slice(pos, lastPos);
|
||
|
|
found = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
state.line = next + 1;
|
||
|
|
const token = state.push("math_block", "math", 0);
|
||
|
|
token.block = true;
|
||
|
|
token.content = (firstLine && firstLine.trim() ? `${firstLine}
|
||
|
|
` : "") + state.getLines(start + 1, next, state.tShift[start], true) + (lastLine && lastLine.trim() ? lastLine : "");
|
||
|
|
token.map = [start, state.line];
|
||
|
|
token.markup = "$$";
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
function MarkdownItKatex(md, options) {
|
||
|
|
const katexInline = function(latex) {
|
||
|
|
options.displayMode = false;
|
||
|
|
try {
|
||
|
|
return katex.renderToString(latex, options);
|
||
|
|
} catch (error) {
|
||
|
|
if (options.throwOnError)
|
||
|
|
console.warn(error);
|
||
|
|
return latex;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
const inlineRenderer = function(tokens, idx) {
|
||
|
|
return katexInline(tokens[idx].content);
|
||
|
|
};
|
||
|
|
const katexBlock = function(latex) {
|
||
|
|
options.displayMode = true;
|
||
|
|
try {
|
||
|
|
return `<p>${katex.renderToString(latex, options)}</p>`;
|
||
|
|
} catch (error) {
|
||
|
|
if (options.throwOnError)
|
||
|
|
console.warn(error);
|
||
|
|
return latex;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
const blockRenderer = function(tokens, idx) {
|
||
|
|
return `${katexBlock(tokens[idx].content)}
|
||
|
|
`;
|
||
|
|
};
|
||
|
|
md.inline.ruler.after("escape", "math_inline", math_inline);
|
||
|
|
md.block.ruler.after("blockquote", "math_block", math_block, {
|
||
|
|
alt: ["paragraph", "reference", "blockquote", "list"]
|
||
|
|
});
|
||
|
|
md.renderer.rules.math_inline = inlineRenderer;
|
||
|
|
md.renderer.rules.math_block = blockRenderer;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-shiki.ts
|
||
|
|
import { isTruthy } from "@antfu/utils";
|
||
|
|
import { fromHighlighter } from "@shikijs/markdown-it/core";
|
||
|
|
|
||
|
|
// node/syntax/transform/utils.ts
|
||
|
|
function normalizeRangeStr(rangeStr = "") {
|
||
|
|
return !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map((i) => i.trim());
|
||
|
|
}
|
||
|
|
function getCodeBlocks(md) {
|
||
|
|
const codeblocks = Array.from(md.matchAll(/^```[\s\S]*?^```/gm)).map((m) => {
|
||
|
|
const start = m.index;
|
||
|
|
const end = m.index + m[0].length;
|
||
|
|
const startLine = md.slice(0, start).match(/\n/g)?.length || 0;
|
||
|
|
const endLine = md.slice(0, end).match(/\n/g)?.length || 0;
|
||
|
|
return [start, end, startLine, endLine];
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
codeblocks,
|
||
|
|
isInsideCodeblocks(idx) {
|
||
|
|
return codeblocks.some(([s, e]) => s <= idx && idx <= e);
|
||
|
|
},
|
||
|
|
isLineInsideCodeblocks(line) {
|
||
|
|
return codeblocks.some(([, , s, e]) => s <= line && line <= e);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function escapeVueInCode(md) {
|
||
|
|
return md.replace(/\{\{/g, "{{");
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-shiki.ts
|
||
|
|
async function MarkdownItShiki({ data: { config }, mode, utils }) {
|
||
|
|
const transformers = [
|
||
|
|
...utils.shikiOptions.transformers || [],
|
||
|
|
(config.twoslash === true || config.twoslash === mode) && (await import("@shikijs/vitepress-twoslash")).transformerTwoslash({
|
||
|
|
explicitTrigger: true,
|
||
|
|
twoslashOptions: {
|
||
|
|
handbookOptions: {
|
||
|
|
noErrorValidation: true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
pre(pre) {
|
||
|
|
this.addClassToHast(pre, "slidev-code");
|
||
|
|
delete pre.properties.tabindex;
|
||
|
|
},
|
||
|
|
postprocess(code) {
|
||
|
|
return escapeVueInCode(code);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
].filter(isTruthy);
|
||
|
|
return fromHighlighter(utils.shiki, {
|
||
|
|
...utils.shikiOptions,
|
||
|
|
transformers
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/markdown-it-v-drag.ts
|
||
|
|
import { SourceMapConsumer } from "source-map-js";
|
||
|
|
var dragComponentRegex = /<(v-?drag-?\w*)([\s>])/i;
|
||
|
|
var dragDirectiveRegex = /(?<![</\w])v-drag(=".*?")?/i;
|
||
|
|
function MarkdownItVDrag(md, markdownTransformMap) {
|
||
|
|
const visited = /* @__PURE__ */ new WeakSet();
|
||
|
|
const sourceMapConsumers = /* @__PURE__ */ new WeakMap();
|
||
|
|
function getSourceMapConsumer(id) {
|
||
|
|
const s = markdownTransformMap.get(id);
|
||
|
|
if (!s)
|
||
|
|
return void 0;
|
||
|
|
let smc = sourceMapConsumers.get(s);
|
||
|
|
if (smc)
|
||
|
|
return smc;
|
||
|
|
const sourceMap = s.generateMap();
|
||
|
|
smc = new SourceMapConsumer({
|
||
|
|
...sourceMap,
|
||
|
|
version: sourceMap.version.toString()
|
||
|
|
});
|
||
|
|
sourceMapConsumers.set(s, smc);
|
||
|
|
return smc;
|
||
|
|
}
|
||
|
|
const _parse = md.parse;
|
||
|
|
md.parse = function(src, env) {
|
||
|
|
const smc = getSourceMapConsumer(env.id);
|
||
|
|
const toOriginalPos = smc ? (line) => smc.originalPositionFor({ line: line + 1, column: 0 }).line - 1 : (line) => line;
|
||
|
|
function toMarkdownSource(map, idx) {
|
||
|
|
const start = toOriginalPos(map[0]);
|
||
|
|
const end = toOriginalPos(map[1]);
|
||
|
|
return `[${start},${Math.max(start + 1, end)},${idx}]`;
|
||
|
|
}
|
||
|
|
function replaceChildren(token, regex, replacement) {
|
||
|
|
for (const child of token.children ?? []) {
|
||
|
|
if (child.type === "html_block" || child.type === "html_inline") {
|
||
|
|
child.content = child.content.replace(regex, replacement);
|
||
|
|
}
|
||
|
|
replaceChildren(child, regex, replacement);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return _parse.call(this, src, env).map((token) => {
|
||
|
|
if (!["html_block", "html_inline", "inline"].includes(token.type) || !token.content.includes("drag") || visited.has(token))
|
||
|
|
return token;
|
||
|
|
token.content = token.content.replace(dragComponentRegex, (_, tag, space, idx) => {
|
||
|
|
const replacement = `<${tag} :markdownSource="${toMarkdownSource(token.map, idx)}"${space}`;
|
||
|
|
replaceChildren(token, dragComponentRegex, replacement);
|
||
|
|
return replacement;
|
||
|
|
}).replace(dragDirectiveRegex, (_, value, idx) => {
|
||
|
|
const replacement = `v-drag${value ?? ""} :markdownSource="${toMarkdownSource(token.map, idx)}"`;
|
||
|
|
replaceChildren(token, dragDirectiveRegex, replacement);
|
||
|
|
return replacement;
|
||
|
|
});
|
||
|
|
visited.add(token);
|
||
|
|
return token;
|
||
|
|
});
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/markdown-it/index.ts
|
||
|
|
async function useMarkdownItPlugins(md, options, markdownTransformMap) {
|
||
|
|
const { roots, data: { features, config } } = options;
|
||
|
|
if (config.highlighter === "shiki") {
|
||
|
|
md.use(await MarkdownItShiki(options));
|
||
|
|
}
|
||
|
|
md.use(MarkdownItLink);
|
||
|
|
md.use(MarkdownItEscapeInlineCode);
|
||
|
|
md.use(MarkdownItFootnote);
|
||
|
|
md.use(taskLists, { enabled: true, lineNumber: true, label: true });
|
||
|
|
if (features.katex)
|
||
|
|
md.use(MarkdownItKatex, await setupKatex(roots));
|
||
|
|
md.use(MarkdownItVDrag, markdownTransformMap);
|
||
|
|
if (config.mdc)
|
||
|
|
md.use(MarkdownItMdc);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/setups/transformers.ts
|
||
|
|
async function setupTransformers(roots) {
|
||
|
|
const returns = await loadSetups(roots, "transformers.ts", []);
|
||
|
|
const result = {
|
||
|
|
pre: [],
|
||
|
|
preCodeblock: [],
|
||
|
|
postCodeblock: [],
|
||
|
|
post: []
|
||
|
|
};
|
||
|
|
for (const r of [...returns].reverse()) {
|
||
|
|
if (r.pre)
|
||
|
|
result.pre.push(...r.pre);
|
||
|
|
if (r.preCodeblock)
|
||
|
|
result.preCodeblock.push(...r.preCodeblock);
|
||
|
|
}
|
||
|
|
for (const r of returns) {
|
||
|
|
if (r.postCodeblock)
|
||
|
|
result.postCodeblock.push(...r.postCodeblock);
|
||
|
|
if (r.post)
|
||
|
|
result.post.push(...r.post);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/code-wrapper.ts
|
||
|
|
var reCodeBlock = /^```([\w'-]+)?\s*(?:\{([\w*,|-]+)\}\s*?(\{[^}]*\})?([^\r\n]*))?\r?\n([ \t]*\S[\s\S]*?)^```$/gm;
|
||
|
|
function transformCodeWrapper(ctx) {
|
||
|
|
ctx.s.replace(
|
||
|
|
reCodeBlock,
|
||
|
|
(full, lang = "", rangeStr = "", options = "", attrs = "", code) => {
|
||
|
|
const ranges = normalizeRangeStr(rangeStr);
|
||
|
|
code = code.trimEnd();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
return `
|
||
|
|
<CodeBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>
|
||
|
|
|
||
|
|
\`\`\`${lang}${attrs}
|
||
|
|
${code}
|
||
|
|
\`\`\`
|
||
|
|
|
||
|
|
</CodeBlockWrapper>`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/in-page-css.ts
|
||
|
|
function transformPageCSS(ctx) {
|
||
|
|
const codeBlocks = getCodeBlocks(ctx.s.original);
|
||
|
|
ctx.s.replace(
|
||
|
|
/(\n<style[^>]*>)([\s\S]+?)(<\/style>)/g,
|
||
|
|
(full, start, css, end, index) => {
|
||
|
|
if (codeBlocks.isInsideCodeblocks(index))
|
||
|
|
return full;
|
||
|
|
if (!start.includes("scoped"))
|
||
|
|
start = start.replace("<style", "<style scoped");
|
||
|
|
return `${start}
|
||
|
|
${css}${end}`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/katex-wrapper.ts
|
||
|
|
function transformKaTexWrapper(ctx) {
|
||
|
|
ctx.s.replace(
|
||
|
|
/^\$\$(?:\s*\{([\w*,|-]+)\}\s*?(?:(\{[^}]*\})\s*?)?)?\n(\S[\s\S]*?)^\$\$/gm,
|
||
|
|
(full, rangeStr = "", options = "", code) => {
|
||
|
|
const ranges = !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map((i) => i.trim());
|
||
|
|
code = code.trimEnd();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
return `<KaTexBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>
|
||
|
|
|
||
|
|
$$
|
||
|
|
${code}
|
||
|
|
$$
|
||
|
|
</KaTexBlockWrapper>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/magic-move.ts
|
||
|
|
import lz from "lz-string";
|
||
|
|
import { codeToKeyedTokens } from "shiki-magic-move/core";
|
||
|
|
var reMagicMoveBlock = /^````(?:md|markdown) magic-move *(\{[^}]*\})?([^ \n]*)\n([\s\S]+?)^````$/gm;
|
||
|
|
function parseLineNumbersOption(options) {
|
||
|
|
return /lines: *true/.test(options) ? true : /lines: *false/.test(options) ? false : void 0;
|
||
|
|
}
|
||
|
|
function transformMagicMove(ctx) {
|
||
|
|
ctx.s.replace(
|
||
|
|
reMagicMoveBlock,
|
||
|
|
(full, options = "{}", _attrs = "", body) => {
|
||
|
|
const matches = Array.from(body.matchAll(reCodeBlock));
|
||
|
|
if (!matches.length)
|
||
|
|
throw new Error("Magic Move block must contain at least one code block");
|
||
|
|
const defaultLineNumbers = parseLineNumbersOption(options) ?? ctx.options.data.config.lineNumbers;
|
||
|
|
const ranges = matches.map((i) => normalizeRangeStr(i[2]));
|
||
|
|
const steps = matches.map((i) => {
|
||
|
|
const lineNumbers = parseLineNumbersOption(i[3]) ?? defaultLineNumbers;
|
||
|
|
return codeToKeyedTokens(ctx.options.utils.shiki, i[5].trimEnd(), {
|
||
|
|
...ctx.options.utils.shikiOptions,
|
||
|
|
lang: i[1]
|
||
|
|
}, lineNumbers);
|
||
|
|
});
|
||
|
|
const compressed = lz.compressToBase64(JSON.stringify(steps));
|
||
|
|
return `<ShikiMagicMove v-bind="${options}" steps-lz="${compressed}" :step-ranges='${JSON.stringify(ranges)}' />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/mermaid.ts
|
||
|
|
import lz2 from "lz-string";
|
||
|
|
function transformMermaid(ctx) {
|
||
|
|
ctx.s.replace(
|
||
|
|
/^```mermaid *(\{[^\n]*\})?\n([\s\S]+?)\n```/gm,
|
||
|
|
(full, options = "", code = "") => {
|
||
|
|
code = code.trim();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
const encoded = lz2.compressToBase64(code);
|
||
|
|
return `<Mermaid code-lz="${encoded}" v-bind="${options}" />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/monaco.ts
|
||
|
|
import lz3 from "lz-string";
|
||
|
|
function transformMonaco(ctx) {
|
||
|
|
const enabled = ctx.options.data.config.monaco === true || ctx.options.data.config.monaco === ctx.options.mode;
|
||
|
|
if (!enabled) {
|
||
|
|
ctx.s.replace(/\{monaco([\w:,-]*)\}/g, "");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
ctx.s.replace(
|
||
|
|
/^```(\w+) *\{monaco-diff\} *(?:(\{[^\n]*\}) *)?\n([\s\S]+?)^~~~ *\n([\s\S]+?)^```/gm,
|
||
|
|
(full, lang = "ts", options = "{}", code, diff) => {
|
||
|
|
lang = lang.trim();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
const encoded = lz3.compressToBase64(code);
|
||
|
|
const encodedDiff = lz3.compressToBase64(diff);
|
||
|
|
return `<Monaco code-lz="${encoded}" diff-lz="${encodedDiff}" lang="${lang}" v-bind="${options}" />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
ctx.s.replace(
|
||
|
|
/^```(\w+) *\{monaco\} *(?:(\{[^\n]*\}) *)?\n([\s\S]+?)^```/gm,
|
||
|
|
(full, lang = "ts", options = "{}", code) => {
|
||
|
|
lang = lang.trim();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
const encoded = lz3.compressToBase64(code);
|
||
|
|
return `<Monaco code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
ctx.s.replace(
|
||
|
|
/^```(\w+) *\{monaco-run\} *(?:(\{[^\n]*\}) *)?\n([\s\S]+?)^```/gm,
|
||
|
|
(full, lang = "ts", options = "{}", code) => {
|
||
|
|
lang = lang.trim();
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
const encoded = lz3.compressToBase64(code);
|
||
|
|
return `<Monaco runnable code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/plant-uml.ts
|
||
|
|
import { encode as encodePlantUml } from "plantuml-encoder";
|
||
|
|
function transformPlantUml(ctx) {
|
||
|
|
const server = ctx.options.data.config.plantUmlServer;
|
||
|
|
ctx.s.replace(
|
||
|
|
/^```plantuml[^\n{}]*(\{[^}\n]*\})?\n([\s\S]+?)\n```/gm,
|
||
|
|
(full, options = "", content = "") => {
|
||
|
|
const code = encodePlantUml(content.trim());
|
||
|
|
options = options.trim() || "{}";
|
||
|
|
return `<PlantUml :code="'${code}'" :server="'${server}'" v-bind="${options}" />`;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/slot-sugar.ts
|
||
|
|
function transformSlotSugar(ctx) {
|
||
|
|
const linesWithNewline = ctx.s.original.split(/(\r?\n)/g);
|
||
|
|
const codeBlocks = getCodeBlocks(ctx.s.original);
|
||
|
|
const lines = [];
|
||
|
|
for (let i = 0; i < linesWithNewline.length; i += 2) {
|
||
|
|
const line = linesWithNewline[i];
|
||
|
|
const newline = linesWithNewline[i + 1] || "";
|
||
|
|
lines.push(line + newline);
|
||
|
|
}
|
||
|
|
let prevSlot = false;
|
||
|
|
let offset = 0;
|
||
|
|
lines.forEach((line) => {
|
||
|
|
const start = offset;
|
||
|
|
offset += line.length;
|
||
|
|
if (codeBlocks.isInsideCodeblocks(offset))
|
||
|
|
return;
|
||
|
|
const match = line.match(/^::\s*([\w.\-:]+)\s*::(\s*)$/);
|
||
|
|
if (match) {
|
||
|
|
ctx.s.overwrite(start, offset - match[2].length, `${prevSlot ? "\n\n</template>\n" : "\n"}<template v-slot:${match[1]}="slotProps">
|
||
|
|
`);
|
||
|
|
prevSlot = true;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (prevSlot)
|
||
|
|
ctx.s.append("\n\n</template>");
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/snippet.ts
|
||
|
|
import path2 from "node:path";
|
||
|
|
import { slash as slash2 } from "@antfu/utils";
|
||
|
|
import fs5 from "fs-extra";
|
||
|
|
import lz4 from "lz-string";
|
||
|
|
|
||
|
|
// node/vite/monacoWrite.ts
|
||
|
|
import fs4 from "node:fs/promises";
|
||
|
|
import path from "node:path";
|
||
|
|
var monacoWriterWhitelist = /* @__PURE__ */ new Set();
|
||
|
|
function createMonacoWriterPlugin({ userRoot }) {
|
||
|
|
return {
|
||
|
|
name: "slidev:monaco-write",
|
||
|
|
apply: "serve",
|
||
|
|
configureServer(server) {
|
||
|
|
server.ws.on("connection", (socket) => {
|
||
|
|
socket.on("message", async (data) => {
|
||
|
|
let json;
|
||
|
|
try {
|
||
|
|
json = JSON.parse(data.toString());
|
||
|
|
} catch {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (json.type === "custom" && json.event === "slidev:monaco-write") {
|
||
|
|
const { file, content } = json.data;
|
||
|
|
if (!monacoWriterWhitelist.has(file)) {
|
||
|
|
console.error(`[Slidev] Unauthorized file write: ${file}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const filepath = path.join(userRoot, file);
|
||
|
|
console.log("[Slidev] Writing file:", filepath);
|
||
|
|
await fs4.writeFile(filepath, content, "utf-8");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/snippet.ts
|
||
|
|
function dedent(text) {
|
||
|
|
const lines = text.split("\n");
|
||
|
|
const minIndentLength = lines.reduce((acc, line) => {
|
||
|
|
for (let i = 0; i < line.length; i++) {
|
||
|
|
if (line[i] !== " " && line[i] !== " ")
|
||
|
|
return Math.min(i, acc);
|
||
|
|
}
|
||
|
|
return acc;
|
||
|
|
}, Number.POSITIVE_INFINITY);
|
||
|
|
if (minIndentLength < Number.POSITIVE_INFINITY)
|
||
|
|
return lines.map((x) => x.slice(minIndentLength)).join("\n");
|
||
|
|
return text;
|
||
|
|
}
|
||
|
|
function findRegion(lines, regionName) {
|
||
|
|
const regionRegexps = [
|
||
|
|
// javascript, typescript, java
|
||
|
|
[/^\/\/ ?#?region ([\w*-]+)$/, /^\/\/ ?#?endregion/],
|
||
|
|
// css, less, scss
|
||
|
|
[/^\/\* ?#region ([\w*-]+) ?\*\/$/, /^\/\* ?#endregion[\s\w*-]*\*\/$/],
|
||
|
|
// C, C++
|
||
|
|
[/^#pragma region ([\w*-]+)$/, /^#pragma endregion/],
|
||
|
|
// HTML, markdown
|
||
|
|
[/^<!-- #?region ([\w*-]+) -->$/, /^<!-- #?region[\s\w*-]*-->$/],
|
||
|
|
// Visual Basic
|
||
|
|
[/^#Region ([\w*-]+)$/, /^#End Region/],
|
||
|
|
// Bat
|
||
|
|
[/^::#region ([\w*-]+)$/, /^::#endregion/],
|
||
|
|
// C#, PHP, Powershell, Python, perl & misc
|
||
|
|
[/^# ?region ([\w*-]+)$/, /^# ?endregion/]
|
||
|
|
];
|
||
|
|
let endReg = null;
|
||
|
|
let start = -1;
|
||
|
|
for (const [lineId, line] of lines.entries()) {
|
||
|
|
if (endReg === null) {
|
||
|
|
for (const [startReg, end] of regionRegexps) {
|
||
|
|
const match = line.trim().match(startReg);
|
||
|
|
if (match && match[1] === regionName) {
|
||
|
|
start = lineId + 1;
|
||
|
|
endReg = end;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (endReg.test(line.trim())) {
|
||
|
|
return {
|
||
|
|
start,
|
||
|
|
end: lineId,
|
||
|
|
regexp: endReg
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
var reMonacoWrite = /^\{monaco-write\}/;
|
||
|
|
function transformSnippet({ s, slide, options }) {
|
||
|
|
const watchFiles = options.data.watchFiles;
|
||
|
|
const dir = path2.dirname(slide.source?.filepath ?? options.entry ?? options.userRoot);
|
||
|
|
s.replace(
|
||
|
|
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
||
|
|
/^<<<\s*(\S.*?)(#[\w-]+)?\s*(?:\s(\S+?))?\s*(\{.*)?$/gm,
|
||
|
|
(full, filepath = "", regionName = "", lang = "", meta = "") => {
|
||
|
|
const src = slash2(
|
||
|
|
/^@\//.test(filepath) ? path2.resolve(options.userRoot, filepath.slice(2)) : path2.resolve(dir, filepath)
|
||
|
|
);
|
||
|
|
meta = meta.trim();
|
||
|
|
lang = lang.trim();
|
||
|
|
lang = lang || path2.extname(filepath).slice(1);
|
||
|
|
const isAFile = fs5.statSync(src).isFile();
|
||
|
|
if (!fs5.existsSync(src) || !isAFile) {
|
||
|
|
throw new Error(isAFile ? `Code snippet path not found: ${src}` : `Invalid code snippet option`);
|
||
|
|
}
|
||
|
|
let content = fs5.readFileSync(src, "utf8");
|
||
|
|
if (regionName) {
|
||
|
|
const lines = content.split(/\r?\n/);
|
||
|
|
const region = findRegion(lines, regionName.slice(1));
|
||
|
|
if (region) {
|
||
|
|
content = dedent(
|
||
|
|
lines.slice(region.start, region.end).filter((line) => !region.regexp.test(line.trim())).join("\n")
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (meta.match(reMonacoWrite)) {
|
||
|
|
monacoWriterWhitelist.add(filepath);
|
||
|
|
lang = lang.trim();
|
||
|
|
meta = meta.replace(reMonacoWrite, "").trim() || "{}";
|
||
|
|
const encoded = lz4.compressToBase64(content);
|
||
|
|
return `<Monaco writable=${JSON.stringify(filepath)} code-lz="${encoded}" lang="${lang}" v-bind="${meta}" />`;
|
||
|
|
} else {
|
||
|
|
watchFiles[src] ??= /* @__PURE__ */ new Set();
|
||
|
|
watchFiles[src].add(slide.index);
|
||
|
|
}
|
||
|
|
return `\`\`\`${lang} ${meta}
|
||
|
|
${content}
|
||
|
|
\`\`\``;
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/syntax/transform/index.ts
|
||
|
|
async function getMarkdownTransformers(options) {
|
||
|
|
const extras = await setupTransformers(options.roots);
|
||
|
|
return [
|
||
|
|
...extras.pre,
|
||
|
|
transformSnippet,
|
||
|
|
options.data.config.highlighter === "shiki" && transformMagicMove,
|
||
|
|
...extras.preCodeblock,
|
||
|
|
transformMermaid,
|
||
|
|
transformPlantUml,
|
||
|
|
options.data.features.monaco && transformMonaco,
|
||
|
|
...extras.postCodeblock,
|
||
|
|
transformCodeWrapper,
|
||
|
|
options.data.features.katex && transformKaTexWrapper,
|
||
|
|
transformPageCSS,
|
||
|
|
transformSlotSugar,
|
||
|
|
...extras.post
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/markdown.ts
|
||
|
|
async function createMarkdownPlugin(options, { markdown: mdOptions }) {
|
||
|
|
const markdownTransformMap = /* @__PURE__ */ new Map();
|
||
|
|
const transformers = await getMarkdownTransformers(options);
|
||
|
|
return Markdown({
|
||
|
|
include: [/\.md$/],
|
||
|
|
wrapperClasses: "",
|
||
|
|
headEnabled: false,
|
||
|
|
frontmatter: false,
|
||
|
|
escapeCodeTagInterpolation: false,
|
||
|
|
markdownItOptions: {
|
||
|
|
quotes: `""''`,
|
||
|
|
html: true,
|
||
|
|
xhtmlOut: true,
|
||
|
|
linkify: true,
|
||
|
|
...mdOptions?.markdownItOptions
|
||
|
|
},
|
||
|
|
...mdOptions,
|
||
|
|
async markdownItSetup(md) {
|
||
|
|
await useMarkdownItPlugins(md, options, markdownTransformMap);
|
||
|
|
await mdOptions?.markdownItSetup?.(md);
|
||
|
|
},
|
||
|
|
transforms: {
|
||
|
|
...mdOptions?.transforms,
|
||
|
|
before(code, id) {
|
||
|
|
if (options.data.markdownFiles[id])
|
||
|
|
return "";
|
||
|
|
code = mdOptions?.transforms?.before?.(code, id) ?? code;
|
||
|
|
const match = id.match(regexSlideSourceId);
|
||
|
|
if (!match)
|
||
|
|
return code;
|
||
|
|
const s = new MagicString(code);
|
||
|
|
markdownTransformMap.set(id, s);
|
||
|
|
const ctx = {
|
||
|
|
s,
|
||
|
|
slide: options.data.slides[+match[1] - 1],
|
||
|
|
options
|
||
|
|
};
|
||
|
|
for (const transformer of transformers) {
|
||
|
|
if (!transformer)
|
||
|
|
continue;
|
||
|
|
transformer(ctx);
|
||
|
|
if (!ctx.s.isEmpty())
|
||
|
|
ctx.s.commit();
|
||
|
|
}
|
||
|
|
return s.toString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/monacoTypes.ts
|
||
|
|
import fs6 from "node:fs/promises";
|
||
|
|
import { dirname, resolve as resolve5 } from "node:path";
|
||
|
|
import { slash as slash3 } from "@antfu/utils";
|
||
|
|
import fg2 from "fast-glob";
|
||
|
|
import { findDepPkgJsonPath } from "vitefu";
|
||
|
|
function createMonacoTypesLoader({ userRoot, utils }) {
|
||
|
|
return {
|
||
|
|
name: "slidev:monaco-types-loader",
|
||
|
|
resolveId(id) {
|
||
|
|
if (id.startsWith("/@slidev-monaco-types/"))
|
||
|
|
return id;
|
||
|
|
return null;
|
||
|
|
},
|
||
|
|
async load(id) {
|
||
|
|
if (!id.startsWith("/@slidev-monaco-types/"))
|
||
|
|
return null;
|
||
|
|
const url = new URL(id, "http://localhost");
|
||
|
|
if (url.pathname === "/@slidev-monaco-types/resolve") {
|
||
|
|
const query = new URLSearchParams(url.search);
|
||
|
|
const pkg = query.get("pkg");
|
||
|
|
const importer = query.get("importer") ?? userRoot;
|
||
|
|
const pkgJsonPath = await findDepPkgJsonPath(pkg, importer);
|
||
|
|
if (!pkgJsonPath)
|
||
|
|
throw new Error(`Package "${pkg}" not found in "${importer}"`);
|
||
|
|
const root = slash3(dirname(pkgJsonPath));
|
||
|
|
const pkgJson = JSON.parse(await fs6.readFile(pkgJsonPath, "utf-8"));
|
||
|
|
let deps = Object.keys(pkgJson.dependencies ?? {});
|
||
|
|
deps = deps.filter((pkg2) => !utils.isMonacoTypesIgnored(pkg2));
|
||
|
|
return [
|
||
|
|
`import "/@slidev-monaco-types/load?${new URLSearchParams({ root, name: pkgJson.name })}"`,
|
||
|
|
...deps.map((dep) => `import "/@slidev-monaco-types/resolve?${new URLSearchParams({ pkg: dep, importer: root })}"`)
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
if (url.pathname === "/@slidev-monaco-types/load") {
|
||
|
|
const query = new URLSearchParams(url.search);
|
||
|
|
const root = query.get("root");
|
||
|
|
const name = query.get("name");
|
||
|
|
const files = await fg2(
|
||
|
|
[
|
||
|
|
"**/*.ts",
|
||
|
|
"**/*.mts",
|
||
|
|
"**/*.cts",
|
||
|
|
"package.json"
|
||
|
|
],
|
||
|
|
{
|
||
|
|
cwd: root,
|
||
|
|
followSymbolicLinks: true,
|
||
|
|
ignore: ["**/node_modules/**"]
|
||
|
|
}
|
||
|
|
);
|
||
|
|
if (!files.length)
|
||
|
|
return "/** No files found **/";
|
||
|
|
return [
|
||
|
|
'import { addFile } from "@slidev/client/setup/monaco.ts"',
|
||
|
|
...files.map((file) => `addFile(() => import(${JSON.stringify(`${toAtFS(resolve5(root, file))}?monaco-types&raw`)}), ${JSON.stringify(`node_modules/${name}/${file}`)})`)
|
||
|
|
].join("\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/remoteAssets.ts
|
||
|
|
async function createRemoteAssetsPlugin({ data: { config }, mode }, pluginOptions) {
|
||
|
|
if (!(config.remoteAssets === true || config.remoteAssets === mode))
|
||
|
|
return;
|
||
|
|
const { VitePluginRemoteAssets, DefaultRules } = await import("vite-plugin-remote-assets");
|
||
|
|
return VitePluginRemoteAssets({
|
||
|
|
resolveMode: (id) => id.endsWith("index.html") ? "relative" : "@fs",
|
||
|
|
awaitDownload: mode === "build",
|
||
|
|
...pluginOptions.remoteAssets,
|
||
|
|
rules: [
|
||
|
|
...DefaultRules,
|
||
|
|
{
|
||
|
|
match: /\b(https?:\/\/image.unsplash\.com.*?)(?=[`'")\]])/gi,
|
||
|
|
ext: ".png"
|
||
|
|
},
|
||
|
|
...pluginOptions.remoteAssets?.rules ?? []
|
||
|
|
]
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/serverRef.ts
|
||
|
|
import ServerRef from "vite-plugin-vue-server-ref";
|
||
|
|
|
||
|
|
// node/integrations/drawings.ts
|
||
|
|
import { basename, dirname as dirname2, join as join9, resolve as resolve6 } from "node:path";
|
||
|
|
import fg3 from "fast-glob";
|
||
|
|
import fs7 from "fs-extra";
|
||
|
|
function resolveDrawingsDir(options) {
|
||
|
|
return options.data.config.drawings.persist ? resolve6(
|
||
|
|
dirname2(options.entry),
|
||
|
|
options.data.config.drawings.persist
|
||
|
|
) : void 0;
|
||
|
|
}
|
||
|
|
async function loadDrawings(options) {
|
||
|
|
const dir = resolveDrawingsDir(options);
|
||
|
|
if (!dir || !fs7.existsSync(dir))
|
||
|
|
return {};
|
||
|
|
const files = await fg3("*.svg", {
|
||
|
|
onlyFiles: true,
|
||
|
|
cwd: dir,
|
||
|
|
absolute: true,
|
||
|
|
suppressErrors: true
|
||
|
|
});
|
||
|
|
const obj = {};
|
||
|
|
await Promise.all(files.map(async (path4) => {
|
||
|
|
const num = +basename(path4, ".svg");
|
||
|
|
if (Number.isNaN(num))
|
||
|
|
return;
|
||
|
|
const content = await fs7.readFile(path4, "utf8");
|
||
|
|
const lines = content.split(/\n/g);
|
||
|
|
obj[num.toString()] = lines.slice(1, -1).join("\n");
|
||
|
|
}));
|
||
|
|
return obj;
|
||
|
|
}
|
||
|
|
async function writeDrawings(options, drawing) {
|
||
|
|
const dir = resolveDrawingsDir(options);
|
||
|
|
if (!dir)
|
||
|
|
return;
|
||
|
|
const width = options.data.config.canvasWidth;
|
||
|
|
const height = Math.round(width / options.data.config.aspectRatio);
|
||
|
|
const SVG_HEAD = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`;
|
||
|
|
await fs7.ensureDir(dir);
|
||
|
|
return Promise.all(
|
||
|
|
Object.entries(drawing).map(async ([key, value]) => {
|
||
|
|
if (!value)
|
||
|
|
return;
|
||
|
|
const svg = `${SVG_HEAD}
|
||
|
|
${value}
|
||
|
|
</svg>`;
|
||
|
|
await fs7.writeFile(join9(dir, `${key}.svg`), svg, "utf-8");
|
||
|
|
})
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/integrations/snapshots.ts
|
||
|
|
import { dirname as dirname3, join as join10, resolve as resolve7 } from "node:path";
|
||
|
|
import fs8 from "fs-extra";
|
||
|
|
function resolveSnapshotsDir(options) {
|
||
|
|
return resolve7(dirname3(options.entry), ".slidev/snapshots");
|
||
|
|
}
|
||
|
|
async function loadSnapshots(options) {
|
||
|
|
const dir = resolveSnapshotsDir(options);
|
||
|
|
const file = join10(dir, "snapshots.json");
|
||
|
|
if (!dir || !fs8.existsSync(file))
|
||
|
|
return {};
|
||
|
|
return JSON.parse(await fs8.readFile(file, "utf8"));
|
||
|
|
}
|
||
|
|
async function writeSnapshots(options, data) {
|
||
|
|
const dir = resolveSnapshotsDir(options);
|
||
|
|
if (!dir)
|
||
|
|
return;
|
||
|
|
await fs8.ensureDir(dir);
|
||
|
|
await fs8.writeFile(join10(dir, "snapshots.json"), JSON.stringify(data, null, 2), "utf-8");
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/serverRef.ts
|
||
|
|
async function createServerRefPlugin(options, pluginOptions) {
|
||
|
|
return ServerRef({
|
||
|
|
debug: false,
|
||
|
|
// process.env.NODE_ENV === 'development',
|
||
|
|
state: {
|
||
|
|
sync: false,
|
||
|
|
nav: {
|
||
|
|
page: 0,
|
||
|
|
clicks: 0
|
||
|
|
},
|
||
|
|
drawings: await loadDrawings(options),
|
||
|
|
snapshots: await loadSnapshots(options),
|
||
|
|
...pluginOptions.serverRef?.state
|
||
|
|
},
|
||
|
|
onChanged(key, data, patch, timestamp) {
|
||
|
|
pluginOptions.serverRef?.onChanged?.(key, data, patch, timestamp);
|
||
|
|
if (options.data.config.drawings.persist && key === "drawings")
|
||
|
|
writeDrawings(options, patch ?? data);
|
||
|
|
if (key === "snapshots")
|
||
|
|
writeSnapshots(options, data);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/staticCopy.ts
|
||
|
|
import { existsSync as existsSync5 } from "node:fs";
|
||
|
|
import { join as join11 } from "node:path";
|
||
|
|
async function createStaticCopyPlugin({ themeRoots, addonRoots }, pluginOptions) {
|
||
|
|
const publicDirs = [...themeRoots, ...addonRoots].map((i) => join11(i, "public")).filter(existsSync5);
|
||
|
|
if (!publicDirs.length)
|
||
|
|
return;
|
||
|
|
const { viteStaticCopy } = await import("vite-plugin-static-copy");
|
||
|
|
return viteStaticCopy({
|
||
|
|
silent: true,
|
||
|
|
targets: publicDirs.map((dir) => ({
|
||
|
|
src: `${dir}/*`,
|
||
|
|
dest: "theme"
|
||
|
|
})),
|
||
|
|
...pluginOptions.staticCopy
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/unocss.ts
|
||
|
|
import UnoCSS from "unocss/vite";
|
||
|
|
|
||
|
|
// node/setups/unocss.ts
|
||
|
|
import { existsSync as existsSync6, readFileSync } from "node:fs";
|
||
|
|
import { resolve as resolve8 } from "node:path";
|
||
|
|
import { mergeConfigs, presetIcons } from "unocss";
|
||
|
|
async function setupUnocss({ clientRoot, roots, data, utils }) {
|
||
|
|
async function loadFileConfigs(root) {
|
||
|
|
return (await Promise.all([
|
||
|
|
resolve8(root, "uno.config.ts"),
|
||
|
|
resolve8(root, "unocss.config.ts")
|
||
|
|
].map(async (i) => {
|
||
|
|
if (!existsSync6(i))
|
||
|
|
return void 0;
|
||
|
|
const loaded = await loadModule(i);
|
||
|
|
return "default" in loaded ? loaded.default : loaded;
|
||
|
|
}))).filter((x) => !!x);
|
||
|
|
}
|
||
|
|
const configs = [
|
||
|
|
{
|
||
|
|
presets: [
|
||
|
|
presetIcons({
|
||
|
|
collectionsNodeResolvePath: utils.iconsResolvePath,
|
||
|
|
collections: {
|
||
|
|
slidev: {
|
||
|
|
logo: () => readFileSync(resolve8(clientRoot, "assets/logo.svg"), "utf-8")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
]
|
||
|
|
},
|
||
|
|
...await loadFileConfigs(clientRoot),
|
||
|
|
...await loadSetups(roots, "unocss.ts", [], loadFileConfigs)
|
||
|
|
].filter(Boolean);
|
||
|
|
const config = mergeConfigs(configs);
|
||
|
|
config.theme ||= {};
|
||
|
|
config.theme.fontFamily ||= {};
|
||
|
|
config.theme.fontFamily.sans ||= data.config.fonts.sans.join(",");
|
||
|
|
config.theme.fontFamily.mono ||= data.config.fonts.mono.join(",");
|
||
|
|
config.theme.fontFamily.serif ||= data.config.fonts.serif.join(",");
|
||
|
|
return config;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/unocss.ts
|
||
|
|
async function createUnocssPlugin(options, pluginOptions) {
|
||
|
|
return UnoCSS({
|
||
|
|
configFile: false,
|
||
|
|
...await setupUnocss(options),
|
||
|
|
...pluginOptions.unocss
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/vue.ts
|
||
|
|
import Vue from "@vitejs/plugin-vue";
|
||
|
|
import VueJsx from "@vitejs/plugin-vue-jsx";
|
||
|
|
var customElements = /* @__PURE__ */ new Set([
|
||
|
|
// katex
|
||
|
|
"annotation",
|
||
|
|
"math",
|
||
|
|
"menclose",
|
||
|
|
"mfrac",
|
||
|
|
"mglyph",
|
||
|
|
"mi",
|
||
|
|
"mlabeledtr",
|
||
|
|
"mn",
|
||
|
|
"mo",
|
||
|
|
"mover",
|
||
|
|
"mpadded",
|
||
|
|
"mphantom",
|
||
|
|
"mroot",
|
||
|
|
"mrow",
|
||
|
|
"mspace",
|
||
|
|
"msqrt",
|
||
|
|
"mstyle",
|
||
|
|
"msub",
|
||
|
|
"msubsup",
|
||
|
|
"msup",
|
||
|
|
"mtable",
|
||
|
|
"mtd",
|
||
|
|
"mtext",
|
||
|
|
"mtr",
|
||
|
|
"munder",
|
||
|
|
"munderover",
|
||
|
|
"semantics"
|
||
|
|
]);
|
||
|
|
async function createVuePlugin(_options, pluginOptions) {
|
||
|
|
const {
|
||
|
|
vue: vueOptions = {},
|
||
|
|
vuejsx: vuejsxOptions = {}
|
||
|
|
} = pluginOptions;
|
||
|
|
const VuePlugin = Vue({
|
||
|
|
include: [/\.vue$/, /\.vue\?vue/, /\.vue\?v=/, /\.md$/, /\.md\?vue/],
|
||
|
|
exclude: [],
|
||
|
|
...vueOptions,
|
||
|
|
template: {
|
||
|
|
...vueOptions?.template,
|
||
|
|
compilerOptions: {
|
||
|
|
...vueOptions?.template?.compilerOptions,
|
||
|
|
isCustomElement(tag) {
|
||
|
|
return customElements.has(tag) || vueOptions?.template?.compilerOptions?.isCustomElement?.(tag);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const VueJsxPlugin = VueJsx(vuejsxOptions);
|
||
|
|
return [
|
||
|
|
VueJsxPlugin,
|
||
|
|
VuePlugin
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/vite/index.ts
|
||
|
|
function ViteSlidevPlugin(options, pluginOptions = {}, serverOptions = {}) {
|
||
|
|
return Promise.all([
|
||
|
|
createSlidesLoader(options, serverOptions),
|
||
|
|
createMarkdownPlugin(options, pluginOptions),
|
||
|
|
createLayoutWrapperPlugin(options),
|
||
|
|
createContextInjectionPlugin(),
|
||
|
|
createVuePlugin(options, pluginOptions),
|
||
|
|
createHmrPatchPlugin(),
|
||
|
|
createComponentsPlugin(options, pluginOptions),
|
||
|
|
createIconsPlugin(options, pluginOptions),
|
||
|
|
createRemoteAssetsPlugin(options, pluginOptions),
|
||
|
|
createServerRefPlugin(options, pluginOptions),
|
||
|
|
createConfigPlugin(options),
|
||
|
|
createMonacoTypesLoader(options),
|
||
|
|
createMonacoWriterPlugin(options),
|
||
|
|
createVueCompilerFlagsPlugin(options),
|
||
|
|
createUnocssPlugin(options, pluginOptions),
|
||
|
|
createStaticCopyPlugin(options, pluginOptions),
|
||
|
|
createInspectPlugin(options, pluginOptions)
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/commands/shared.ts
|
||
|
|
var sharedMd = MarkdownIt({ html: true });
|
||
|
|
sharedMd.use(MarkdownItLink);
|
||
|
|
function getSlideTitle(data) {
|
||
|
|
const tokens = sharedMd.parseInline(data.config.title, {});
|
||
|
|
const title = stringifyMarkdownTokens(tokens);
|
||
|
|
const slideTitle = data.config.titleTemplate.replace("%s", title);
|
||
|
|
return slideTitle === "Slidev - Slidev" ? "Slidev" : slideTitle;
|
||
|
|
}
|
||
|
|
async function resolveViteConfigs(options, baseConfig, overrideConfigs, command, serverOptions) {
|
||
|
|
const configEnv = {
|
||
|
|
mode: command === "build" ? "production" : "development",
|
||
|
|
command
|
||
|
|
};
|
||
|
|
const files = options.roots.map((i) => join12(i, "vite.config.ts"));
|
||
|
|
for (const file of files) {
|
||
|
|
if (!existsSync7(file))
|
||
|
|
continue;
|
||
|
|
const viteConfig = await loadConfigFromFile(configEnv, file);
|
||
|
|
if (!viteConfig?.config)
|
||
|
|
continue;
|
||
|
|
baseConfig = mergeConfig2(baseConfig, viteConfig.config);
|
||
|
|
}
|
||
|
|
baseConfig = mergeConfig2(baseConfig, overrideConfigs);
|
||
|
|
baseConfig = mergeConfig2(baseConfig, {
|
||
|
|
configFile: false,
|
||
|
|
root: options.userRoot,
|
||
|
|
plugins: await ViteSlidevPlugin(options, baseConfig.slidev, serverOptions),
|
||
|
|
define: {
|
||
|
|
// Fixes Vue production mode breaking PDF Export #1245
|
||
|
|
__VUE_PROD_DEVTOOLS__: false
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return baseConfig;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/setups/indexHtml.ts
|
||
|
|
function toAttrValue(unsafe) {
|
||
|
|
return JSON.stringify(escapeHtml(String(unsafe)));
|
||
|
|
}
|
||
|
|
function setupIndexHtml({ mode, entry, clientRoot, userRoot, roots, data }) {
|
||
|
|
let main = readFileSync2(join13(clientRoot, "index.html"), "utf-8");
|
||
|
|
let head = "";
|
||
|
|
let body = "";
|
||
|
|
const { info, author, keywords } = data.headmatter;
|
||
|
|
head += [
|
||
|
|
`<meta name="slidev:version" content="${version}">`,
|
||
|
|
mode === "dev" && `<meta charset="slidev:entry" content="${slash4(entry)}">`,
|
||
|
|
`<link rel="icon" href="${data.config.favicon}">`,
|
||
|
|
`<title>${getSlideTitle(data)}</title>`,
|
||
|
|
info && `<meta name="description" content=${toAttrValue(info)}>`,
|
||
|
|
author && `<meta name="author" content=${toAttrValue(author)}>`,
|
||
|
|
keywords && `<meta name="keywords" content=${toAttrValue(Array.isArray(keywords) ? keywords.join(", ") : keywords)}>`
|
||
|
|
].filter(Boolean).join("\n");
|
||
|
|
for (const root of roots) {
|
||
|
|
const path4 = join13(root, "index.html");
|
||
|
|
if (!existsSync8(path4))
|
||
|
|
continue;
|
||
|
|
const index = readFileSync2(path4, "utf-8");
|
||
|
|
if (root === userRoot && index.includes("<!DOCTYPE")) {
|
||
|
|
console.error(yellow2(`[Slidev] Ignored provided index.html with doctype declaration. (${white(path4)})`));
|
||
|
|
console.error(yellow2("This file may be generated by Slidev, please remove it from your project."));
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
head += `
|
||
|
|
${(index.match(/<head>([\s\S]*?)<\/head>/i)?.[1] || "").trim()}`;
|
||
|
|
body += `
|
||
|
|
${(index.match(/<body>([\s\S]*?)<\/body>/i)?.[1] || "").trim()}`;
|
||
|
|
}
|
||
|
|
if (data.features.tweet)
|
||
|
|
body += '\n<script async src="https://platform.twitter.com/widgets.js"></script>';
|
||
|
|
if (data.config.fonts.webfonts.length && data.config.fonts.provider !== "none")
|
||
|
|
head += `
|
||
|
|
<link rel="stylesheet" href="${generateGoogleFontsUrl(data.config.fonts)}" type="text/css">`;
|
||
|
|
if (data.headmatter.lang)
|
||
|
|
main = main.replace('<html lang="en">', `<html lang="${data.headmatter.lang}">`);
|
||
|
|
main = main.replace("__ENTRY__", toAtFS(join13(clientRoot, "main.ts"))).replace("<!-- head -->", head).replace("<!-- body -->", body);
|
||
|
|
return main;
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/setups/shiki.ts
|
||
|
|
import fs9 from "node:fs/promises";
|
||
|
|
import { bundledLanguages, createHighlighter } from "shiki";
|
||
|
|
var cachedRoots;
|
||
|
|
var cachedShiki;
|
||
|
|
async function setupShiki(roots) {
|
||
|
|
if (cachedRoots === roots)
|
||
|
|
return cachedShiki;
|
||
|
|
cachedShiki?.shiki.dispose();
|
||
|
|
const options = await loadSetups(
|
||
|
|
roots,
|
||
|
|
"shiki.ts",
|
||
|
|
[{
|
||
|
|
/** @deprecated */
|
||
|
|
async loadTheme(path4) {
|
||
|
|
console.warn("[slidev] `loadTheme` in `setup/shiki.ts` is deprecated. Pass directly the theme name it's supported by Shiki. For custom themes, load it manually via `JSON.parse(fs.readFileSync(path, 'utf-8'))` and pass the raw JSON object instead.");
|
||
|
|
return JSON.parse(await fs9.readFile(path4, "utf-8"));
|
||
|
|
}
|
||
|
|
}]
|
||
|
|
);
|
||
|
|
const mergedOptions = Object.assign({}, ...options);
|
||
|
|
if ("theme" in mergedOptions && "themes" in mergedOptions)
|
||
|
|
delete mergedOptions.theme;
|
||
|
|
if (mergedOptions.theme && typeof mergedOptions.theme !== "string" && !mergedOptions.theme.name && !mergedOptions.theme.tokenColors) {
|
||
|
|
mergedOptions.themes = mergedOptions.theme;
|
||
|
|
delete mergedOptions.theme;
|
||
|
|
}
|
||
|
|
if (!mergedOptions.theme && !mergedOptions.themes) {
|
||
|
|
mergedOptions.themes = {
|
||
|
|
dark: "vitesse-dark",
|
||
|
|
light: "vitesse-light"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
if (mergedOptions.themes)
|
||
|
|
mergedOptions.defaultColor = false;
|
||
|
|
const shiki = await createHighlighter({
|
||
|
|
...mergedOptions,
|
||
|
|
langs: mergedOptions.langs ?? Object.keys(bundledLanguages),
|
||
|
|
themes: "themes" in mergedOptions ? Object.values(mergedOptions.themes) : [mergedOptions.theme]
|
||
|
|
});
|
||
|
|
cachedRoots = roots;
|
||
|
|
return cachedShiki = {
|
||
|
|
shiki,
|
||
|
|
shikiOptions: mergedOptions
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// node/options.ts
|
||
|
|
var debug = Debug("slidev:options");
|
||
|
|
async function resolveOptions(entryOptions, mode) {
|
||
|
|
const entry = await resolveEntry(entryOptions.entry);
|
||
|
|
const rootsInfo = await getRoots(entry);
|
||
|
|
const loaded = await parser.load(rootsInfo.userRoot, entry, void 0, mode);
|
||
|
|
let themeRaw = entryOptions.theme || loaded.headmatter.theme;
|
||
|
|
themeRaw = themeRaw === null ? "none" : themeRaw || "default";
|
||
|
|
const [theme, themeRoot] = await resolveTheme(themeRaw, entry);
|
||
|
|
const themeRoots = themeRoot ? [themeRoot] : [];
|
||
|
|
const themeMeta = themeRoot ? await getThemeMeta(theme, themeRoot) : void 0;
|
||
|
|
const config = parser.resolveConfig(loaded.headmatter, themeMeta, entryOptions.entry);
|
||
|
|
const addonRoots = await resolveAddons(config.addons);
|
||
|
|
const roots = uniq5([...themeRoots, ...addonRoots, rootsInfo.userRoot]);
|
||
|
|
if (entryOptions.download)
|
||
|
|
config.download ||= entryOptions.download;
|
||
|
|
debug({
|
||
|
|
...rootsInfo,
|
||
|
|
...entryOptions,
|
||
|
|
config,
|
||
|
|
mode,
|
||
|
|
entry,
|
||
|
|
themeRaw,
|
||
|
|
theme,
|
||
|
|
themeRoots,
|
||
|
|
addonRoots,
|
||
|
|
roots
|
||
|
|
});
|
||
|
|
const data = {
|
||
|
|
...loaded,
|
||
|
|
config,
|
||
|
|
themeMeta
|
||
|
|
};
|
||
|
|
const resolved = {
|
||
|
|
...rootsInfo,
|
||
|
|
...entryOptions,
|
||
|
|
data,
|
||
|
|
mode,
|
||
|
|
entry,
|
||
|
|
themeRaw,
|
||
|
|
theme,
|
||
|
|
themeRoots,
|
||
|
|
addonRoots,
|
||
|
|
roots
|
||
|
|
};
|
||
|
|
return {
|
||
|
|
...resolved,
|
||
|
|
utils: await createDataUtils(resolved)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
async function createDataUtils(resolved) {
|
||
|
|
const monacoTypesIgnorePackagesMatches = (resolved.data.config.monacoTypesIgnorePackages || []).map((i) => mm.matcher(i));
|
||
|
|
let _layouts_cache_time = 0;
|
||
|
|
let _layouts_cache = {};
|
||
|
|
return {
|
||
|
|
...await setupShiki(resolved.roots),
|
||
|
|
indexHtml: setupIndexHtml(resolved),
|
||
|
|
define: getDefine(resolved),
|
||
|
|
iconsResolvePath: [resolved.clientRoot, ...resolved.roots].reverse(),
|
||
|
|
isMonacoTypesIgnored: (pkg) => monacoTypesIgnorePackagesMatches.some((i) => i(pkg)),
|
||
|
|
getLayouts: () => {
|
||
|
|
const now = Date.now();
|
||
|
|
if (now - _layouts_cache_time < 2e3)
|
||
|
|
return _layouts_cache;
|
||
|
|
const layouts = {};
|
||
|
|
for (const root of [resolved.clientRoot, ...resolved.roots]) {
|
||
|
|
const layoutPaths = fg4.sync("layouts/**/*.{vue,ts}", {
|
||
|
|
cwd: root,
|
||
|
|
absolute: true,
|
||
|
|
suppressErrors: true
|
||
|
|
});
|
||
|
|
for (const layoutPath of layoutPaths) {
|
||
|
|
const layoutName = path3.basename(layoutPath).replace(/\.\w+$/, "");
|
||
|
|
layouts[layoutName] = layoutPath;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_layouts_cache_time = now;
|
||
|
|
_layouts_cache = layouts;
|
||
|
|
return layouts;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function getDefine(options) {
|
||
|
|
const matchMode = (mode) => mode === true || mode === options.mode;
|
||
|
|
return objectMap2(
|
||
|
|
{
|
||
|
|
__DEV__: options.mode === "dev",
|
||
|
|
__SLIDEV_CLIENT_ROOT__: toAtFS(options.clientRoot),
|
||
|
|
__SLIDEV_HASH_ROUTE__: options.data.config.routerMode === "hash",
|
||
|
|
__SLIDEV_FEATURE_DRAWINGS__: matchMode(options.data.config.drawings.enabled),
|
||
|
|
__SLIDEV_FEATURE_EDITOR__: options.mode === "dev" && options.data.config.editor !== false,
|
||
|
|
__SLIDEV_FEATURE_DRAWINGS_PERSIST__: !!options.data.config.drawings.persist,
|
||
|
|
__SLIDEV_FEATURE_RECORD__: matchMode(options.data.config.record),
|
||
|
|
__SLIDEV_FEATURE_PRESENTER__: matchMode(options.data.config.presenter),
|
||
|
|
__SLIDEV_FEATURE_PRINT__: options.mode === "export" || options.mode === "build" && [true, "true", "auto"].includes(options.data.config.download),
|
||
|
|
__SLIDEV_FEATURE_BROWSER_EXPORTER__: matchMode(options.data.config.browserExporter),
|
||
|
|
__SLIDEV_FEATURE_WAKE_LOCK__: matchMode(options.data.config.wakeLock),
|
||
|
|
__SLIDEV_HAS_SERVER__: options.mode !== "build"
|
||
|
|
},
|
||
|
|
(v, k) => [v, JSON.stringify(k)]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export {
|
||
|
|
version,
|
||
|
|
resolveAddons,
|
||
|
|
resolveTheme,
|
||
|
|
getThemeMeta,
|
||
|
|
parser,
|
||
|
|
loadSetups,
|
||
|
|
resolveOptions,
|
||
|
|
createDataUtils,
|
||
|
|
ViteSlidevPlugin,
|
||
|
|
resolveViteConfigs
|
||
|
|
};
|