返回 slidev
drawings.ts
根目录 / packages / slidev / node / integrations / drawings.ts
1 import type { ResolvedSlidevOptions } from '@slidev/types'
2 import { existsSync } from 'node:fs'
3 import fs from 'node:fs/promises'
4 import fg from 'fast-glob'
5 import { basename, dirname, isAbsolute, join, relative, resolve } from 'pathe'
6
7 /**
8 * Whether `key` is a safe bare slide id that can be joined onto `dir` without
9 * escaping it. `loadDrawings` only ever produces numeric keys, so writes
10 * should only ever accept numeric keys too.
11 */
12 export function isSafeDrawingKey(dir: string, key: string): boolean {
13 if (!/^\d+$/.test(key))
14 return false
15 const target = join(dir, `${key}.svg`)
16 const rel = relative(dir, target)
17 return !rel.startsWith('..') && !isAbsolute(rel)
18 }
19
20 function resolveDrawingsDir(options: ResolvedSlidevOptions): string | undefined {
21 return options.data.config.drawings.persist
22 ? resolve(
23 dirname(options.entry),
24 options.data.config.drawings.persist,
25 )
26 : undefined
27 }
28
29 export async function loadDrawings(options: ResolvedSlidevOptions) {
30 const dir = resolveDrawingsDir(options)
31 if (!dir || !existsSync(dir))
32 return {}
33
34 const files = await fg('*.svg', {
35 onlyFiles: true,
36 cwd: dir,
37 absolute: true,
38 suppressErrors: true,
39 })
40
41 const obj: Record<string, string> = {}
42 await Promise.all(files.map(async (path) => {
43 const num = +basename(path, '.svg')
44 if (Number.isNaN(num))
45 return
46 const content = await fs.readFile(path, 'utf8')
47 const lines = content.split(/\n/g)
48 obj[num.toString()] = lines.slice(1, -1).join('\n')
49 }))
50
51 return obj
52 }
53
54 export async function writeDrawings(options: ResolvedSlidevOptions, drawing: Record<string, string>) {
55 const dir = resolveDrawingsDir(options)
56 if (!dir)
57 return
58
59 const width = options.data.config.canvasWidth
60 const height = Math.round(width / options.data.config.aspectRatio)
61 const SVG_HEAD = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">`
62
63 await fs.mkdir(dir, { recursive: true })
64
65 return Promise.all(
66 Object.entries(drawing).map(async ([key, value]) => {
67 if (!value)
68 return
69
70 if (!isSafeDrawingKey(dir, key)) {
71 console.warn(`[slidev] Ignoring drawing with unsafe key: ${key}`)
72 return
73 }
74
75 const svg = `${SVG_HEAD}\n${value}\n</svg>`
76 await fs.writeFile(join(dir, `${key}.svg`), svg, 'utf-8')
77 }),
78 )
79 }
80
80 lines TYPESCRIPT