@slidev/cli 51.6.0 → 51.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,520 +0,0 @@
1
- import {
2
- getRoots
3
- } from "./chunk-TJFRPB4N.js";
4
-
5
- // node/commands/export.ts
6
- import { Buffer } from "node:buffer";
7
- import fs from "node:fs/promises";
8
- import path, { dirname, relative } from "node:path";
9
- import process from "node:process";
10
- import { clearUndefined, ensureSuffix, slash } from "@antfu/utils";
11
- import { outlinePdfFactory } from "@lillallol/outline-pdf";
12
- import { parseRangeString } from "@slidev/parser/core";
13
- import { blue, cyan, dim, green, yellow } from "ansis";
14
- import { Presets, SingleBar } from "cli-progress";
15
- import { resolve } from "mlly";
16
- import * as pdfLib from "pdf-lib";
17
- import { PDFDocument } from "pdf-lib";
18
- function addToTree(tree, info, slideIndexes, level = 1) {
19
- const titleLevel = info.level;
20
- if (titleLevel && titleLevel > level && tree.length > 0 && tree[tree.length - 1].titleLevel < titleLevel) {
21
- addToTree(tree[tree.length - 1].children, info, slideIndexes, level + 1);
22
- } else {
23
- tree.push({
24
- no: info.index,
25
- children: [],
26
- level,
27
- titleLevel: titleLevel ?? level,
28
- path: String(slideIndexes[info.index + 1]),
29
- hideInToc: Boolean(info.frontmatter?.hideInToc),
30
- title: info.title
31
- });
32
- }
33
- }
34
- function makeOutline(tree) {
35
- return tree.map(({ title, path: path2, level, children }) => {
36
- const rootOutline = title ? `${path2}|${"-".repeat(level - 1)}|${title}` : null;
37
- const childrenOutline = makeOutline(children);
38
- return childrenOutline.length > 0 ? `${rootOutline}
39
- ${childrenOutline}` : rootOutline;
40
- }).filter((outline) => !!outline).join("\n");
41
- }
42
- function createSlidevProgress(indeterminate = false) {
43
- function getSpinner(n = 0) {
44
- return [cyan("\u25CF"), green("\u25C6"), blue("\u25A0"), yellow("\u25B2")][n % 4];
45
- }
46
- let current = 0;
47
- let spinner = 0;
48
- let timer;
49
- const progress = new SingleBar({
50
- clearOnComplete: true,
51
- hideCursor: true,
52
- format: ` {spin} ${yellow("rendering")}${indeterminate ? dim(yellow("...")) : " {bar} {value}/{total}"}`,
53
- linewrap: false,
54
- barsize: 30
55
- }, Presets.shades_grey);
56
- return {
57
- bar: progress,
58
- start(total) {
59
- progress.start(total, 0, { spin: getSpinner(spinner) });
60
- timer = setInterval(() => {
61
- spinner += 1;
62
- progress.update(current, { spin: getSpinner(spinner) });
63
- }, 200);
64
- },
65
- update(v) {
66
- current = v;
67
- progress.update(v, { spin: getSpinner(spinner) });
68
- },
69
- stop() {
70
- clearInterval(timer);
71
- progress.stop();
72
- }
73
- };
74
- }
75
- async function exportNotes({
76
- port = 18724,
77
- base = "/",
78
- output = "notes",
79
- timeout = 3e4,
80
- wait = 0
81
- }) {
82
- const { chromium } = await importPlaywright();
83
- const browser = await chromium.launch();
84
- const context = await browser.newContext();
85
- const page = await context.newPage();
86
- const progress = createSlidevProgress(true);
87
- progress.start(1);
88
- if (!output.endsWith(".pdf"))
89
- output = `${output}.pdf`;
90
- await page.goto(`http://localhost:${port}${base}presenter/print`, { waitUntil: "networkidle", timeout });
91
- await page.waitForLoadState("networkidle");
92
- await page.emulateMedia({ media: "screen" });
93
- if (wait)
94
- await page.waitForTimeout(wait);
95
- await page.pdf({
96
- path: output,
97
- margin: {
98
- left: 0,
99
- top: 0,
100
- right: 0,
101
- bottom: 0
102
- },
103
- printBackground: true,
104
- preferCSSPageSize: true
105
- });
106
- progress.stop();
107
- browser.close();
108
- return output;
109
- }
110
- async function exportSlides({
111
- port = 18724,
112
- total = 0,
113
- range,
114
- format = "pdf",
115
- output = "slides",
116
- slides,
117
- base = "/",
118
- timeout = 3e4,
119
- wait = 0,
120
- dark = false,
121
- routerMode = "history",
122
- width = 1920,
123
- height = 1080,
124
- withClicks = false,
125
- executablePath = void 0,
126
- withToc = false,
127
- perSlide = false,
128
- scale = 1,
129
- waitUntil,
130
- omitBackground = false
131
- }) {
132
- const pages = parseRangeString(total, range);
133
- const { chromium } = await importPlaywright();
134
- const browser = await chromium.launch({
135
- executablePath
136
- });
137
- const context = await browser.newContext({
138
- viewport: {
139
- width,
140
- // Calculate height for every slides to be in the viewport to trigger the rendering of iframes (twitter, youtube...)
141
- height: perSlide ? height : height * pages.length
142
- },
143
- deviceScaleFactor: scale
144
- });
145
- const page = await context.newPage();
146
- const progress = createSlidevProgress(!perSlide);
147
- progress.start(pages.length);
148
- if (format === "pdf") {
149
- await genPagePdf();
150
- } else if (format === "png") {
151
- await genPagePng(output);
152
- } else if (format === "md") {
153
- await genPageMd();
154
- } else if (format === "pptx") {
155
- const buffers = await genPagePng(false);
156
- await genPagePptx(buffers);
157
- } else {
158
- throw new Error(`[slidev] Unsupported exporting format "${format}"`);
159
- }
160
- progress.stop();
161
- browser.close();
162
- const relativeOutput = slash(relative(".", output));
163
- return relativeOutput.startsWith(".") ? relativeOutput : `./${relativeOutput}`;
164
- async function go(no, clicks) {
165
- const query = new URLSearchParams();
166
- if (withClicks)
167
- query.set("print", "clicks");
168
- else
169
- query.set("print", "true");
170
- if (range)
171
- query.set("range", range);
172
- if (clicks)
173
- query.set("clicks", clicks);
174
- const url = routerMode === "hash" ? `http://localhost:${port}${base}?${query}#${no}` : `http://localhost:${port}${base}${no}?${query}`;
175
- await page.goto(url, {
176
- waitUntil,
177
- timeout
178
- });
179
- if (waitUntil)
180
- await page.waitForLoadState(waitUntil);
181
- await page.emulateMedia({ colorScheme: dark ? "dark" : "light", media: "screen" });
182
- const slide = no === "print" ? page.locator("body") : page.locator(`[data-slidev-no="${no}"]`);
183
- await slide.waitFor();
184
- {
185
- const elements = slide.locator(".slidev-slide-loading");
186
- const count = await elements.count();
187
- for (let index = 0; index < count; index++)
188
- await elements.nth(index).waitFor({ state: "detached" });
189
- }
190
- {
191
- const elements = slide.locator("[data-waitfor]");
192
- const count = await elements.count();
193
- for (let index = 0; index < count; index++) {
194
- const element = elements.nth(index);
195
- const attribute = await element.getAttribute("data-waitfor");
196
- if (attribute) {
197
- await element.locator(attribute).waitFor({ state: "visible" }).catch((e) => {
198
- console.error(e);
199
- process.exitCode = 1;
200
- });
201
- }
202
- }
203
- }
204
- {
205
- const frames = page.frames();
206
- await Promise.all(frames.map((frame) => frame.waitForLoadState()));
207
- }
208
- {
209
- const container = slide.locator("#mermaid-rendering-container");
210
- const count = await container.count();
211
- if (count > 0) {
212
- while (true) {
213
- const element = container.locator("div").first();
214
- if (await element.count() === 0)
215
- break;
216
- await element.waitFor({ state: "detached" });
217
- }
218
- await container.evaluate((node) => node.style.display = "none");
219
- }
220
- }
221
- {
222
- const elements = slide.locator(".monaco-aria-container");
223
- const count = await elements.count();
224
- for (let index = 0; index < count; index++) {
225
- const element = elements.nth(index);
226
- await element.evaluate((node) => node.style.display = "none");
227
- }
228
- }
229
- if (wait)
230
- await page.waitForTimeout(wait);
231
- }
232
- async function getSlidesIndex() {
233
- const clicksBySlide = {};
234
- const slides2 = page.locator(".print-slide-container");
235
- const count = await slides2.count();
236
- for (let i = 0; i < count; i++) {
237
- const id = await slides2.nth(i).getAttribute("id") || "";
238
- const path2 = Number(id.split("-")[0]);
239
- clicksBySlide[path2] = (clicksBySlide[path2] || 0) + 1;
240
- }
241
- const slideIndexes = Object.fromEntries(Object.entries(clicksBySlide).reduce((acc, [path2, clicks], i) => {
242
- acc.push([path2, clicks + (acc[i - 1]?.[1] ?? 0)]);
243
- return acc;
244
- }, []));
245
- return slideIndexes;
246
- }
247
- function getClicksFromUrl(url) {
248
- return url.match(/clicks=([1-9]\d*)/)?.[1];
249
- }
250
- async function genPageWithClicks(fn, no, clicks) {
251
- await fn(no, clicks);
252
- if (withClicks) {
253
- await page.keyboard.press("ArrowRight", { delay: 100 });
254
- const _clicks = getClicksFromUrl(page.url());
255
- if (_clicks && clicks !== _clicks)
256
- await genPageWithClicks(fn, no, _clicks);
257
- }
258
- }
259
- async function genPagePdfPerSlide() {
260
- const buffers = [];
261
- const genPdfBuffer = async (i, clicks) => {
262
- await go(i, clicks);
263
- const pdf = await page.pdf({
264
- width,
265
- height,
266
- margin: {
267
- left: 0,
268
- top: 0,
269
- right: 0,
270
- bottom: 0
271
- },
272
- pageRanges: "1",
273
- printBackground: true,
274
- preferCSSPageSize: true
275
- });
276
- buffers.push(pdf);
277
- };
278
- let idx = 0;
279
- for (const i of pages) {
280
- await genPageWithClicks(genPdfBuffer, i);
281
- progress.update(++idx);
282
- }
283
- let mergedPdf = await PDFDocument.create({});
284
- for (const pdfBytes of buffers) {
285
- const pdf = await PDFDocument.load(pdfBytes);
286
- const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
287
- copiedPages.forEach((page2) => {
288
- mergedPdf.addPage(page2);
289
- });
290
- }
291
- addPdfMetadata(mergedPdf);
292
- if (withToc)
293
- mergedPdf = await addTocToPdf(mergedPdf);
294
- const buffer = await mergedPdf.save();
295
- await fs.writeFile(output, buffer);
296
- }
297
- async function genPagePdfOnePiece() {
298
- await go("print");
299
- await page.pdf({
300
- path: output,
301
- width,
302
- height,
303
- margin: {
304
- left: 0,
305
- top: 0,
306
- right: 0,
307
- bottom: 0
308
- },
309
- printBackground: true,
310
- preferCSSPageSize: true
311
- });
312
- let pdfData = await fs.readFile(output);
313
- let pdf = await PDFDocument.load(pdfData);
314
- addPdfMetadata(pdf);
315
- if (withToc)
316
- pdf = await addTocToPdf(pdf);
317
- pdfData = Buffer.from(await pdf.save());
318
- await fs.writeFile(output, pdfData);
319
- }
320
- async function genPagePngOnePiece(writeToDisk) {
321
- const result = [];
322
- await go("print");
323
- const slideContainers = page.locator(".print-slide-container");
324
- const count = await slideContainers.count();
325
- for (let i = 0; i < count; i++) {
326
- progress.update(i + 1);
327
- const id = await slideContainers.nth(i).getAttribute("id") || "";
328
- const slideNo = +id.split("-")[0];
329
- const buffer = await slideContainers.nth(i).screenshot({
330
- omitBackground
331
- });
332
- const filename = `${withClicks ? id : slideNo}.png`;
333
- result.push({ slideIndex: slideNo - 1, buffer, filename });
334
- if (writeToDisk)
335
- await fs.writeFile(path.join(writeToDisk, filename), buffer);
336
- }
337
- return result;
338
- }
339
- async function genPagePngPerSlide(writeToDisk) {
340
- const result = [];
341
- const genScreenshot = async (no, clicks) => {
342
- await go(no, clicks);
343
- const buffer = await page.screenshot({
344
- omitBackground
345
- });
346
- const filename = `${no.toString().padStart(2, "0")}${clicks ? `-${clicks}` : ""}.png`;
347
- result.push({ slideIndex: no - 1, buffer, filename });
348
- if (writeToDisk) {
349
- await fs.writeFile(
350
- path.join(writeToDisk, filename),
351
- buffer
352
- );
353
- }
354
- };
355
- for (const no of pages)
356
- await genPageWithClicks(genScreenshot, no);
357
- return result;
358
- }
359
- function genPagePdf() {
360
- if (!output.endsWith(".pdf"))
361
- output = `${output}.pdf`;
362
- return perSlide ? genPagePdfPerSlide() : genPagePdfOnePiece();
363
- }
364
- async function genPagePng(writeToDisk) {
365
- if (writeToDisk) {
366
- await fs.rm(writeToDisk, { force: true, recursive: true });
367
- await fs.mkdir(writeToDisk, { recursive: true });
368
- }
369
- return perSlide ? genPagePngPerSlide(writeToDisk) : genPagePngOnePiece(writeToDisk);
370
- }
371
- async function genPageMd() {
372
- const pngs = await genPagePng(dirname(output));
373
- const content = slides.map(
374
- ({ title, index, note }) => pngs.filter(({ slideIndex }) => slideIndex === index).map(({ filename }) => `![${title || index + 1}](./${filename})
375
-
376
- `).join("") + (note ? `${note.trim()}
377
-
378
- ` : "")
379
- ).join("---\n\n");
380
- await fs.writeFile(ensureSuffix(".md", output), content);
381
- }
382
- async function genPagePptx(pngs) {
383
- const { default: PptxGenJS } = await import("pptxgenjs");
384
- const pptx = new PptxGenJS();
385
- const layoutName = `${width}x${height}`;
386
- pptx.defineLayout({
387
- name: layoutName,
388
- width: width / 96,
389
- height: height / 96
390
- });
391
- pptx.layout = layoutName;
392
- const titleSlide = slides[0];
393
- pptx.author = titleSlide?.frontmatter?.author;
394
- pptx.company = "Created using Slidev";
395
- if (titleSlide?.title)
396
- pptx.title = titleSlide?.title;
397
- if (titleSlide?.frontmatter?.info)
398
- pptx.subject = titleSlide?.frontmatter?.info;
399
- pngs.forEach(({ slideIndex, buffer: buffer2 }) => {
400
- const slide = pptx.addSlide();
401
- slide.background = {
402
- data: `data:image/png;base64,${buffer2.toString("base64")}`
403
- };
404
- const note = slides[slideIndex].note;
405
- if (note)
406
- slide.addNotes(note);
407
- });
408
- const buffer = await pptx.write({
409
- outputType: "nodebuffer"
410
- });
411
- if (!output.endsWith(".pptx"))
412
- output = `${output}.pptx`;
413
- await fs.writeFile(output, buffer);
414
- }
415
- function addPdfMetadata(pdf) {
416
- const titleSlide = slides[0];
417
- if (titleSlide?.title)
418
- pdf.setTitle(titleSlide.title);
419
- if (titleSlide?.frontmatter?.info)
420
- pdf.setSubject(titleSlide.frontmatter.info);
421
- if (titleSlide?.frontmatter?.author)
422
- pdf.setAuthor(titleSlide.frontmatter.author);
423
- if (titleSlide?.frontmatter?.keywords) {
424
- if (Array.isArray(titleSlide?.frontmatter?.keywords))
425
- pdf.setKeywords(titleSlide?.frontmatter?.keywords);
426
- else
427
- pdf.setKeywords(titleSlide?.frontmatter?.keywords.split(","));
428
- }
429
- }
430
- async function addTocToPdf(pdf) {
431
- const outlinePdf = outlinePdfFactory(pdfLib);
432
- const slideIndexes = await getSlidesIndex();
433
- const tocTree = slides.filter((slide) => slide.title).reduce((acc, slide) => {
434
- addToTree(acc, slide, slideIndexes);
435
- return acc;
436
- }, []);
437
- const outline = makeOutline(tocTree);
438
- return await outlinePdf({ outline, pdf });
439
- }
440
- }
441
- function getExportOptions(args, options, outFilename) {
442
- const config = {
443
- ...options.data.config.export,
444
- ...args,
445
- ...clearUndefined({
446
- waitUntil: args["wait-until"],
447
- withClicks: args["with-clicks"],
448
- executablePath: args["executable-path"],
449
- withToc: args["with-toc"],
450
- perSlide: args["per-slide"],
451
- omitBackground: args["omit-background"]
452
- })
453
- };
454
- const {
455
- entry,
456
- output,
457
- format,
458
- timeout,
459
- wait,
460
- waitUntil,
461
- range,
462
- dark,
463
- withClicks,
464
- executablePath,
465
- withToc,
466
- perSlide,
467
- scale,
468
- omitBackground
469
- } = config;
470
- outFilename = output || options.data.config.exportFilename || outFilename || `${path.basename(entry, ".md")}-export`;
471
- return {
472
- output: outFilename,
473
- slides: options.data.slides,
474
- total: options.data.slides.length,
475
- range,
476
- format: format || "pdf",
477
- timeout: timeout ?? 3e4,
478
- wait: wait ?? 0,
479
- waitUntil: waitUntil === "none" ? void 0 : waitUntil ?? "networkidle",
480
- dark: dark || options.data.config.colorSchema === "dark",
481
- routerMode: options.data.config.routerMode,
482
- width: options.data.config.canvasWidth,
483
- height: Math.round(options.data.config.canvasWidth / options.data.config.aspectRatio),
484
- withClicks: withClicks ?? format === "pptx",
485
- executablePath,
486
- withToc: withToc || false,
487
- perSlide: perSlide || false,
488
- scale: scale || 2,
489
- omitBackground: omitBackground ?? false
490
- };
491
- }
492
- async function importPlaywright() {
493
- const { userRoot, userWorkspaceRoot } = await getRoots();
494
- try {
495
- return await import(await resolve("playwright-chromium", { url: userRoot }));
496
- } catch {
497
- }
498
- if (userWorkspaceRoot !== userRoot) {
499
- try {
500
- return await import(await resolve("playwright-chromium", { url: userWorkspaceRoot }));
501
- } catch {
502
- }
503
- }
504
- const { resolveGlobal } = await import("resolve-global");
505
- try {
506
- const imported = await import(resolveGlobal("playwright-chromium"));
507
- return imported.default ?? imported;
508
- } catch {
509
- }
510
- try {
511
- return await import("playwright-chromium");
512
- } catch {
513
- }
514
- throw new Error("The exporting for Slidev is powered by Playwright, please install it via `npm i -D playwright-chromium`");
515
- }
516
- export {
517
- exportNotes,
518
- exportSlides,
519
- getExportOptions
520
- };