| 1 | import type { DrawingsOptions, FontOptions, ResolvedDrawingsOptions, ResolvedExportOptions, ResolvedFontOptions, SlidevConfig, SlidevThemeMeta } from '@slidev/types' |
| 2 | import { toArray, uniq } from '@antfu/utils' |
| 3 | import { parseAspectRatio } from './utils' |
| 4 | |
| 5 | const RE_QUOTED_STRING = /^(['"]).*\1$/ |
| 6 | const RE_FILENAME_STEM = /([^\\/]+?)(?:\.\w+)?$/ |
| 7 | |
| 8 | export function getDefaultConfig(): SlidevConfig { |
| 9 | return { |
| 10 | theme: 'default', |
| 11 | title: 'Slidev', |
| 12 | titleTemplate: '%s - Slidev', |
| 13 | addons: [], |
| 14 | remoteAssets: false, |
| 15 | monaco: true, |
| 16 | monacoTypesSource: 'local', |
| 17 | monacoTypesAdditionalPackages: [], |
| 18 | monacoTypesIgnorePackages: [], |
| 19 | monacoRunAdditionalDeps: [], |
| 20 | monacoRunUseStrict: true, |
| 21 | download: false, |
| 22 | export: {} as ResolvedExportOptions, |
| 23 | info: false, |
| 24 | highlighter: 'shiki', |
| 25 | twoslash: true, |
| 26 | lineNumbers: false, |
| 27 | colorSchema: 'auto', |
| 28 | routerMode: 'history', |
| 29 | aspectRatio: 16 / 9, |
| 30 | canvasWidth: 980, |
| 31 | exportFilename: '', |
| 32 | selectable: false, |
| 33 | themeConfig: {}, |
| 34 | fonts: {} as ResolvedFontOptions, |
| 35 | favicon: 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png', |
| 36 | drawings: {} as ResolvedDrawingsOptions, |
| 37 | plantUmlServer: 'https://www.plantuml.com/plantuml', |
| 38 | codeCopy: true, |
| 39 | magicMoveCopy: true, |
| 40 | author: '', |
| 41 | record: 'dev', |
| 42 | css: 'unocss', |
| 43 | presenter: true, |
| 44 | browserExporter: 'dev', |
| 45 | htmlAttrs: {}, |
| 46 | transition: null, |
| 47 | editor: true, |
| 48 | mcp: true, |
| 49 | contextMenu: null, |
| 50 | wakeLock: true, |
| 51 | pwa: false, |
| 52 | remote: false, |
| 53 | mdc: false, |
| 54 | comark: false, |
| 55 | seoMeta: {}, |
| 56 | notesAutoRuby: {}, |
| 57 | duration: '30min', |
| 58 | timer: 'stopwatch', |
| 59 | magicMoveDuration: 800, |
| 60 | preloadImages: true, |
| 61 | clickAnimation: '', |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | export function resolveConfig(headmatter: any, themeMeta: SlidevThemeMeta = {}, filepath?: string, verify = false) { |
| 66 | const themeHightlighter = ['prism', 'shiki', 'shikiji'].includes(themeMeta.highlighter || '') |
| 67 | ? themeMeta.highlighter as 'shiki' |
| 68 | : undefined |
| 69 | const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '') |
| 70 | ? themeMeta.colorSchema as 'light' | 'dark' |
| 71 | : undefined |
| 72 | |
| 73 | const defaultConfig = getDefaultConfig() |
| 74 | |
| 75 | const config: SlidevConfig = { |
| 76 | ...defaultConfig, |
| 77 | highlighter: themeHightlighter || defaultConfig.highlighter, |
| 78 | colorSchema: themeColorSchema || defaultConfig.colorSchema, |
| 79 | ...themeMeta.defaults, |
| 80 | ...headmatter.config, |
| 81 | ...headmatter, |
| 82 | fonts: resolveFonts({ |
| 83 | ...defaultConfig.fonts, |
| 84 | ...themeMeta.defaults?.fonts, |
| 85 | ...headmatter.config?.fonts, |
| 86 | ...headmatter?.fonts, |
| 87 | }), |
| 88 | drawings: resolveDrawings(headmatter.drawings, filepath), |
| 89 | htmlAttrs: { |
| 90 | ...defaultConfig.htmlAttrs, |
| 91 | ...themeMeta.defaults?.htmlAttrs, |
| 92 | ...headmatter.config?.htmlAttrs, |
| 93 | ...headmatter?.htmlAttrs, |
| 94 | }, |
| 95 | themeConfig: { |
| 96 | ...defaultConfig.themeConfig, |
| 97 | ...themeMeta.defaults?.themeConfig, |
| 98 | ...headmatter.config?.themeConfig, |
| 99 | ...headmatter?.themeConfig, |
| 100 | }, |
| 101 | } |
| 102 | |
| 103 | // @ts-expect-error compat |
| 104 | if (config.highlighter === 'shikiji') { |
| 105 | console.warn(`[slidev] "shikiji" is merged back to "shiki", you can safely change it "highlighter: shiki"`) |
| 106 | config.highlighter = 'shiki' |
| 107 | } |
| 108 | |
| 109 | // @ts-expect-error compat |
| 110 | if (config.highlighter === 'prism') |
| 111 | throw new Error(`[slidev] "prism" support has been dropped. Please use "highlighter: shiki" instead`) |
| 112 | |
| 113 | if (config.colorSchema !== 'dark' && config.colorSchema !== 'light') |
| 114 | config.colorSchema = 'auto' |
| 115 | if (themeColorSchema && config.colorSchema === 'auto') |
| 116 | config.colorSchema = themeColorSchema |
| 117 | |
| 118 | config.aspectRatio = parseAspectRatio(config.aspectRatio) |
| 119 | |
| 120 | if (verify) |
| 121 | verifyConfig(config, themeMeta) |
| 122 | |
| 123 | return config |
| 124 | } |
| 125 | |
| 126 | export function verifyConfig( |
| 127 | config: SlidevConfig, |
| 128 | themeMeta: SlidevThemeMeta = {}, |
| 129 | warn = (v: string) => console.warn(`[slidev] ${v}`), |
| 130 | ) { |
| 131 | const themeHightlighter = themeMeta.highlighter === 'shiki' |
| 132 | ? themeMeta.highlighter as 'shiki' |
| 133 | : undefined |
| 134 | const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '') |
| 135 | ? themeMeta.colorSchema as 'light' | 'dark' |
| 136 | : undefined |
| 137 | |
| 138 | if (themeColorSchema && config.colorSchema !== themeColorSchema) |
| 139 | warn(`Color schema "${config.colorSchema}" does not supported by the theme`) |
| 140 | |
| 141 | if (themeHightlighter && config.highlighter !== themeHightlighter) |
| 142 | warn(`Syntax highlighter "${config.highlighter}" does not supported by the theme`) |
| 143 | |
| 144 | if (config.css !== 'unocss') { |
| 145 | warn(`Unsupported Atomic CSS engine "${config.css}", fallback to UnoCSS`) |
| 146 | config.css = 'unocss' |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | export function resolveFonts(fonts: FontOptions = {}): ResolvedFontOptions { |
| 151 | const { |
| 152 | fallbacks = true, |
| 153 | italic = false, |
| 154 | provider = 'google', |
| 155 | } = fonts |
| 156 | let sans = toArray(fonts.sans).flatMap(i => i.split(',')).map(i => i.trim()) |
| 157 | let serif = toArray(fonts.serif).flatMap(i => i.split(',')).map(i => i.trim()) |
| 158 | let mono = toArray(fonts.mono).flatMap(i => i.split(',')).map(i => i.trim()) |
| 159 | const weights = toArray(fonts.weights || '200,400,600').flatMap(i => i.toString().split(',')).map(i => i.trim()) |
| 160 | const custom = toArray(fonts.custom).flatMap(i => i.split(',')).map(i => i.trim()) |
| 161 | |
| 162 | const local = toArray(fonts.local).flatMap(i => i.split(',')).map(i => i.trim()) |
| 163 | const webfonts = fonts.webfonts |
| 164 | ? fonts.webfonts |
| 165 | : fallbacks |
| 166 | ? uniq([...sans, ...serif, ...mono, ...custom]).filter(i => !local.includes(i)) |
| 167 | : [] |
| 168 | |
| 169 | function toQuoted(font: string) { |
| 170 | if (RE_QUOTED_STRING.test(font)) |
| 171 | return font |
| 172 | return `"${font}"` |
| 173 | } |
| 174 | |
| 175 | if (fallbacks) { |
| 176 | sans = uniq([ |
| 177 | ...sans.map(toQuoted), |
| 178 | 'ui-sans-serif', |
| 179 | 'system-ui', |
| 180 | '-apple-system', |
| 181 | 'BlinkMacSystemFont', |
| 182 | '"Segoe UI"', |
| 183 | 'Roboto', |
| 184 | '"Helvetica Neue"', |
| 185 | 'Arial', |
| 186 | '"Noto Sans"', |
| 187 | 'sans-serif', |
| 188 | '"Apple Color Emoji"', |
| 189 | '"Segoe UI Emoji"', |
| 190 | '"Segoe UI Symbol"', |
| 191 | '"Noto Color Emoji"', |
| 192 | ]) |
| 193 | serif = uniq([ |
| 194 | ...serif.map(toQuoted), |
| 195 | 'ui-serif', |
| 196 | 'Georgia', |
| 197 | 'Cambria', |
| 198 | '"Times New Roman"', |
| 199 | 'Times', |
| 200 | 'serif', |
| 201 | ]) |
| 202 | mono = uniq([ |
| 203 | ...mono.map(toQuoted), |
| 204 | 'ui-monospace', |
| 205 | 'SFMono-Regular', |
| 206 | 'Menlo', |
| 207 | 'Monaco', |
| 208 | 'Consolas', |
| 209 | '"Liberation Mono"', |
| 210 | '"Courier New"', |
| 211 | 'monospace', |
| 212 | ]) |
| 213 | } |
| 214 | |
| 215 | return { |
| 216 | sans, |
| 217 | serif, |
| 218 | mono, |
| 219 | webfonts, |
| 220 | provider, |
| 221 | local, |
| 222 | italic, |
| 223 | weights, |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | function resolveDrawings(options: DrawingsOptions = {}, filepath?: string): ResolvedDrawingsOptions { |
| 228 | const { |
| 229 | enabled = true, |
| 230 | persist = false, |
| 231 | presenterOnly = false, |
| 232 | syncAll = true, |
| 233 | } = options |
| 234 | |
| 235 | const persistPath = typeof persist === 'string' |
| 236 | ? persist |
| 237 | : persist |
| 238 | ? `.slidev/drawings${filepath ? `/${filepath.match(RE_FILENAME_STEM)?.[1]}` : ''}` |
| 239 | : false |
| 240 | |
| 241 | return { |
| 242 | enabled, |
| 243 | persist: persistPath, |
| 244 | presenterOnly, |
| 245 | syncAll, |
| 246 | } |
| 247 | } |
| 248 |