build: add precompiled CLI distribution
This commit is contained in:
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
export interface EasySlidesConfig {
|
||||
title: string;
|
||||
description: string;
|
||||
slidesDir: string;
|
||||
outDir: string;
|
||||
exportDir: string;
|
||||
theme?: string;
|
||||
}
|
||||
export declare function loadProjectConfig(cwd?: string): Promise<EasySlidesConfig>;
|
||||
export declare function normalizeConfig(value: Partial<EasySlidesConfig> | undefined): EasySlidesConfig;
|
||||
@@ -0,0 +1,61 @@
|
||||
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
||||
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
||||
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
||||
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
||||
});
|
||||
}
|
||||
return path;
|
||||
};
|
||||
import { access, readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
const defaults = {
|
||||
title: 'easy-slides',
|
||||
description: 'Markdown presentations powered by easy-slides',
|
||||
slidesDir: 'slides',
|
||||
outDir: 'dist',
|
||||
exportDir: 'exports',
|
||||
};
|
||||
export async function loadProjectConfig(cwd = process.cwd()) {
|
||||
const candidates = [
|
||||
'easy-slides.config.mjs',
|
||||
'easy-slides.config.js',
|
||||
'easy-slides.config.json',
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const filename = path.join(cwd, candidate);
|
||||
if (!(await exists(filename)))
|
||||
continue;
|
||||
const loaded = candidate.endsWith('.json')
|
||||
? JSON.parse(await readFile(filename, 'utf8'))
|
||||
: (await import(__rewriteRelativeImportExtension(`${pathToFileURL(filename).href}?t=${Date.now()}`))).default;
|
||||
return normalizeConfig(loaded);
|
||||
}
|
||||
return { ...defaults };
|
||||
}
|
||||
export function normalizeConfig(value) {
|
||||
return {
|
||||
...defaults,
|
||||
...value,
|
||||
slidesDir: normalizeRelativePath(value?.slidesDir ?? defaults.slidesDir, 'slidesDir'),
|
||||
outDir: normalizeRelativePath(value?.outDir ?? defaults.outDir, 'outDir'),
|
||||
exportDir: normalizeRelativePath(value?.exportDir ?? defaults.exportDir, 'exportDir'),
|
||||
};
|
||||
}
|
||||
function normalizeRelativePath(value, field) {
|
||||
const normalized = value.trim().replace(/[\\/]+$/, '');
|
||||
const portable = normalized.replaceAll('\\', '/');
|
||||
if (!normalized || path.isAbsolute(normalized) || portable === '..' || portable.startsWith('../'))
|
||||
throw new Error(`${field} 必须是项目内的相对路径`);
|
||||
return normalized;
|
||||
}
|
||||
async function exists(filename) {
|
||||
try {
|
||||
await access(filename);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=config.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../scripts/lib/config.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAWxC,MAAM,QAAQ,GAAqB;IACjC,KAAK,EAAE,aAAa;IACpB,WAAW,EAAE,+CAA+C;IAC5D,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,MAAM;IACd,SAAS,EAAE,SAAS;CACrB,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACzD,MAAM,UAAU,GAAG;QACjB,wBAAwB;QACxB,uBAAuB;QACvB,yBAAyB;KAC1B,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;QAC1C,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC3B,SAAQ;QAEV,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;YACxC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC,MAAM,MAAM,kCAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,EAAC,CAAC,CAAC,OAAO,CAAA;QAE7E,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;IAChC,CAAC;IAED,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA4C;IAC1E,OAAO;QACL,GAAG,QAAQ;QACX,GAAG,KAAK;QACR,SAAS,EAAE,qBAAqB,CAAC,KAAK,EAAE,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;QACrF,MAAM,EAAE,qBAAqB,CAAC,KAAK,EAAE,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;QACzE,SAAS,EAAE,qBAAqB,CAAC,KAAK,EAAE,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;KACtF,CAAA;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,KAAa;IACzD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjD,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC;QAC/F,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,CAAC,CAAA;IACzC,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,QAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAA;QACtB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
export interface DeckMetadata {
|
||||
slug: string;
|
||||
entry: string;
|
||||
directory: string;
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
date: string;
|
||||
tags: string[];
|
||||
cover?: string;
|
||||
draft: boolean;
|
||||
}
|
||||
export interface ValidationIssue {
|
||||
level: 'error' | 'warning';
|
||||
file: string;
|
||||
message: string;
|
||||
}
|
||||
export declare function discoverDecks(cwd?: string, slidesDir?: string): Promise<DeckMetadata[]>;
|
||||
export declare function readDeck(entry: string): Promise<DeckMetadata>;
|
||||
export declare function resolveDeck(input: string | undefined, cwd?: string, slidesDir?: string): Promise<DeckMetadata>;
|
||||
export declare function validateDeck(deck: DeckMetadata): Promise<ValidationIssue[]>;
|
||||
export declare function extractMarkdownImages(markdown: string): {
|
||||
source: string;
|
||||
attributes: Record<string, string>;
|
||||
}[];
|
||||
export declare function parseAttributes(input: string): Record<string, string>;
|
||||
@@ -0,0 +1,124 @@
|
||||
import { access, readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import fg from 'fast-glob';
|
||||
import matter from 'gray-matter';
|
||||
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const allowedFits = new Set(['contain', 'cover', 'fill', 'scale-down', 'none']);
|
||||
export async function discoverDecks(cwd = process.cwd(), slidesDir = 'slides') {
|
||||
const entries = await fg(`${slidesDir.replaceAll('\\', '/')}/*/slides.md`, { cwd, absolute: true, onlyFiles: true });
|
||||
const decks = await Promise.all(entries.map(entry => readDeck(entry)));
|
||||
return decks.sort((a, b) => compareDecks(a, b));
|
||||
}
|
||||
export async function readDeck(entry) {
|
||||
const source = await readFile(entry, 'utf8');
|
||||
const parsed = matter(source);
|
||||
const directory = path.dirname(entry);
|
||||
const slug = path.basename(directory);
|
||||
const tags = Array.isArray(parsed.data.tags)
|
||||
? parsed.data.tags.map((tag) => String(tag).trim()).filter(Boolean)
|
||||
: [];
|
||||
return {
|
||||
slug,
|
||||
entry,
|
||||
directory,
|
||||
title: String(parsed.data.title ?? '').trim(),
|
||||
description: String(parsed.data.description ?? '').trim(),
|
||||
author: String(parsed.data.author ?? '').trim(),
|
||||
date: normalizeDate(parsed.data.date),
|
||||
tags,
|
||||
cover: parsed.data.cover ? String(parsed.data.cover).trim() : undefined,
|
||||
draft: parsed.data.draft === true,
|
||||
};
|
||||
}
|
||||
export async function resolveDeck(input, cwd = process.cwd(), slidesDir = 'slides') {
|
||||
const decks = await discoverDecks(cwd, slidesDir);
|
||||
if (!input)
|
||||
return decks.find(deck => !deck.draft) ?? decks[0];
|
||||
const direct = path.resolve(cwd, input);
|
||||
const bySlug = decks.find(deck => deck.slug === input);
|
||||
const byPath = decks.find(deck => path.resolve(deck.entry) === direct);
|
||||
const deck = bySlug ?? byPath;
|
||||
if (!deck)
|
||||
throw new Error(`找不到演示“${input}”。可用 slug:${decks.map(item => item.slug).join(', ') || '无'}`);
|
||||
return deck;
|
||||
}
|
||||
export async function validateDeck(deck) {
|
||||
const issues = [];
|
||||
const source = await readFile(deck.entry, 'utf8');
|
||||
const parsed = matter(source);
|
||||
if (!slugPattern.test(deck.slug))
|
||||
issues.push(error(deck.entry, '目录名必须是小写 kebab-case slug'));
|
||||
if (!deck.title)
|
||||
issues.push(error(deck.entry, 'frontmatter 缺少必填 title'));
|
||||
if (deck.date && Number.isNaN(Date.parse(deck.date)))
|
||||
issues.push(error(deck.entry, `date 不是有效日期:${deck.date}`));
|
||||
if (parsed.data.tags !== undefined && !Array.isArray(parsed.data.tags))
|
||||
issues.push(error(deck.entry, 'tags 必须是数组'));
|
||||
if (parsed.data.theme && parsed.data.theme !== 'easy-jyy')
|
||||
issues.push(warning(deck.entry, `当前主题为 ${parsed.data.theme},easy-jyy 主题能力可能不会生效`));
|
||||
if (deck.cover && !isRemote(deck.cover)) {
|
||||
const coverPath = path.resolve(deck.directory, deck.cover);
|
||||
if (!(await exists(coverPath)))
|
||||
issues.push(error(deck.entry, `封面不存在:${deck.cover}`));
|
||||
}
|
||||
for (const image of extractMarkdownImages(parsed.content)) {
|
||||
if (!isRemote(image.source) && !image.source.startsWith('/') && !(await exists(path.resolve(deck.directory, image.source))))
|
||||
issues.push(error(deck.entry, `图片不存在:${image.source}`));
|
||||
if (image.attributes.fit && !allowedFits.has(image.attributes.fit))
|
||||
issues.push(error(deck.entry, `图片 fit 不受支持:${image.attributes.fit}`));
|
||||
if (image.attributes['max-height'] && !isSafeCssSize(image.attributes['max-height']))
|
||||
issues.push(error(deck.entry, `图片 max-height 不合法:${image.attributes['max-height']}`));
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
export function extractMarkdownImages(markdown) {
|
||||
const images = [];
|
||||
const pattern = /!\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)\s*(?:\{([^}]*)\})?/g;
|
||||
for (const match of markdown.matchAll(pattern)) {
|
||||
images.push({
|
||||
source: match[1],
|
||||
attributes: parseAttributes(match[2] ?? ''),
|
||||
});
|
||||
}
|
||||
return images;
|
||||
}
|
||||
export function parseAttributes(input) {
|
||||
const attributes = {};
|
||||
const pattern = /([\w-]+)=(?:"([^"]*)"|'([^']*)'|([^\s]+))/g;
|
||||
for (const match of input.matchAll(pattern))
|
||||
attributes[match[1]] = match[2] ?? match[3] ?? match[4] ?? '';
|
||||
return attributes;
|
||||
}
|
||||
function normalizeDate(value) {
|
||||
if (!value)
|
||||
return '';
|
||||
if (value instanceof Date)
|
||||
return value.toISOString().slice(0, 10);
|
||||
return String(value).trim();
|
||||
}
|
||||
function compareDecks(a, b) {
|
||||
const byDate = (Date.parse(b.date || '1970-01-01') || 0) - (Date.parse(a.date || '1970-01-01') || 0);
|
||||
return byDate || a.title.localeCompare(b.title, 'zh-CN');
|
||||
}
|
||||
function isRemote(value) {
|
||||
return /^(?:https?:)?\/\//.test(value) || value.startsWith('data:');
|
||||
}
|
||||
function isSafeCssSize(value) {
|
||||
return /^(?:\d+(?:\.\d+)?)(?:px|rem|em|vh|vw|%|vmin|vmax)$/.test(value);
|
||||
}
|
||||
async function exists(file) {
|
||||
try {
|
||||
await access(file);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function error(file, message) {
|
||||
return { level: 'error', file, message };
|
||||
}
|
||||
function warning(file, message) {
|
||||
return { level: 'warning', file, message };
|
||||
}
|
||||
//# sourceMappingURL=content.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import type { DeckMetadata } from './content.ts';
|
||||
interface DevOverflowAuditOptions {
|
||||
cwd?: string;
|
||||
debounceMs?: number;
|
||||
deck: DeckMetadata;
|
||||
output?: (line: string) => void;
|
||||
}
|
||||
export interface DevOverflowAuditController {
|
||||
close: () => Promise<void>;
|
||||
ready: Promise<void>;
|
||||
}
|
||||
export declare function startDevOverflowAudit(options: DevOverflowAuditOptions): DevOverflowAuditController;
|
||||
export {};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { formatStaticOverflowResult, staticOverflowFingerprint } from "./overflow-report.js";
|
||||
import { StaticOverflowAuditor } from "./static-overflow.js";
|
||||
export function startDevOverflowAudit(options) {
|
||||
const auditor = new StaticOverflowAuditor();
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
const debounceMs = options.debounceMs ?? 250;
|
||||
const output = options.output ?? console.log;
|
||||
let activeAudit;
|
||||
let closed = false;
|
||||
let fingerprint = '';
|
||||
let queued = false;
|
||||
let timer;
|
||||
let watcher;
|
||||
function scheduleAudit(delay = debounceMs) {
|
||||
if (closed)
|
||||
return;
|
||||
if (timer)
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
timer = undefined;
|
||||
void runAudit();
|
||||
}, delay);
|
||||
}
|
||||
async function runAudit() {
|
||||
if (closed)
|
||||
return;
|
||||
if (activeAudit) {
|
||||
queued = true;
|
||||
return activeAudit;
|
||||
}
|
||||
activeAudit = auditor.audit([options.deck])
|
||||
.then((result) => {
|
||||
if (closed)
|
||||
return;
|
||||
const nextFingerprint = staticOverflowFingerprint(result);
|
||||
if (nextFingerprint !== fingerprint) {
|
||||
fingerprint = nextFingerprint;
|
||||
formatStaticOverflowResult(result, cwd).forEach(line => output(line));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (closed)
|
||||
return;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const nextFingerprint = `error:${message}`;
|
||||
if (nextFingerprint !== fingerprint) {
|
||||
fingerprint = nextFingerprint;
|
||||
output(`WARN ${path.relative(cwd, options.deck.entry)}: 无法完成静态内容检查:${message}`);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
activeAudit = undefined;
|
||||
if (queued) {
|
||||
queued = false;
|
||||
scheduleAudit(0);
|
||||
}
|
||||
});
|
||||
return activeAudit;
|
||||
}
|
||||
function startWatcher() {
|
||||
const handleChange = () => scheduleAudit();
|
||||
try {
|
||||
watcher = watch(options.deck.directory, { recursive: true }, handleChange);
|
||||
}
|
||||
catch {
|
||||
watcher = watch(options.deck.entry, handleChange);
|
||||
}
|
||||
}
|
||||
const ready = (async () => {
|
||||
output('\n检查开发内容中的静态溢出风险...');
|
||||
startWatcher();
|
||||
await runAudit();
|
||||
})();
|
||||
return {
|
||||
ready,
|
||||
async close() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
watcher?.close();
|
||||
if (timer)
|
||||
clearTimeout(timer);
|
||||
await ready.catch(() => undefined);
|
||||
await activeAudit?.catch(() => undefined);
|
||||
},
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=dev-overflow-audit.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dev-overflow-audit.js","sourceRoot":"","sources":["../../scripts/lib/dev-overflow-audit.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
export interface OverflowAuditDeck {
|
||||
entry: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface SlideOverflowIssue {
|
||||
deck: OverflowAuditDeck;
|
||||
heading: string;
|
||||
horizontal: number;
|
||||
slide: number;
|
||||
vertical: number;
|
||||
}
|
||||
export interface OverflowAuditResult {
|
||||
errors: Array<{
|
||||
deck: OverflowAuditDeck;
|
||||
message: string;
|
||||
}>;
|
||||
issues: SlideOverflowIssue[];
|
||||
}
|
||||
interface OverflowAuditOptions {
|
||||
decks: OverflowAuditDeck[];
|
||||
root: string;
|
||||
siteBase: string;
|
||||
}
|
||||
export interface OverflowAuditTarget {
|
||||
deck: OverflowAuditDeck;
|
||||
routerMode?: 'hash' | 'history';
|
||||
url: string;
|
||||
}
|
||||
export declare class OverflowAuditor {
|
||||
private browser?;
|
||||
private page?;
|
||||
audit(targets: OverflowAuditTarget[]): Promise<OverflowAuditResult>;
|
||||
close(): Promise<void>;
|
||||
private ensurePage;
|
||||
}
|
||||
export declare function auditBuiltDecks(options: OverflowAuditOptions): Promise<OverflowAuditResult>;
|
||||
export {};
|
||||
@@ -0,0 +1,228 @@
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { stat } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { chromium } from 'playwright-chromium';
|
||||
const overflowTolerance = 2;
|
||||
export class OverflowAuditor {
|
||||
browser;
|
||||
page;
|
||||
async audit(targets) {
|
||||
const page = await this.ensurePage();
|
||||
const result = { errors: [], issues: [] };
|
||||
for (const target of targets) {
|
||||
try {
|
||||
result.issues.push(...await auditDeck(page, target));
|
||||
}
|
||||
catch (error) {
|
||||
result.errors.push({
|
||||
deck: target.deck,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async close() {
|
||||
await this.browser?.close();
|
||||
this.browser = undefined;
|
||||
this.page = undefined;
|
||||
}
|
||||
async ensurePage() {
|
||||
if (this.page && !this.page.isClosed())
|
||||
return this.page;
|
||||
await this.browser?.close().catch(() => undefined);
|
||||
this.browser = await chromium.launch({ headless: true });
|
||||
const context = await this.browser.newContext({ viewport: { height: 900, width: 1200 } });
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'wakeLock', {
|
||||
configurable: true,
|
||||
value: {
|
||||
async request(type) {
|
||||
const sentinel = new EventTarget();
|
||||
sentinel.released = false;
|
||||
sentinel.type = type;
|
||||
sentinel.release = async () => {
|
||||
if (sentinel.released)
|
||||
return;
|
||||
sentinel.released = true;
|
||||
sentinel.dispatchEvent(new Event('release'));
|
||||
};
|
||||
return sentinel;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
this.page = await context.newPage();
|
||||
return this.page;
|
||||
}
|
||||
}
|
||||
export async function auditBuiltDecks(options) {
|
||||
const server = await startStaticServer(options.root, options.siteBase);
|
||||
const address = server.address();
|
||||
const origin = `http://127.0.0.1:${address.port}`;
|
||||
const auditor = new OverflowAuditor();
|
||||
try {
|
||||
return await auditor.audit(options.decks.map(deck => ({
|
||||
deck,
|
||||
url: new URL(`${options.siteBase}${deck.slug}/`.replace(/\/{2,}/g, '/'), origin).href,
|
||||
})));
|
||||
}
|
||||
finally {
|
||||
await auditor.close();
|
||||
await new Promise(resolve => server.close(() => resolve()));
|
||||
}
|
||||
}
|
||||
async function auditDeck(page, target) {
|
||||
const issues = [];
|
||||
const overviewUrl = auditRouteUrl(target, 'overview');
|
||||
await page.goto(overviewUrl.href, { timeout: 30_000, waitUntil: 'networkidle' });
|
||||
await page.locator('.slidev-layout').first().waitFor({ state: 'attached', timeout: 15_000 });
|
||||
await waitForPageAssets(page);
|
||||
const slideNumbers = await page.locator('[class*="slidev-page-"]').evaluateAll((pages) => {
|
||||
const numbers = pages.flatMap((page) => {
|
||||
const pageClass = Array.from(page.classList).find(className => /^slidev-page-\d+$/.test(className));
|
||||
const number = Number.parseInt(pageClass?.replace('slidev-page-', '') ?? '', 10);
|
||||
return Number.isFinite(number) ? [number] : [];
|
||||
});
|
||||
return Array.from(new Set(numbers)).sort((a, b) => a - b);
|
||||
});
|
||||
if (slideNumbers.length && target.routerMode !== 'history') {
|
||||
await page.evaluate((number) => {
|
||||
window.location.hash = `#/${number}`;
|
||||
}, slideNumbers[0]);
|
||||
await page.reload({ timeout: 30_000, waitUntil: 'networkidle' });
|
||||
await page.evaluate(async () => {
|
||||
await document.fonts?.ready;
|
||||
});
|
||||
}
|
||||
for (const slide of slideNumbers) {
|
||||
if (target.routerMode === 'history')
|
||||
await page.goto(auditRouteUrl(target, String(slide)).href, { timeout: 30_000, waitUntil: 'networkidle' });
|
||||
else
|
||||
await page.evaluate((number) => {
|
||||
window.location.hash = `#/${number}`;
|
||||
}, slide);
|
||||
await page.waitForFunction((number) => {
|
||||
const layouts = Array.from(document.querySelectorAll(`.slidev-page-${number} .slidev-layout`));
|
||||
return layouts.some(layout => layout.getBoundingClientRect().width > 0);
|
||||
}, slide, { timeout: 10_000 });
|
||||
await waitForSlideAssets(page, slide);
|
||||
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
|
||||
const measurement = await page.evaluate((number) => {
|
||||
const layouts = Array.from(document.querySelectorAll(`.slidev-page-${number} .slidev-layout`));
|
||||
const layout = layouts.find(item => item.getBoundingClientRect().width > 0) ?? layouts[0];
|
||||
return {
|
||||
heading: layout?.querySelector('h1, h2, h3')?.textContent?.trim() ?? '',
|
||||
horizontal: layout ? Math.max(0, Math.ceil(layout.scrollWidth - layout.clientWidth)) : 0,
|
||||
slide: number,
|
||||
vertical: layout ? Math.max(0, Math.ceil(layout.scrollHeight - layout.clientHeight)) : 0,
|
||||
};
|
||||
}, slide);
|
||||
if (measurement.vertical <= overflowTolerance && measurement.horizontal <= overflowTolerance)
|
||||
continue;
|
||||
issues.push({ deck: target.deck, ...measurement });
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
function auditRouteUrl(target, route) {
|
||||
const url = target.routerMode === 'history'
|
||||
? new URL(`${route.replace(/^\/+/, '')}${route === 'overview' ? '/' : ''}`, ensureTrailingSlash(target.url))
|
||||
: new URL(target.url);
|
||||
url.searchParams.set('easy-slides-audit', '1');
|
||||
if (target.routerMode !== 'history')
|
||||
url.hash = `#/${route}`;
|
||||
return url;
|
||||
}
|
||||
function ensureTrailingSlash(value) {
|
||||
return value.endsWith('/') ? value : `${value}/`;
|
||||
}
|
||||
async function waitForPageAssets(page) {
|
||||
await page.evaluate(async () => {
|
||||
await document.fonts?.ready;
|
||||
const images = Array.from(document.images);
|
||||
await Promise.race([
|
||||
Promise.all(images.map(image => image.complete
|
||||
? Promise.resolve()
|
||||
: new Promise((resolve) => {
|
||||
image.addEventListener('load', () => resolve(), { once: true });
|
||||
image.addEventListener('error', () => resolve(), { once: true });
|
||||
}))),
|
||||
new Promise(resolve => setTimeout(resolve, 5_000)),
|
||||
]);
|
||||
});
|
||||
}
|
||||
async function waitForSlideAssets(page, slide) {
|
||||
await page.evaluate(async (number) => {
|
||||
const layout = Array.from(document.querySelectorAll(`.slidev-page-${number} .slidev-layout`))
|
||||
.find(item => item.getBoundingClientRect().width > 0);
|
||||
const images = Array.from(layout?.querySelectorAll('img') ?? []);
|
||||
await Promise.race([
|
||||
Promise.all(images.map(image => image.complete
|
||||
? Promise.resolve()
|
||||
: new Promise((resolve) => {
|
||||
image.addEventListener('load', () => resolve(), { once: true });
|
||||
image.addEventListener('error', () => resolve(), { once: true });
|
||||
}))),
|
||||
new Promise(resolve => setTimeout(resolve, 5_000)),
|
||||
]);
|
||||
}, slide);
|
||||
}
|
||||
async function startStaticServer(root, siteBase) {
|
||||
const server = createServer(async (request, response) => {
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
response.writeHead(405).end();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
|
||||
const file = await resolveStaticFile(root, requestUrl.pathname, siteBase);
|
||||
response.statusCode = 200;
|
||||
response.setHeader('Content-Type', contentType(file));
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
createReadStream(file).pipe(response);
|
||||
}
|
||||
catch {
|
||||
response.writeHead(404).end('Not found');
|
||||
}
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
return server;
|
||||
}
|
||||
async function resolveStaticFile(root, requestPath, siteBase) {
|
||||
let pathname = decodeURIComponent(requestPath);
|
||||
const basePrefix = siteBase === '/' ? '' : siteBase.replace(/\/$/, '');
|
||||
if (basePrefix && pathname.startsWith(`${basePrefix}/`))
|
||||
pathname = pathname.slice(basePrefix.length);
|
||||
const candidate = path.resolve(root, pathname.replace(/^\/+/, ''));
|
||||
const relative = path.relative(root, candidate);
|
||||
if (relative.startsWith('..') || path.isAbsolute(relative))
|
||||
throw new Error('Path outside static root');
|
||||
const details = await stat(candidate);
|
||||
return details.isDirectory() ? path.join(candidate, 'index.html') : candidate;
|
||||
}
|
||||
function contentType(file) {
|
||||
const extension = path.extname(file).toLowerCase();
|
||||
return {
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.gif': 'image/gif',
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ttf': 'font/ttf',
|
||||
'.webp': 'image/webp',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
}[extension] ?? 'application/octet-stream';
|
||||
}
|
||||
//# sourceMappingURL=overflow-audit.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import type { OverflowAuditResult } from './overflow-audit.ts';
|
||||
import type { StaticOverflowResult } from './static-overflow.ts';
|
||||
export declare function formatOverflowAuditResult(result: OverflowAuditResult, cwd?: string): string[];
|
||||
export declare function overflowAuditFingerprint(result: OverflowAuditResult): string;
|
||||
export declare function formatStaticOverflowResult(result: StaticOverflowResult, cwd?: string): string[];
|
||||
export declare function staticOverflowFingerprint(result: StaticOverflowResult): string;
|
||||
@@ -0,0 +1,58 @@
|
||||
import path from 'node:path';
|
||||
const overflowTolerance = 2;
|
||||
export function formatOverflowAuditResult(result, cwd = process.cwd()) {
|
||||
const lines = [];
|
||||
for (const issue of result.issues) {
|
||||
const dimensions = [
|
||||
issue.vertical > overflowTolerance ? `纵向 ${issue.vertical}px` : '',
|
||||
issue.horizontal > overflowTolerance ? `横向 ${issue.horizontal}px` : '',
|
||||
].filter(Boolean).join('、');
|
||||
const heading = issue.heading ? `(${issue.heading})` : '';
|
||||
lines.push(`WARN ${path.relative(cwd, issue.deck.entry)}: 第 ${issue.slide} 页${heading}内容溢出:${dimensions};放映时可上下滚动,请优先拆页或精简内容`);
|
||||
}
|
||||
for (const error of result.errors)
|
||||
lines.push(`WARN ${path.relative(cwd, error.deck.entry)}: 无法完成内容溢出检查:${error.message}`);
|
||||
if (!lines.length)
|
||||
lines.push('内容边界检查通过:未发现溢出');
|
||||
return lines;
|
||||
}
|
||||
export function overflowAuditFingerprint(result) {
|
||||
return JSON.stringify({
|
||||
errors: result.errors.map(error => [error.deck.entry, error.message]),
|
||||
issues: result.issues.map(issue => [
|
||||
issue.deck.entry,
|
||||
issue.slide,
|
||||
issue.heading,
|
||||
issue.horizontal,
|
||||
issue.vertical,
|
||||
]),
|
||||
});
|
||||
}
|
||||
export function formatStaticOverflowResult(result, cwd = process.cwd()) {
|
||||
const lines = [];
|
||||
for (const issue of result.issues) {
|
||||
const directions = issue.directions
|
||||
.map(direction => direction === 'vertical' ? '纵向' : '横向')
|
||||
.join('、');
|
||||
const heading = issue.heading ? `(${issue.heading})` : '';
|
||||
lines.push(`WARN ${path.relative(cwd, issue.deck.entry)}: 第 ${issue.slide} 页${heading}存在静态${directions}溢出风险:${issue.reason};请优先拆页或精简内容(最终以 build 渲染检查为准)`);
|
||||
}
|
||||
for (const error of result.errors)
|
||||
lines.push(`WARN ${path.relative(cwd, error.deck.entry)}: 无法完成静态内容检查:${error.message}`);
|
||||
if (!lines.length)
|
||||
lines.push('静态内容检查通过:未发现明显溢出风险(最终以 build 渲染检查为准)');
|
||||
return lines;
|
||||
}
|
||||
export function staticOverflowFingerprint(result) {
|
||||
return JSON.stringify({
|
||||
errors: result.errors.map(error => [error.deck.entry, error.message]),
|
||||
issues: result.issues.map(issue => [
|
||||
issue.deck.entry,
|
||||
issue.slide,
|
||||
issue.heading,
|
||||
issue.directions,
|
||||
issue.reason,
|
||||
]),
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=overflow-report.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"overflow-report.js","sourceRoot":"","sources":["../../scripts/lib/overflow-report.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
interface RunSlidevOptions {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}
|
||||
export declare function startSlidev(args: string[], options?: RunSlidevOptions): ChildProcess;
|
||||
export declare function waitForSlidev(child: ChildProcess): Promise<void>;
|
||||
export declare function runSlidev(args: string[], options?: RunSlidevOptions): Promise<void>;
|
||||
export {};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
import { hashPresenterToken } from "./token.js";
|
||||
const require = createRequire(import.meta.url);
|
||||
const slidevCli = require.resolve('@slidev/cli/bin/slidev.mjs');
|
||||
export function startSlidev(args, options = {}) {
|
||||
const token = options.env?.PRESENTER_TOKEN ?? process.env.PRESENTER_TOKEN;
|
||||
const presenterHash = token?.trim() ? hashPresenterToken(token) : '';
|
||||
return spawn(process.execPath, [slidevCli, ...args], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
...options.env,
|
||||
VITE_EASY_SLIDES_PRESENTER_HASH: presenterHash,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
export async function waitForSlidev(child) {
|
||||
await new Promise((resolve, reject) => {
|
||||
child.once('error', reject);
|
||||
child.once('exit', (code, signal) => {
|
||||
if (code === 0)
|
||||
resolve();
|
||||
else
|
||||
reject(new Error(`Slidev 退出:${signal ?? `code ${code}`}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
export async function runSlidev(args, options = {}) {
|
||||
await waitForSlidev(startSlidev(args, options));
|
||||
}
|
||||
//# sourceMappingURL=run-slidev.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"run-slidev.js","sourceRoot":"","sources":["../../scripts/lib/run-slidev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAA;AAM/D,MAAM,UAAU,WAAW,CAAC,IAAc,EAAE,UAA4B,EAAE;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACzE,MAAM,aAAa,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACpE,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE;QACnD,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,GAAG,EAAE;YACH,GAAG,OAAO,CAAC,GAAG;YACd,GAAG,OAAO,CAAC,GAAG;YACd,+BAA+B,EAAE,aAAa;SAC/C;QACD,KAAK,EAAE,SAAS;KACjB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAmB;IACrD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC3B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAClC,IAAI,IAAI,KAAK,CAAC;gBACZ,OAAO,EAAE,CAAA;;gBAET,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,MAAM,IAAI,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;QAC9D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAc,EAAE,UAA4B,EAAE;IAC5E,MAAM,aAAa,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;AACjD,CAAC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare function normalizeSiteBase(value?: string): string;
|
||||
export declare function joinBase(base: string, ...parts: string[]): string;
|
||||
@@ -0,0 +1,15 @@
|
||||
export function normalizeSiteBase(value = '/') {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || trimmed === '/')
|
||||
return '/';
|
||||
return `/${trimmed.replace(/^\/+|\/+$/g, '')}/`;
|
||||
}
|
||||
export function joinBase(base, ...parts) {
|
||||
const normalized = normalizeSiteBase(base);
|
||||
const suffix = parts
|
||||
.map(part => part.replace(/^\/+|\/+$/g, ''))
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
return suffix ? `${normalized}${suffix}/` : normalized;
|
||||
}
|
||||
//# sourceMappingURL=site-base.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"site-base.js","sourceRoot":"","sources":["../../scripts/lib/site-base.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,KAAK,GAAG,GAAG;IAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,GAAG;QAC7B,OAAO,GAAG,CAAA;IAEZ,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,GAAG,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,GAAG,KAAe;IACvD,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;SAC3C,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAA;IAEZ,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,UAAU,CAAA;AACxD,CAAC"}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import type { DeckMetadata } from './content.ts';
|
||||
export type StaticOverflowDirection = 'horizontal' | 'vertical';
|
||||
export interface StaticOverflowIssue {
|
||||
deck: DeckMetadata;
|
||||
directions: StaticOverflowDirection[];
|
||||
heading: string;
|
||||
reason: string;
|
||||
slide: number;
|
||||
}
|
||||
export interface StaticOverflowResult {
|
||||
errors: Array<{
|
||||
deck: DeckMetadata;
|
||||
message: string;
|
||||
}>;
|
||||
issues: StaticOverflowIssue[];
|
||||
}
|
||||
interface StaticOverflowAuditorOptions {
|
||||
onAnalyzeSlide?: (deck: DeckMetadata, slide: number) => void;
|
||||
}
|
||||
export declare class StaticOverflowAuditor {
|
||||
private cache;
|
||||
private onAnalyzeSlide?;
|
||||
constructor(options?: StaticOverflowAuditorOptions);
|
||||
audit(decks: DeckMetadata[]): Promise<StaticOverflowResult>;
|
||||
private auditDeck;
|
||||
}
|
||||
export declare function auditStaticDecks(decks: DeckMetadata[]): Promise<StaticOverflowResult>;
|
||||
export {};
|
||||
@@ -0,0 +1,313 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { parseSync } from '@slidev/parser';
|
||||
const canvasHeight = 768;
|
||||
const canvasWidth = 1024;
|
||||
const slidePaddingX = 52;
|
||||
const slidePaddingY = 42;
|
||||
const contentHeight = canvasHeight - slidePaddingY * 2;
|
||||
const contentWidth = canvasWidth - slidePaddingX * 2;
|
||||
const twoColumnGap = 46;
|
||||
const baseFontSize = 32;
|
||||
const baseLineHeight = baseFontSize * 1.42;
|
||||
const verticalRiskTolerance = 1.08;
|
||||
const codeHeightPresets = new Map([
|
||||
['easy-code-height-sm', 180],
|
||||
['easy-code-height-md', 300],
|
||||
['easy-code-height-lg', 420],
|
||||
]);
|
||||
export class StaticOverflowAuditor {
|
||||
cache = new Map();
|
||||
onAnalyzeSlide;
|
||||
constructor(options = {}) {
|
||||
this.onAnalyzeSlide = options.onAnalyzeSlide;
|
||||
}
|
||||
async audit(decks) {
|
||||
const result = { errors: [], issues: [] };
|
||||
for (const deck of decks) {
|
||||
try {
|
||||
result.issues.push(...await this.auditDeck(deck));
|
||||
}
|
||||
catch (error) {
|
||||
result.errors.push({
|
||||
deck,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async auditDeck(deck) {
|
||||
const source = await readFile(deck.entry, 'utf8');
|
||||
const parsed = parseSync(source, deck.entry);
|
||||
const previous = this.cache.get(deck.entry) ?? new Map();
|
||||
const next = new Map();
|
||||
const issues = [];
|
||||
parsed.slides.forEach((slide, index) => {
|
||||
const number = index + 1;
|
||||
const fingerprint = slide.revision ?? slide.raw;
|
||||
const cached = previous.get(number);
|
||||
if (cached?.fingerprint === fingerprint) {
|
||||
next.set(number, cached);
|
||||
issues.push(...cached.issues);
|
||||
return;
|
||||
}
|
||||
this.onAnalyzeSlide?.(deck, number);
|
||||
const slideIssues = analyzeSlide(deck, number, slide);
|
||||
next.set(number, { fingerprint, issues: slideIssues });
|
||||
issues.push(...slideIssues);
|
||||
});
|
||||
this.cache.set(deck.entry, next);
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
export async function auditStaticDecks(decks) {
|
||||
return await new StaticOverflowAuditor().audit(decks);
|
||||
}
|
||||
function analyzeSlide(deck, number, slide) {
|
||||
const content = stripComments(slide.content);
|
||||
const layout = String(slide.frontmatter.layout ?? (number === 1 ? 'cover' : 'default'));
|
||||
const classNames = normalizeClasses(slide.frontmatter.class);
|
||||
const heading = slide.title?.trim() || extractHeading(content);
|
||||
const estimates = estimateSlide(content, layout, classNames, String(slide.frontmatter.imagePlacement ?? 'auto'));
|
||||
const directions = [];
|
||||
if (estimates.some(estimate => estimate.height > contentHeight * verticalRiskTolerance))
|
||||
directions.push('vertical');
|
||||
if (estimates.some(estimate => estimate.horizontalRisk))
|
||||
directions.push('horizontal');
|
||||
if (!directions.length)
|
||||
return [];
|
||||
const reason = directions.length === 2
|
||||
? '估算内容高度超过画布容量,且存在无法可靠换行的宽内容'
|
||||
: directions[0] === 'vertical'
|
||||
? '估算内容高度超过 easy-jyy 画布容量'
|
||||
: '存在无法可靠换行的宽内容';
|
||||
return [{ deck, directions, heading, reason, slide: number }];
|
||||
}
|
||||
function estimateSlide(content, layout, classNames, imagePlacement) {
|
||||
if (layout === 'two-cols') {
|
||||
const slots = splitTwoColumnContent(content);
|
||||
const headingEstimate = estimateRegion(slots.before, contentWidth, layout, classNames);
|
||||
const columnWidth = (contentWidth - twoColumnGap) / 2;
|
||||
const left = estimateRegion(slots.left, columnWidth, layout, classNames);
|
||||
const right = estimateRegion(slots.right, columnWidth, layout, classNames);
|
||||
return [
|
||||
{
|
||||
height: headingEstimate.height + Math.max(left.height, right.height),
|
||||
horizontalRisk: headingEstimate.horizontalRisk || left.horizontalRisk || right.horizontalRisk,
|
||||
},
|
||||
];
|
||||
}
|
||||
const splitImageLayout = ['image-left', 'image-right'].includes(layout)
|
||||
|| (layout === 'image-auto' && ['left', 'right'].includes(imagePlacement));
|
||||
const width = splitImageLayout ? (contentWidth - 42) / 2 : contentWidth;
|
||||
return [estimateRegion(content, width, layout, classNames)];
|
||||
}
|
||||
function splitTwoColumnContent(content) {
|
||||
const leftMarker = /^::left::\s*$/m;
|
||||
const rightMarker = /^::right::\s*$/m;
|
||||
const leftMatch = leftMarker.exec(content);
|
||||
const rightMatch = rightMarker.exec(content);
|
||||
if (!leftMatch && !rightMatch)
|
||||
return { before: content, left: '', right: '' };
|
||||
const firstIndex = Math.min(leftMatch?.index ?? Number.POSITIVE_INFINITY, rightMatch?.index ?? Number.POSITIVE_INFINITY);
|
||||
const before = content.slice(0, firstIndex);
|
||||
const left = leftMatch
|
||||
? content.slice(leftMatch.index + leftMatch[0].length, rightMatch && rightMatch.index > leftMatch.index ? rightMatch.index : undefined)
|
||||
: '';
|
||||
const right = rightMatch
|
||||
? content.slice(rightMatch.index + rightMatch[0].length, leftMatch && leftMatch.index > rightMatch.index ? leftMatch.index : undefined)
|
||||
: '';
|
||||
return { before, left, right };
|
||||
}
|
||||
function estimateRegion(markdown, width, layout, classNames) {
|
||||
const lines = markdown.replace(/\r/g, '').split('\n');
|
||||
let height = 0;
|
||||
let horizontalRisk = false;
|
||||
for (let index = 0; index < lines.length;) {
|
||||
const raw = lines[index];
|
||||
const line = raw.trim();
|
||||
if (!line || isStructuralLine(line)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
const fence = /^(?:`{3,}|~{3,})([^\s{]*)/.exec(line);
|
||||
if (fence) {
|
||||
const marker = line.startsWith('`') ? '`' : '~';
|
||||
const markerLength = line.match(new RegExp(`^\\${marker}+`))?.[0].length ?? 3;
|
||||
const language = fence[1].toLowerCase();
|
||||
let end = index + 1;
|
||||
while (end < lines.length && !new RegExp(`^\\s*\\${marker}{${markerLength},}`).test(lines[end]))
|
||||
end++;
|
||||
if (language === 'mermaid')
|
||||
height += 180;
|
||||
else
|
||||
height += explicitCodeHeight(lines, index, classNames);
|
||||
index = Math.min(lines.length, end + 1);
|
||||
continue;
|
||||
}
|
||||
const heading = /^(#{1,4})\s+(.+)$/.exec(line);
|
||||
if (heading) {
|
||||
const level = heading[1].length;
|
||||
const fontSize = level === 1 ? (layout === 'cover' ? 68 : 60) : level === 2 ? 48 : level === 3 ? 38 : 32;
|
||||
const wraps = wrappedLines(heading[2], width, fontSize);
|
||||
const margins = level === 1 ? 26 : level === 2 ? 39 : level === 3 ? 34 : 24;
|
||||
height += wraps * fontSize * 1.15 + margins;
|
||||
horizontalRisk ||= hasHorizontalRisk(heading[2], width, fontSize);
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (line === '$$' || line.startsWith('$$')) {
|
||||
let end = index + 1;
|
||||
while (end < lines.length && !lines[end].includes('$$'))
|
||||
end++;
|
||||
height += 82 + Math.max(0, end - index - 2) * 38;
|
||||
index = Math.min(lines.length, end + 1);
|
||||
continue;
|
||||
}
|
||||
if (isStandaloneImage(line)) {
|
||||
height += canvasHeight * 0.46 + 16;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (isTableRow(line) && isTableSeparator(lines[index + 1] ?? '')) {
|
||||
const tableLines = [line];
|
||||
index += 2;
|
||||
while (index < lines.length && isTableRow(lines[index].trim())) {
|
||||
tableLines.push(lines[index].trim());
|
||||
index++;
|
||||
}
|
||||
const columnCount = Math.max(1, splitTableRow(tableLines[0]).length);
|
||||
const cellWidth = width / columnCount - 28;
|
||||
for (const row of tableLines) {
|
||||
const rowWraps = Math.max(...splitTableRow(row).map(cell => wrappedLines(cell, cellWidth, 30)), 1);
|
||||
height += rowWraps * 30 * 1.42 + 20;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (isListLine(line)) {
|
||||
const items = [];
|
||||
while (index < lines.length && (isListLine(lines[index].trim()) || /^\s{2,}\S/.test(lines[index]))) {
|
||||
if (isListLine(lines[index].trim()))
|
||||
items.push(lines[index].trim().replace(/^(?:[-+*]|\d+[.)])\s+/, ''));
|
||||
index++;
|
||||
}
|
||||
const itemHeight = items.reduce((sum, item) => sum + wrappedLines(item, width - baseFontSize * 1.2, baseFontSize) * baseLineHeight, 0);
|
||||
height += itemHeight + Math.max(0, items.length - 1) * 10 + 28;
|
||||
horizontalRisk ||= items.some(item => hasHorizontalRisk(item, width - baseFontSize * 1.2, baseFontSize));
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('>')) {
|
||||
const quote = [];
|
||||
while (index < lines.length && lines[index].trim().startsWith('>')) {
|
||||
quote.push(lines[index].trim().replace(/^>\s?/, ''));
|
||||
index++;
|
||||
}
|
||||
const fontSize = layout === 'quote' ? 46 : baseFontSize;
|
||||
height += wrappedLines(quote.join(' '), width - 29, fontSize) * fontSize * (layout === 'quote' ? 1.35 : 1.42) + 48;
|
||||
horizontalRisk ||= hasHorizontalRisk(quote.join(' '), width - 29, fontSize);
|
||||
continue;
|
||||
}
|
||||
const paragraph = [];
|
||||
while (index < lines.length && !isBlockStart(lines, index)) {
|
||||
const text = stripMarkup(lines[index]);
|
||||
if (text)
|
||||
paragraph.push(text);
|
||||
index++;
|
||||
}
|
||||
if (paragraph.length) {
|
||||
const text = paragraph.join(' ');
|
||||
height += wrappedLines(text, width, baseFontSize) * baseLineHeight + 28;
|
||||
horizontalRisk ||= hasHorizontalRisk(text, width, baseFontSize);
|
||||
}
|
||||
else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
horizontalRisk ||= explicitWidthRisk(markdown, width);
|
||||
return { height, horizontalRisk };
|
||||
}
|
||||
function isBlockStart(lines, index) {
|
||||
const line = lines[index].trim();
|
||||
if (!line || isStructuralLine(line) || /^(?:`{3,}|~{3,}|#{1,4}\s|\$\$|>)/.test(line))
|
||||
return true;
|
||||
if (isStandaloneImage(line) || isListLine(line))
|
||||
return true;
|
||||
return isTableRow(line) && isTableSeparator(lines[index + 1] ?? '');
|
||||
}
|
||||
function explicitCodeHeight(lines, fenceIndex, classNames) {
|
||||
const nearby = `${classNames.join(' ')} ${lines.slice(Math.max(0, fenceIndex - 8), fenceIndex + 1).join(' ')}`;
|
||||
for (const [className, height] of codeHeightPresets) {
|
||||
if (new RegExp(`(?:^|\\s|["'])${className}(?:$|\\s|["'])`).test(nearby))
|
||||
return height;
|
||||
}
|
||||
const inlineHeight = /(?:height|max-height)\s*:\s*(\d+(?:\.\d+)?)px/i.exec(nearby);
|
||||
return inlineHeight ? Number.parseFloat(inlineHeight[1]) : 0;
|
||||
}
|
||||
function wrappedLines(value, width, fontSize) {
|
||||
const available = Math.max(1, width / fontSize);
|
||||
return Math.max(1, Math.ceil(displayWidth(stripMarkup(value)) / available));
|
||||
}
|
||||
function displayWidth(value) {
|
||||
let width = 0;
|
||||
for (const character of value) {
|
||||
if (/\s/.test(character))
|
||||
width += 0.33;
|
||||
else if (/[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe10-\ufe6f\uff01-\uff60\uffe0-\uffe6]/.test(character))
|
||||
width += 1;
|
||||
else
|
||||
width += 0.56;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
function hasHorizontalRisk(value, width, fontSize) {
|
||||
const available = width / fontSize;
|
||||
return stripMarkup(value)
|
||||
.split(/\s+/)
|
||||
.some(token => !/[\u2e80-\u9fff]/.test(token) && displayWidth(token) > available * 1.05);
|
||||
}
|
||||
function explicitWidthRisk(markdown, width) {
|
||||
for (const match of markdown.matchAll(/(?:width\s*:\s*|width\s*=\s*["']?)(\d+(?:\.\d+)?)px/gi)) {
|
||||
if (Number.parseFloat(match[1]) > width)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function normalizeClasses(value) {
|
||||
if (Array.isArray(value))
|
||||
return value.flatMap(item => String(item).split(/\s+/)).filter(Boolean);
|
||||
return String(value ?? '').split(/\s+/).filter(Boolean);
|
||||
}
|
||||
function extractHeading(markdown) {
|
||||
return /^(?:#{1,4})\s+(.+)$/m.exec(markdown)?.[1].trim() ?? '';
|
||||
}
|
||||
function stripComments(markdown) {
|
||||
return markdown.replace(/<!--[\s\S]*?-->/g, '');
|
||||
}
|
||||
function stripMarkup(value) {
|
||||
return value
|
||||
.replace(/!\[[^\]]*\]\([^)]*\)(?:\{[^}]*\})?/g, '')
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/[`*_~]/g, '')
|
||||
.trim();
|
||||
}
|
||||
function isStructuralLine(line) {
|
||||
return /^::(?:left|right|image)::$/i.test(line)
|
||||
|| /^<\/?(?:v-clicks|div|span|template|slot)(?:\s[^>]*)?>$/i.test(line);
|
||||
}
|
||||
function isStandaloneImage(line) {
|
||||
return /^!\[[^\]]*\]\([^)]*\)(?:\{[^}]*\})?$/.test(line);
|
||||
}
|
||||
function isListLine(line) {
|
||||
return /^(?:[-+*]|\d+[.)])\s+/.test(line);
|
||||
}
|
||||
function isTableRow(line) {
|
||||
return line.includes('|');
|
||||
}
|
||||
function isTableSeparator(line) {
|
||||
return /^\s*\|?\s*:?-{3,}:?(?:\s*\|\s*:?-{3,}:?)+\s*\|?\s*$/.test(line);
|
||||
}
|
||||
function splitTableRow(line) {
|
||||
return line.replace(/^\s*\||\|\s*$/g, '').split('|').map(cell => cell.trim());
|
||||
}
|
||||
//# sourceMappingURL=static-overflow.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare function resolveTheme(theme?: string): string;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
const directory = path.dirname(fileURLToPath(import.meta.url));
|
||||
const bundledTheme = path.resolve(directory, '..', 'theme');
|
||||
const workspaceTheme = path.resolve(directory, '..', '..', 'packages', 'slidev-theme-easy-jyy');
|
||||
export function resolveTheme(theme) {
|
||||
if (theme)
|
||||
return theme;
|
||||
if (existsSync(path.join(bundledTheme, 'package.json')))
|
||||
return bundledTheme;
|
||||
if (existsSync(path.join(workspaceTheme, 'package.json')))
|
||||
return workspaceTheme;
|
||||
throw new Error('easy-slides 主题不存在;请重新安装或构建 @easy-slides/cli');
|
||||
}
|
||||
//# sourceMappingURL=theme.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../scripts/lib/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,uBAAuB,CAAC,CAAA;AAE/F,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK;QACP,OAAO,KAAK,CAAA;IACd,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;QACrD,OAAO,YAAY,CAAA;IACrB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;QACvD,OAAO,cAAc,CAAA;IACvB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;AAChE,CAAC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare function hashPresenterToken(token: string): string;
|
||||
export declare function hasPresenterToken(env?: NodeJS.ProcessEnv): boolean;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
export function hashPresenterToken(token) {
|
||||
return createHash('sha256').update(token.trim()).digest('hex');
|
||||
}
|
||||
export function hasPresenterToken(env = process.env) {
|
||||
return Boolean(env.PRESENTER_TOKEN?.trim());
|
||||
}
|
||||
//# sourceMappingURL=token.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../scripts/lib/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAChE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACpE,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,CAAA;AAC7C,CAAC"}
|
||||
Reference in New Issue
Block a user