@slidev/client 52.8.0 → 52.9.1
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.
- package/internals/QuickOverview.vue +1 -1
- package/internals/ShikiEditor.vue +10 -4
- package/internals/SlideContainer.vue +2 -1
- package/internals/SlideWrapper.vue +10 -11
- package/package.json +4 -3
- package/setup/code-runners.ts +11 -3
- package/setup/monaco.ts +11 -11
- package/setup/shiki-options.ts +125 -0
- package/setup/shiki.ts +30 -0
- package/styles/transitions.css +4 -4
|
@@ -84,7 +84,7 @@ useEventListener('keypress', (e) => {
|
|
|
84
84
|
keyboardBuffer.value += String(num)
|
|
85
85
|
|
|
86
86
|
// beyond the number of slides, reset
|
|
87
|
-
if (+keyboardBuffer.value
|
|
87
|
+
if (+keyboardBuffer.value > slides.value.length) {
|
|
88
88
|
keyboardBuffer.value = ''
|
|
89
89
|
return
|
|
90
90
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { getHighlighter } from '#slidev/shiki'
|
|
3
2
|
import { ref, shallowRef } from 'vue'
|
|
4
3
|
import { useIME } from '../composables/useIME'
|
|
5
4
|
|
|
@@ -11,14 +10,21 @@ const { composingContent, onInput, onCompositionEnd } = useIME(content)
|
|
|
11
10
|
|
|
12
11
|
const textareaEl = ref<HTMLTextAreaElement | null>(null)
|
|
13
12
|
|
|
14
|
-
const highlight = shallowRef<
|
|
15
|
-
|
|
13
|
+
const highlight = shallowRef<((code: string) => string) | null>(null)
|
|
14
|
+
import('../setup/shiki').then(async (m) => {
|
|
15
|
+
const { getEagerHighlighter, defaultHighlightOptions } = await m.default()
|
|
16
|
+
const highlighter = await getEagerHighlighter()
|
|
17
|
+
highlight.value = (code: string) => highlighter.codeToHtml(code, {
|
|
18
|
+
...defaultHighlightOptions,
|
|
19
|
+
lang: 'markdown',
|
|
20
|
+
})
|
|
21
|
+
})
|
|
16
22
|
</script>
|
|
17
23
|
|
|
18
24
|
<template>
|
|
19
25
|
<div class="absolute left-3 right-0 inset-y-2 font-mono overflow-x-hidden overflow-y-auto cursor-text">
|
|
20
26
|
<div v-if="highlight" class="relative w-full h-max min-h-full">
|
|
21
|
-
<div class="relative w-full h-max" v-html="`${highlight(composingContent
|
|
27
|
+
<div class="relative w-full h-max" v-html="`${highlight(composingContent)} `" />
|
|
22
28
|
<textarea
|
|
23
29
|
ref="textareaEl" v-model="composingContent" :placeholder="props.placeholder"
|
|
24
30
|
class="absolute inset-0 resize-none text-transparent bg-transparent focus:outline-none caret-black dark:caret-white overflow-y-hidden"
|
|
@@ -52,7 +52,6 @@ const contentStyle = computed(() => ({
|
|
|
52
52
|
...props.contentStyle,
|
|
53
53
|
'height': `${slideHeight.value}px`,
|
|
54
54
|
'width': `${slideWidth.value}px`,
|
|
55
|
-
'transform': `translate(-50%, -50%) scale(${scale.value})`,
|
|
56
55
|
'--slidev-slide-scale': scale.value,
|
|
57
56
|
}))
|
|
58
57
|
|
|
@@ -117,6 +116,8 @@ const snapshot = computed(() => {
|
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
.slidev-slide-content {
|
|
119
|
+
--slidev-slide-container-scale: var(--slidev-slide-scale);
|
|
120
|
+
transform: translate(-50%, -50%) scale(var(--slidev-slide-scale));
|
|
120
121
|
@apply absolute left-1/2 top-1/2 overflow-hidden bg-main;
|
|
121
122
|
}
|
|
122
123
|
</style>
|
|
@@ -32,19 +32,9 @@ provideLocal(injectionRenderContext, ref(props.renderContext))
|
|
|
32
32
|
provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
|
|
33
33
|
provideLocal(injectionSlideZoom, zoom)
|
|
34
34
|
|
|
35
|
-
const zoomStyle = computed(() => {
|
|
36
|
-
return zoom.value === 1
|
|
37
|
-
? undefined
|
|
38
|
-
: {
|
|
39
|
-
width: `${100 / zoom.value}%`,
|
|
40
|
-
height: `${100 / zoom.value}%`,
|
|
41
|
-
transformOrigin: 'top left',
|
|
42
|
-
transform: `scale(${zoom.value})`,
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
35
|
const style = computed<CSSProperties>(() => ({
|
|
46
|
-
...zoomStyle.value,
|
|
47
36
|
'user-select': configs.selectable ? undefined : 'none',
|
|
37
|
+
'--slidev-slide-zoom-scale': zoom.value === 1 ? undefined : zoom.value,
|
|
48
38
|
}))
|
|
49
39
|
</script>
|
|
50
40
|
|
|
@@ -69,5 +59,14 @@ const style = computed<CSSProperties>(() => ({
|
|
|
69
59
|
.slidev-page {
|
|
70
60
|
position: absolute;
|
|
71
61
|
inset: 0;
|
|
62
|
+
|
|
63
|
+
/* Zoom handling */
|
|
64
|
+
--slidev-slide-zoom-scale: 1;
|
|
65
|
+
width: calc(100% / var(--slidev-slide-zoom-scale));
|
|
66
|
+
height: calc(100% / var(--slidev-slide-zoom-scale));
|
|
67
|
+
transform-origin: top left;
|
|
68
|
+
scale: var(--slidev-slide-zoom-scale);
|
|
69
|
+
/* slide scale = container scale * zoom scale */
|
|
70
|
+
--slidev-slide-scale: calc(var(--slidev-slide-container-scale) * var(--slidev-slide-zoom-scale));
|
|
72
71
|
}
|
|
73
72
|
</style>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "52.
|
|
4
|
+
"version": "52.9.1",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@vueuse/core": "^14.0.0",
|
|
43
43
|
"@vueuse/math": "^14.0.0",
|
|
44
44
|
"@vueuse/motion": "^3.0.3",
|
|
45
|
+
"ansis": "^4.2.0",
|
|
45
46
|
"drauu": "^0.4.3",
|
|
46
47
|
"file-saver": "^2.0.5",
|
|
47
48
|
"floating-vue": "^5.2.2",
|
|
@@ -61,8 +62,8 @@
|
|
|
61
62
|
"vue": "^3.5.24",
|
|
62
63
|
"vue-router": "^4.6.3",
|
|
63
64
|
"yaml": "^2.8.1",
|
|
64
|
-
"@slidev/parser": "52.
|
|
65
|
-
"@slidev/types": "52.
|
|
65
|
+
"@slidev/parser": "52.9.1",
|
|
66
|
+
"@slidev/types": "52.9.1"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"vite": "^7.2.2"
|
package/setup/code-runners.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CodeRunner, CodeRunnerOutput, CodeRunnerOutputs, CodeRunnerOutputText } from '@slidev/types'
|
|
2
|
+
import type { CodeToHastOptions } from 'shiki'
|
|
2
3
|
import type ts from 'typescript'
|
|
3
|
-
|
|
4
4
|
import deps from '#slidev/monaco-run-deps'
|
|
5
5
|
import setups from '#slidev/setups/code-runners'
|
|
6
6
|
import { createSingletonPromise } from '@antfu/utils'
|
|
@@ -15,8 +15,16 @@ export default createSingletonPromise(async () => {
|
|
|
15
15
|
ts: runTypeScript,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const {
|
|
19
|
-
|
|
18
|
+
const { defaultHighlightOptions, getEagerHighlighter } = await (await import('./shiki')).default()
|
|
19
|
+
|
|
20
|
+
const highlighter = await getEagerHighlighter()
|
|
21
|
+
const highlight = (code: string, lang: string, options?: Partial<CodeToHastOptions>) => {
|
|
22
|
+
return highlighter.codeToHtml(code, {
|
|
23
|
+
...defaultHighlightOptions,
|
|
24
|
+
lang,
|
|
25
|
+
...options,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
20
28
|
|
|
21
29
|
const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {
|
|
22
30
|
try {
|
package/setup/monaco.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { MonacoSetupReturn } from '@slidev/types'
|
|
|
2
2
|
import configs from '#slidev/configs'
|
|
3
3
|
import setups from '#slidev/setups/monaco'
|
|
4
4
|
import { createSingletonPromise } from '@antfu/utils'
|
|
5
|
+
import { shikiToMonaco } from '@shikijs/monaco'
|
|
5
6
|
import { setupTypeAcquisition } from '@typescript/ata'
|
|
6
7
|
import * as monaco from 'monaco-editor'
|
|
7
8
|
|
|
@@ -83,14 +84,16 @@ const setup = createSingletonPromise(async () => {
|
|
|
83
84
|
})
|
|
84
85
|
: () => { }
|
|
85
86
|
|
|
87
|
+
const { getEagerHighlighter, languageNames, themeOption } = await (await import('./shiki')).default()
|
|
88
|
+
|
|
86
89
|
monaco.languages.register({ id: 'vue' })
|
|
87
90
|
monaco.languages.register({ id: 'html' })
|
|
88
91
|
monaco.languages.register({ id: 'css' })
|
|
89
92
|
monaco.languages.register({ id: 'typescript' })
|
|
90
93
|
monaco.languages.register({ id: 'javascript' })
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
for (const lang of languageNames) {
|
|
95
|
+
monaco.languages.register({ id: lang })
|
|
96
|
+
}
|
|
94
97
|
|
|
95
98
|
const editorOptions: MonacoSetupReturn['editorOptions'] & object = {}
|
|
96
99
|
for (const setup of setups) {
|
|
@@ -111,21 +114,18 @@ const setup = createSingletonPromise(async () => {
|
|
|
111
114
|
})
|
|
112
115
|
|
|
113
116
|
// Use Shiki to highlight Monaco
|
|
117
|
+
const highlighter = await getEagerHighlighter()
|
|
114
118
|
shikiToMonaco(highlighter, monaco)
|
|
115
|
-
if (typeof
|
|
116
|
-
monaco.editor.setTheme(
|
|
119
|
+
if (typeof themeOption === 'string') {
|
|
120
|
+
monaco.editor.setTheme(themeOption)
|
|
117
121
|
}
|
|
118
122
|
else {
|
|
119
123
|
watchEffect(() => {
|
|
120
124
|
monaco.editor.setTheme(isDark.value
|
|
121
|
-
?
|
|
122
|
-
:
|
|
125
|
+
? themeOption.dark || 'vitesse-dark'
|
|
126
|
+
: themeOption.light || 'vitesse-light')
|
|
123
127
|
})
|
|
124
128
|
}
|
|
125
|
-
// Register all languages, otherwise Monaco will not highlight them
|
|
126
|
-
for (const lang of languages) {
|
|
127
|
-
monaco.languages.register({ id: lang })
|
|
128
|
-
}
|
|
129
129
|
|
|
130
130
|
return {
|
|
131
131
|
monaco,
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// This module also runs in the Node.js environment
|
|
2
|
+
|
|
3
|
+
import type { ResolvedSlidevUtils, ShikiContext, ShikiSetupReturn } from '@slidev/types'
|
|
4
|
+
import type { LanguageInput, ThemeInput, ThemeRegistrationAny } from 'shiki'
|
|
5
|
+
import { objectMap } from '@antfu/utils'
|
|
6
|
+
import { red, yellow } from 'ansis'
|
|
7
|
+
import { bundledLanguages, bundledThemes } from 'shiki'
|
|
8
|
+
|
|
9
|
+
export const shikiContext: ShikiContext = {
|
|
10
|
+
/** @deprecated */
|
|
11
|
+
loadTheme() {
|
|
12
|
+
throw new Error('`loadTheme` is no longer supported.')
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveShikiOptions(options: (ShikiSetupReturn | void)[]) {
|
|
17
|
+
const mergedOptions: Record<string, any> = Object.assign({}, ...options)
|
|
18
|
+
|
|
19
|
+
if ('theme' in mergedOptions && 'themes' in mergedOptions)
|
|
20
|
+
delete mergedOptions.theme
|
|
21
|
+
|
|
22
|
+
// Rename theme to themes when provided in multiple themes format, but exclude when it's a theme object.
|
|
23
|
+
if (mergedOptions.theme && typeof mergedOptions.theme !== 'string' && !mergedOptions.theme.name && !mergedOptions.theme.tokenColors) {
|
|
24
|
+
mergedOptions.themes = mergedOptions.theme
|
|
25
|
+
delete mergedOptions.theme
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// No theme at all, apply the default
|
|
29
|
+
if (!mergedOptions.theme && !mergedOptions.themes) {
|
|
30
|
+
mergedOptions.themes = {
|
|
31
|
+
dark: 'vitesse-dark',
|
|
32
|
+
light: 'vitesse-light',
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (mergedOptions.themes)
|
|
37
|
+
mergedOptions.defaultColor = false
|
|
38
|
+
|
|
39
|
+
const themeOption = extractThemeName(mergedOptions.theme) || extractThemeNames(mergedOptions.themes || {})
|
|
40
|
+
const themeNames = typeof themeOption === 'string' ? [themeOption] : Object.values(themeOption)
|
|
41
|
+
|
|
42
|
+
const themeInput: Record<string, ThemeInput> = Object.assign({}, bundledThemes)
|
|
43
|
+
if (typeof mergedOptions.theme === 'object' && mergedOptions.theme?.name) {
|
|
44
|
+
themeInput[mergedOptions.theme.name] = mergedOptions.theme
|
|
45
|
+
}
|
|
46
|
+
if (mergedOptions.themes) {
|
|
47
|
+
for (const theme of Object.values<ThemeRegistrationAny | string>(mergedOptions.themes)) {
|
|
48
|
+
if (typeof theme === 'object' && theme?.name) {
|
|
49
|
+
themeInput[theme.name] = theme
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const languageNames = new Set<string>(['markdown', 'vue', 'javascript', 'typescript', 'html', 'css'])
|
|
55
|
+
const languageInput: Record<string, LanguageInput> = Object.assign({}, bundledLanguages)
|
|
56
|
+
for (const option of options) {
|
|
57
|
+
const langs = option?.langs
|
|
58
|
+
if (langs == null)
|
|
59
|
+
continue
|
|
60
|
+
if (Array.isArray(langs)) {
|
|
61
|
+
for (const lang of langs.flat()) {
|
|
62
|
+
if (typeof lang === 'function') {
|
|
63
|
+
console.error(red('[slidev] `langs` option returned by setup/shiki.ts cannot be an array containing functions. Please use the record format (`{ [name]: () => {...} }`) instead.'))
|
|
64
|
+
}
|
|
65
|
+
else if (typeof lang === 'string') {
|
|
66
|
+
// a name of a Shiki built-in language
|
|
67
|
+
// which can be loaded on demand without overhead, so all built-in languages are available.
|
|
68
|
+
// Only need to include them explicitly in browser environment.
|
|
69
|
+
languageNames.add(lang)
|
|
70
|
+
}
|
|
71
|
+
else if (lang.name) {
|
|
72
|
+
// a custom grammar object
|
|
73
|
+
languageNames.add(lang.name)
|
|
74
|
+
languageInput[lang.name] = lang
|
|
75
|
+
for (const alias of lang.aliases || []) {
|
|
76
|
+
languageNames.add(alias)
|
|
77
|
+
languageInput[alias] = lang
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.error(red('[slidev] Invalid lang option in shiki setup:'), lang)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (typeof langs === 'object') {
|
|
86
|
+
// a map from name to loader or grammar object
|
|
87
|
+
for (const name of Object.keys(langs))
|
|
88
|
+
languageNames.add(name)
|
|
89
|
+
Object.assign(languageInput, langs)
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.error(red('[slidev] Invalid langs option in shiki setup:'), langs)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
options: mergedOptions as ResolvedSlidevUtils['shikiOptions'],
|
|
98
|
+
themeOption,
|
|
99
|
+
themeNames,
|
|
100
|
+
themeInput,
|
|
101
|
+
languageNames,
|
|
102
|
+
languageInput,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractThemeName(theme?: ThemeRegistrationAny | string): string | undefined {
|
|
107
|
+
if (!theme)
|
|
108
|
+
return undefined
|
|
109
|
+
if (typeof theme === 'string')
|
|
110
|
+
return theme
|
|
111
|
+
if (!theme.name)
|
|
112
|
+
console.warn(yellow('[slidev] Theme'), theme, yellow('does not have a name, which may cause issues.'))
|
|
113
|
+
return theme.name
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function extractThemeNames(themes?: Record<string, ThemeRegistrationAny | string>): Record<string, string> {
|
|
117
|
+
if (!themes)
|
|
118
|
+
return {}
|
|
119
|
+
return objectMap(themes, (key, theme) => {
|
|
120
|
+
const name = extractThemeName(theme)
|
|
121
|
+
if (!name)
|
|
122
|
+
return undefined
|
|
123
|
+
return [key, name]
|
|
124
|
+
})
|
|
125
|
+
}
|
package/setup/shiki.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import setups from '#slidev/setups/shiki'
|
|
2
|
+
import { createSingletonPromise } from '@antfu/utils'
|
|
3
|
+
import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
|
|
4
|
+
import { createdBundledHighlighter, createSingletonShorthands } from 'shiki/core'
|
|
5
|
+
import { resolveShikiOptions, shikiContext } from './shiki-options'
|
|
6
|
+
|
|
7
|
+
export default createSingletonPromise(async () => {
|
|
8
|
+
const { options, languageNames, languageInput, themeOption, themeNames, themeInput } = resolveShikiOptions(await Promise.all(setups.map(setup => setup(shikiContext))))
|
|
9
|
+
|
|
10
|
+
const createHighlighter = createdBundledHighlighter<string, string>({
|
|
11
|
+
engine: createJavaScriptRegexEngine,
|
|
12
|
+
langs: languageInput,
|
|
13
|
+
themes: themeInput,
|
|
14
|
+
})
|
|
15
|
+
const shorthands = createSingletonShorthands(createHighlighter)
|
|
16
|
+
const getEagerHighlighter = createSingletonPromise(() => shorthands.getSingletonHighlighter({
|
|
17
|
+
...options,
|
|
18
|
+
langs: [...languageNames],
|
|
19
|
+
themes: themeNames,
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
defaultHighlightOptions: options,
|
|
24
|
+
getEagerHighlighter,
|
|
25
|
+
shorthands,
|
|
26
|
+
languageNames,
|
|
27
|
+
themeNames,
|
|
28
|
+
themeOption,
|
|
29
|
+
}
|
|
30
|
+
})
|
package/styles/transitions.css
CHANGED
|
@@ -12,22 +12,22 @@
|
|
|
12
12
|
|
|
13
13
|
.slide-left-enter-from,
|
|
14
14
|
.slide-right-leave-to {
|
|
15
|
-
|
|
15
|
+
translate: 100% 0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.slide-left-leave-to,
|
|
19
19
|
.slide-right-enter-from {
|
|
20
|
-
|
|
20
|
+
translate: -100% 0;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.slide-up-enter-from,
|
|
24
24
|
.slide-down-leave-to {
|
|
25
|
-
|
|
25
|
+
translate: 0 100%;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
.slide-up-leave-to,
|
|
29
29
|
.slide-down-enter-from {
|
|
30
|
-
|
|
30
|
+
translate: 0 -100%;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/* Fading */
|