464 lines
12 KiB
JavaScript
464 lines
12 KiB
JavaScript
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
|
|
};
|