464 lines
12 KiB
JavaScript
Raw Normal View History

2026-05-31 13:21:13 +02:00
import {
parseAspectRatio
} from "./chunk-47YLTX76.mjs";
// src/core.ts
import { ensurePrefix } from "@antfu/utils";
import YAML from "yaml";
// src/config.ts
import { toArray, uniq } from "@antfu/utils";
function getDefaultConfig() {
return {
theme: "default",
title: "Slidev",
titleTemplate: "%s - Slidev",
addons: [],
remoteAssets: false,
monaco: true,
monacoTypesSource: "local",
monacoTypesAdditionalPackages: [],
monacoTypesIgnorePackages: [],
monacoRunAdditionalDeps: [],
download: false,
export: {},
info: false,
highlighter: "shiki",
twoslash: true,
lineNumbers: false,
colorSchema: "auto",
routerMode: "history",
aspectRatio: 16 / 9,
canvasWidth: 980,
exportFilename: "",
selectable: false,
themeConfig: {},
fonts: {},
favicon: "https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png",
drawings: {},
plantUmlServer: "https://www.plantuml.com/plantuml",
codeCopy: true,
author: "",
record: "dev",
css: "unocss",
presenter: true,
browserExporter: "dev",
htmlAttrs: {},
transition: null,
editor: true,
contextMenu: null,
overviewSnapshots: false,
wakeLock: true,
remote: false,
mdc: false
};
}
function resolveConfig(headmatter, themeMeta = {}, filepath, verify = false) {
const themeHightlighter = ["prism", "shiki", "shikiji"].includes(themeMeta.highlighter || "") ? themeMeta.highlighter : void 0;
const themeColorSchema = ["light", "dark"].includes(themeMeta.colorSchema || "") ? themeMeta.colorSchema : void 0;
const defaultConfig = getDefaultConfig();
const config = {
...defaultConfig,
highlighter: themeHightlighter || defaultConfig.highlighter,
colorSchema: themeColorSchema || defaultConfig.colorSchema,
...themeMeta.defaults,
...headmatter.config,
...headmatter,
fonts: resolveFonts({
...themeMeta.defaults?.fonts,
...headmatter.config?.fonts,
...headmatter?.fonts
}),
drawings: resolveDrawings(headmatter.drawings, filepath),
htmlAttrs: {
...themeMeta.defaults?.htmlAttrs,
...headmatter.config?.htmlAttrs,
...headmatter?.htmlAttrs
}
};
if (config.highlighter === "shikiji") {
console.warn(`[slidev] "shikiji" is merged back to "shiki", you can safely change it "highlighter: shiki"`);
config.highlighter = "shiki";
}
if (config.highlighter === "prism")
throw new Error(`[slidev] "prism" support has been dropped. Please use "highlighter: shiki" instead`);
if (config.colorSchema !== "dark" && config.colorSchema !== "light")
config.colorSchema = "auto";
if (themeColorSchema && config.colorSchema === "auto")
config.colorSchema = themeColorSchema;
config.aspectRatio = parseAspectRatio(config.aspectRatio);
if (verify)
verifyConfig(config, themeMeta);
return config;
}
function verifyConfig(config, themeMeta = {}, warn = (v) => console.warn(`[slidev] ${v}`)) {
const themeHightlighter = themeMeta.highlighter === "shiki" ? themeMeta.highlighter : void 0;
const themeColorSchema = ["light", "dark"].includes(themeMeta.colorSchema || "") ? themeMeta.colorSchema : void 0;
if (themeColorSchema && config.colorSchema !== themeColorSchema)
warn(`Color schema "${config.colorSchema}" does not supported by the theme`);
if (themeHightlighter && config.highlighter !== themeHightlighter)
warn(`Syntax highlighter "${config.highlighter}" does not supported by the theme`);
if (config.css !== "unocss") {
warn(`Unsupported Atomic CSS engine "${config.css}", fallback to UnoCSS`);
config.css = "unocss";
}
}
function resolveFonts(fonts = {}) {
const {
fallbacks = true,
italic = false,
provider = "google"
} = fonts;
let sans = toArray(fonts.sans).flatMap((i) => i.split(/,\s*/g)).map((i) => i.trim());
let serif = toArray(fonts.serif).flatMap((i) => i.split(/,\s*/g)).map((i) => i.trim());
let mono = toArray(fonts.mono).flatMap((i) => i.split(/,\s*/g)).map((i) => i.trim());
const weights = toArray(fonts.weights || "200,400,600").flatMap((i) => i.toString().split(/,\s*/g)).map((i) => i.trim());
const custom = toArray(fonts.custom).flatMap((i) => i.split(/,\s*/g)).map((i) => i.trim());
const local = toArray(fonts.local).flatMap((i) => i.split(/,\s*/g)).map((i) => i.trim());
const webfonts = fonts.webfonts ? fonts.webfonts : fallbacks ? uniq([...sans, ...serif, ...mono, ...custom]) : [];
webfonts.filter((i) => local.includes(i));
function toQuoted(font) {
if (/^(['"]).*\1$/.test(font))
return font;
return `"${font}"`;
}
if (fallbacks) {
sans = uniq([
...sans.map(toQuoted),
"ui-sans-serif",
"system-ui",
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
"Roboto",
'"Helvetica Neue"',
"Arial",
'"Noto Sans"',
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"'
]);
serif = uniq([
...serif.map(toQuoted),
"ui-serif",
"Georgia",
"Cambria",
'"Times New Roman"',
"Times",
"serif"
]);
mono = uniq([
...mono.map(toQuoted),
"ui-monospace",
"SFMono-Regular",
"Menlo",
"Monaco",
"Consolas",
'"Liberation Mono"',
'"Courier New"',
"monospace"
]);
}
return {
sans,
serif,
mono,
webfonts,
provider,
local,
italic,
weights
};
}
function resolveDrawings(options = {}, filepath) {
const {
enabled = true,
persist = false,
presenterOnly = false,
syncAll = true
} = options;
const persistPath = typeof persist === "string" ? persist : persist ? `.slidev/drawings${filepath ? `/${filepath.match(/([^\\/]+?)(\.\w+)?$/)?.[1]}` : ""}` : false;
return {
enabled,
persist: persistPath,
presenterOnly,
syncAll
};
}
// src/core.ts
function stringify(data) {
return `${data.slides.map(stringifySlide).join("\n").trim()}
`;
}
function stringifySlide(data, idx = 0) {
return data.raw.startsWith("---") || idx === 0 ? data.raw : `---
${ensurePrefix("\n", data.raw)}`;
}
function prettifySlide(data) {
const trimed = data.content.trim();
data.content = trimed ? `
${data.content.trim()}
` : "";
data.raw = data.frontmatterDoc?.contents ? data.frontmatterStyle === "yaml" ? `\`\`\`yaml
${data.frontmatterDoc.toString().trim()}
\`\`\`
${data.content}` : `---
${data.frontmatterDoc.toString().trim()}
---
${data.content}` : data.content;
if (data.note)
data.raw += `
<!--
${data.note.trim()}
-->
`;
return data;
}
function prettify(data) {
data.slides.forEach(prettifySlide);
return data;
}
function matter(code, options) {
let type;
let raw;
let content = code.replace(/^---.*\r?\n([\s\S]*?)---/, (_, f) => {
type = "frontmatter";
raw = f;
return "";
});
if (type !== "frontmatter") {
content = content.replace(/^\s*```ya?ml([\s\S]*?)```/, (_, f) => {
type = "yaml";
raw = f;
return "";
});
}
const doc = raw && !options.noParseYAML ? YAML.parseDocument(raw) : void 0;
return {
type,
raw,
doc,
data: doc?.toJSON(),
content
};
}
function detectFeatures(code) {
return {
katex: !!code.match(/\$.*?\$/) || !!code.match(/\$\$/),
monaco: code.match(/\{monaco.*\}/) ? scanMonacoReferencedMods(code) : false,
tweet: !!code.match(/<Tweet\b/),
mermaid: !!code.match(/^```mermaid/m)
};
}
function parseSlide(raw, options = {}) {
const matterResult = matter(raw, options);
let note;
const frontmatter = matterResult.data || {};
let content = matterResult.content.trim();
const revision = hash(raw.trim());
const comments = Array.from(content.matchAll(/<!--([\s\S]*?)-->/g));
if (comments.length) {
const last = comments[comments.length - 1];
if (last.index !== void 0 && last.index + last[0].length >= content.length) {
note = last[1].trim();
content = content.slice(0, last.index).trim();
}
}
let title;
let level;
if (frontmatter.title || frontmatter.name) {
title = frontmatter.title || frontmatter.name;
} else {
const match = content.match(/^(#+) (.*)$/m);
title = match?.[2]?.trim();
level = match?.[1]?.length;
}
if (frontmatter.level)
level = frontmatter.level || 1;
return {
raw,
title,
level,
revision,
content,
frontmatter,
frontmatterStyle: matterResult.type,
frontmatterDoc: matterResult.doc,
frontmatterRaw: matterResult.raw,
note
};
}
async function parse(markdown, filepath, extensions, options = {}) {
const lines = markdown.split(options.preserveCR ? "\n" : /\r?\n/g);
const slides = [];
let start = 0;
let contentStart = 0;
async function slice(end) {
if (start === end)
return;
const raw = lines.slice(start, end).join("\n");
const slide = {
...parseSlide(raw, options),
filepath,
index: slides.length,
start,
contentStart,
end
};
if (extensions) {
for (const e of extensions) {
if (e.transformSlide) {
const newContent = await e.transformSlide(slide.content, slide.frontmatter);
if (newContent !== void 0)
slide.content = newContent;
}
}
}
slides.push(slide);
start = end + 1;
contentStart = end + 1;
}
if (extensions) {
for (const e of extensions) {
if (e.transformRawLines)
await e.transformRawLines(lines);
}
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trimEnd();
if (line.startsWith("---")) {
await slice(i);
const next = lines[i + 1];
if (line[3] !== "-" && next?.trim()) {
start = i;
for (i += 1; i < lines.length; i++) {
if (lines[i].trimEnd() === "---")
break;
}
contentStart = i + 1;
}
} else if (line.trimStart().startsWith("```")) {
const codeBlockLevel = line.match(/^\s*`+/)[0];
let j = i + 1;
for (; j < lines.length; j++) {
if (lines[j].startsWith(codeBlockLevel))
break;
}
if (j !== lines.length)
i = j;
}
}
if (start <= lines.length - 1)
await slice(lines.length);
return {
filepath,
raw: markdown,
slides
};
}
function parseSync(markdown, filepath, options = {}) {
const lines = markdown.split(options.preserveCR ? "\n" : /\r?\n/g);
const slides = [];
let start = 0;
let contentStart = 0;
function slice(end) {
if (start === end)
return;
const raw = lines.slice(start, end).join("\n");
const slide = {
...parseSlide(raw, options),
filepath,
index: slides.length,
start,
contentStart,
end
};
slides.push(slide);
start = end + 1;
contentStart = end + 1;
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trimEnd();
if (line.startsWith("---")) {
slice(i);
const next = lines[i + 1];
if (line[3] !== "-" && next?.trim()) {
start = i;
for (i += 1; i < lines.length; i++) {
if (lines[i].trimEnd() === "---")
break;
}
contentStart = i + 1;
}
} else if (line.trimStart().startsWith("```")) {
const codeBlockLevel = line.match(/^\s*`+/)[0];
let j = i + 1;
for (; j < lines.length; j++) {
if (lines[j].startsWith(codeBlockLevel))
break;
}
if (j !== lines.length)
i = j;
}
}
if (start <= lines.length - 1)
slice(lines.length);
return {
filepath,
raw: markdown,
slides
};
}
function scanMonacoReferencedMods(md) {
const types = /* @__PURE__ */ new Set();
const deps = /* @__PURE__ */ new Set();
md.replace(
/^```(\w+)\s*\{monaco([^}]*)\}\s*(\S[\s\S]*?)^```/gm,
(full, lang = "ts", kind, code) => {
lang = lang.trim();
const isDep = kind === "-run";
if (["js", "javascript", "ts", "typescript"].includes(lang)) {
for (const [, , specifier] of code.matchAll(/\s+from\s+(["'])([/.\w@-]+)\1/g)) {
if (specifier) {
if (!"./".includes(specifier))
types.add(specifier);
if (isDep)
deps.add(specifier);
}
}
}
return "";
}
);
return {
types: Array.from(types),
deps: Array.from(deps)
};
}
function hash(str) {
let hash2 = 0;
for (let i = 0; i < str.length; i++) {
hash2 = (hash2 << 5) - hash2 + str.charCodeAt(i);
hash2 |= 0;
}
return hash2.toString(36).slice(0, 12);
}
export {
getDefaultConfig,
resolveConfig,
verifyConfig,
resolveFonts,
stringify,
stringifySlide,
prettifySlide,
prettify,
detectFeatures,
parseSlide,
parse,
parseSync
};