feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+37
View File
@@ -0,0 +1,37 @@
// src/utils.ts
import { isNumber, range, uniq } from "@antfu/utils";
function parseRangeString(total, rangeStr) {
if (!rangeStr || rangeStr === "all" || rangeStr === "*")
return range(1, total + 1);
if (rangeStr === "none")
return [];
const indexes = [];
for (const part of rangeStr.split(/[,;]/g)) {
if (!part.includes("-")) {
indexes.push(+part);
} else {
const [start, end] = part.split("-", 2);
indexes.push(
...range(+start, !end ? total + 1 : +end + 1)
);
}
}
return uniq(indexes).filter((i) => i <= total).sort((a, b) => a - b);
}
function parseAspectRatio(str) {
if (isNumber(str))
return str;
if (!Number.isNaN(+str))
return +str;
const [wStr = "", hStr = ""] = str.split(/[:/x|]/);
const w = Number.parseFloat(wStr.trim());
const h = Number.parseFloat(hStr.trim());
if (Number.isNaN(w) || Number.isNaN(h) || h === 0)
throw new Error(`Invalid aspect ratio "${str}"`);
return w / h;
}
export {
parseRangeString,
parseAspectRatio
};
+463
View File
@@ -0,0 +1,463 @@
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
};
+22
View File
@@ -0,0 +1,22 @@
import { SlidevConfig, SlidevThemeMeta, FontOptions, ResolvedFontOptions, SlidevMarkdown, SourceSlideInfo, SlidevDetectedFeatures, SlidevPreparserExtension } from '@slidev/types';
export { parseAspectRatio, parseRangeString } from './utils.mjs';
declare function getDefaultConfig(): SlidevConfig;
declare function resolveConfig(headmatter: any, themeMeta?: SlidevThemeMeta, filepath?: string, verify?: boolean): SlidevConfig;
declare function verifyConfig(config: SlidevConfig, themeMeta?: SlidevThemeMeta, warn?: (v: string) => void): void;
declare function resolveFonts(fonts?: FontOptions): ResolvedFontOptions;
interface SlidevParserOptions {
noParseYAML?: boolean;
preserveCR?: boolean;
}
declare function stringify(data: SlidevMarkdown): string;
declare function stringifySlide(data: SourceSlideInfo, idx?: number): string;
declare function prettifySlide(data: SourceSlideInfo): SourceSlideInfo;
declare function prettify(data: SlidevMarkdown): SlidevMarkdown;
declare function detectFeatures(code: string): SlidevDetectedFeatures;
declare function parseSlide(raw: string, options?: SlidevParserOptions): Omit<SourceSlideInfo, 'filepath' | 'index' | 'start' | 'contentStart' | 'end'>;
declare function parse(markdown: string, filepath: string, extensions?: SlidevPreparserExtension[], options?: SlidevParserOptions): Promise<SlidevMarkdown>;
declare function parseSync(markdown: string, filepath: string, options?: SlidevParserOptions): SlidevMarkdown;
export { type SlidevParserOptions, detectFeatures, getDefaultConfig, parse, parseSlide, parseSync, prettify, prettifySlide, resolveConfig, resolveFonts, stringify, stringifySlide, verifyConfig };
+34
View File
@@ -0,0 +1,34 @@
import {
detectFeatures,
getDefaultConfig,
parse,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
stringify,
stringifySlide,
verifyConfig
} from "./chunk-JZNWQ6OH.mjs";
import {
parseAspectRatio,
parseRangeString
} from "./chunk-47YLTX76.mjs";
export {
detectFeatures,
getDefaultConfig,
parse,
parseAspectRatio,
parseRangeString,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
stringify,
stringifySlide,
verifyConfig
};
+14
View File
@@ -0,0 +1,14 @@
import { PreparserExtensionLoader, SlidevData, SlidevMarkdown } from '@slidev/types';
export { SlidevParserOptions, detectFeatures, getDefaultConfig, parse, parseSlide, parseSync, prettify, prettifySlide, resolveConfig, resolveFonts, stringify, stringifySlide, verifyConfig } from './core.mjs';
export { parseAspectRatio, parseRangeString } from './utils.mjs';
declare function injectPreparserExtensionLoader(fn: PreparserExtensionLoader): void;
/**
* Slidev data without config and themeMeta,
* because config and themeMeta depends on the theme to be loaded.
*/
type LoadedSlidevData = Omit<SlidevData, 'config' | 'themeMeta'>;
declare function load(userRoot: string, filepath: string, loadedSource?: Record<string, string>, mode?: string): Promise<LoadedSlidevData>;
declare function save(markdown: SlidevMarkdown): Promise<string>;
export { type LoadedSlidevData, injectPreparserExtensionLoader, load, save };
+146
View File
@@ -0,0 +1,146 @@
import {
detectFeatures,
getDefaultConfig,
parse,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
stringify,
stringifySlide,
verifyConfig
} from "./chunk-JZNWQ6OH.mjs";
import {
parseAspectRatio,
parseRangeString
} from "./chunk-47YLTX76.mjs";
// src/fs.ts
import fs from "node:fs";
import { dirname, resolve } from "node:path";
import { slash } from "@antfu/utils";
import YAML from "yaml";
var preparserExtensionLoader = null;
function injectPreparserExtensionLoader(fn) {
preparserExtensionLoader = fn;
}
async function load(userRoot, filepath, loadedSource = {}, mode) {
const markdown = loadedSource[filepath] ?? fs.readFileSync(filepath, "utf-8");
let extensions;
if (preparserExtensionLoader) {
const lines = markdown.split(/\r?\n/g);
let hm = "";
if (lines[0].match(/^---([^-].*)?$/) && !lines[1]?.match(/^\s*$/)) {
let hEnd = 1;
while (hEnd < lines.length && !lines[hEnd].trimEnd().match(/^---$/))
hEnd++;
hm = lines.slice(1, hEnd).join("\n");
}
const o = YAML.parse(hm) ?? {};
extensions = await preparserExtensionLoader(o, filepath, mode);
}
const markdownFiles = {};
const watchFiles = {};
const slides = [];
async function loadMarkdown(path, range, frontmatterOverride, importers) {
let md = markdownFiles[path];
if (!md) {
const raw = loadedSource[path] ?? fs.readFileSync(path, "utf-8");
md = await parse(raw, path, extensions);
markdownFiles[path] = md;
watchFiles[path] = /* @__PURE__ */ new Set();
}
const directImporter = importers?.at(-1);
for (const index of parseRangeString(md.slides.length, range)) {
const subSlide = md.slides[index - 1];
try {
await loadSlide(md, subSlide, frontmatterOverride, importers);
} catch (e) {
md.errors ??= [];
md.errors.push({
row: subSlide.start,
message: `Error when loading slide: ${e}`
});
continue;
}
if (directImporter)
(directImporter.imports ??= []).push(subSlide);
}
return md;
}
async function loadSlide(md, slide, frontmatterOverride, importChain) {
if (slide.frontmatter.disabled || slide.frontmatter.hide)
return;
if (slide.frontmatter.src) {
const [rawPath, rangeRaw] = slide.frontmatter.src.split("#");
const path = slash(
rawPath.startsWith("/") ? resolve(userRoot, rawPath.substring(1)) : resolve(dirname(slide.filepath), rawPath)
);
frontmatterOverride = {
...slide.frontmatter,
...frontmatterOverride
};
delete frontmatterOverride.src;
if (!fs.existsSync(path)) {
md.errors ??= [];
md.errors.push({
row: slide.start,
message: `Imported markdown file not found: ${path}`
});
} else {
await loadMarkdown(path, rangeRaw, frontmatterOverride, importChain ? [...importChain, slide] : [slide]);
}
} else {
slides.push({
frontmatter: { ...slide.frontmatter, ...frontmatterOverride },
content: slide.content,
revision: slide.revision,
frontmatterRaw: slide.frontmatterRaw,
note: slide.note,
title: slide.title,
level: slide.level,
index: slides.length,
importChain,
source: slide
});
}
}
const entry = await loadMarkdown(slash(filepath));
const headmatter = { ...entry.slides[0]?.frontmatter };
if (slides[0]?.title)
headmatter.title ??= slides[0].title;
return {
slides,
entry,
headmatter,
features: detectFeatures(slides.map((s) => s.source.raw).join("")),
markdownFiles,
watchFiles
};
}
async function save(markdown) {
const fileContent = stringify(markdown);
fs.writeFileSync(markdown.filepath, fileContent, "utf-8");
return fileContent;
}
export {
detectFeatures,
getDefaultConfig,
injectPreparserExtensionLoader,
load,
parse,
parseAspectRatio,
parseRangeString,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
save,
stringify,
stringifySlide,
verifyConfig
};
+3
View File
@@ -0,0 +1,3 @@
export { SlidevParserOptions, detectFeatures, getDefaultConfig, parse, parseSlide, parseSync, prettify, prettifySlide, resolveConfig, resolveFonts, stringify, stringifySlide, verifyConfig } from './core.mjs';
export { parseAspectRatio, parseRangeString } from './utils.mjs';
import '@slidev/types';
+34
View File
@@ -0,0 +1,34 @@
import {
detectFeatures,
getDefaultConfig,
parse,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
stringify,
stringifySlide,
verifyConfig
} from "./chunk-JZNWQ6OH.mjs";
import {
parseAspectRatio,
parseRangeString
} from "./chunk-47YLTX76.mjs";
export {
detectFeatures,
getDefaultConfig,
parse,
parseAspectRatio,
parseRangeString,
parseSlide,
parseSync,
prettify,
prettifySlide,
resolveConfig,
resolveFonts,
stringify,
stringifySlide,
verifyConfig
};
+10
View File
@@ -0,0 +1,10 @@
/**
* 1,3-5,8 => [1, 3, 4, 5, 8]
*/
declare function parseRangeString(total: number, rangeStr?: string): number[];
/**
* Accepts `16/9` `1:1` `3x4`
*/
declare function parseAspectRatio(str: string | number): number;
export { parseAspectRatio, parseRangeString };
+8
View File
@@ -0,0 +1,8 @@
import {
parseAspectRatio,
parseRangeString
} from "./chunk-47YLTX76.mjs";
export {
parseAspectRatio,
parseRangeString
};