@slidev/client 0.48.8 → 0.48.9
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/builtin/ShikiMagicMove.vue +34 -25
- package/constants.ts +1 -0
- package/internals/SlidesShow.vue +0 -1
- package/modules/v-click.ts +40 -21
- package/modules/v-mark.ts +7 -1
- package/modules/v-motion.ts +120 -0
- package/package.json +10 -10
- package/setup/code-runners.ts +94 -10
- package/setup/main.ts +2 -2
- package/utils.ts +16 -0
|
@@ -45,43 +45,45 @@ onMounted(() => {
|
|
|
45
45
|
// Calculate the step and rangeStr based on the current click count
|
|
46
46
|
const clickCount = clicks.current - start
|
|
47
47
|
let step = steps.length - 1
|
|
48
|
-
let
|
|
48
|
+
let currentClickSum = 0
|
|
49
49
|
let rangeStr = 'all'
|
|
50
50
|
for (let i = 0; i < ranges.value.length; i++) {
|
|
51
51
|
const current = ranges.value[i]
|
|
52
|
-
if (clickCount <
|
|
52
|
+
if (clickCount < currentClickSum + current.length - 1) {
|
|
53
53
|
step = i
|
|
54
|
-
rangeStr = current[clickCount -
|
|
54
|
+
rangeStr = current[clickCount - currentClickSum + 1]
|
|
55
55
|
break
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
currentClickSum += current.length || 1
|
|
58
58
|
}
|
|
59
59
|
stepIndex.value = step
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
const pre = container.value?.querySelector('.shiki') as HTMLElement
|
|
63
|
+
if (!pre)
|
|
64
|
+
return
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const children = (Array.from(pre.children) as HTMLElement[])
|
|
67
|
+
.slice(1) // Remove the first anchor
|
|
68
|
+
.filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
// Group to lines between `<br>`
|
|
71
|
+
const lines = children.reduce((acc, el) => {
|
|
72
|
+
if (el.tagName === 'BR')
|
|
73
|
+
acc.push([])
|
|
74
|
+
else
|
|
75
|
+
acc[acc.length - 1].push(el)
|
|
76
|
+
return acc
|
|
77
|
+
}, [[]] as HTMLElement[][])
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// Update highlight range
|
|
80
|
+
updateCodeHighlightRange(
|
|
81
|
+
rangeStr,
|
|
82
|
+
lines.length,
|
|
83
|
+
1,
|
|
84
|
+
no => lines[no],
|
|
85
|
+
)
|
|
86
|
+
})
|
|
85
87
|
},
|
|
86
88
|
{ immediate: true },
|
|
87
89
|
)
|
|
@@ -104,3 +106,10 @@ onMounted(() => {
|
|
|
104
106
|
/>
|
|
105
107
|
</div>
|
|
106
108
|
</template>
|
|
109
|
+
|
|
110
|
+
<style>
|
|
111
|
+
.slidev-code-magic-move .shiki-magic-move-enter-from,
|
|
112
|
+
.slidev-code-magic-move .shiki-magic-move-leave-to {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
package/constants.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const injectionRenderContext = '$$slidev-render-context' as unknown as In
|
|
|
13
13
|
export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
|
|
14
14
|
export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
|
|
15
15
|
export const injectionSlideZoom = '$$slidev-slide-zoom' as unknown as InjectionKey<ComputedRef<number>>
|
|
16
|
+
export const injectionClickVisibility = '$$slidev-click-visibility' as unknown as InjectionKey<ComputedRef<true | 'before' | 'after'>>
|
|
16
17
|
|
|
17
18
|
export const CLASS_VCLICK_TARGET = 'slidev-vclick-target'
|
|
18
19
|
export const CLASS_VCLICK_HIDDEN = 'slidev-vclick-hidden'
|
package/internals/SlidesShow.vue
CHANGED
package/modules/v-click.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResolvedClicksInfo } from '@slidev/types'
|
|
2
|
-
import type { App, DirectiveBinding
|
|
2
|
+
import type { App, DirectiveBinding } from 'vue'
|
|
3
3
|
import { computed, watchEffect } from 'vue'
|
|
4
4
|
import {
|
|
5
5
|
CLASS_VCLICK_CURRENT,
|
|
@@ -8,14 +8,12 @@ import {
|
|
|
8
8
|
CLASS_VCLICK_HIDDEN_EXP,
|
|
9
9
|
CLASS_VCLICK_PRIOR,
|
|
10
10
|
CLASS_VCLICK_TARGET,
|
|
11
|
+
injectionClickVisibility,
|
|
11
12
|
injectionClicksContext,
|
|
12
13
|
} from '../constants'
|
|
14
|
+
import { directiveInject, directiveProvide } from '../utils'
|
|
13
15
|
|
|
14
|
-
export type VClickValue = string | [string | number, string | number] | boolean
|
|
15
|
-
|
|
16
|
-
export function dirInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
|
|
17
|
-
return (dir.instance?.$ as any).provides[key as any] ?? defaultValue
|
|
18
|
-
}
|
|
16
|
+
export type VClickValue = undefined | string | number | [string | number, string | number] | boolean
|
|
19
17
|
|
|
20
18
|
export function createVClickDirectives() {
|
|
21
19
|
return {
|
|
@@ -25,7 +23,7 @@ export function createVClickDirectives() {
|
|
|
25
23
|
name: 'v-click',
|
|
26
24
|
|
|
27
25
|
mounted(el, dir) {
|
|
28
|
-
const resolved = resolveClick(el, dir, dir.value)
|
|
26
|
+
const resolved = resolveClick(el, dir, dir.value, true)
|
|
29
27
|
if (resolved == null)
|
|
30
28
|
return
|
|
31
29
|
|
|
@@ -37,7 +35,8 @@ export function createVClickDirectives() {
|
|
|
37
35
|
if (clicks[1] != null)
|
|
38
36
|
el.dataset.slidevClicksEnd = String(clicks[1])
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
// @ts-expect-error extra prop
|
|
39
|
+
el.watchStopHandle = watchEffect(() => {
|
|
41
40
|
const active = resolved.isActive.value
|
|
42
41
|
const current = resolved.isCurrent.value
|
|
43
42
|
const prior = active && !current
|
|
@@ -62,13 +61,14 @@ export function createVClickDirectives() {
|
|
|
62
61
|
name: 'v-after',
|
|
63
62
|
|
|
64
63
|
mounted(el, dir) {
|
|
65
|
-
const resolved = resolveClick(el, dir, dir.value, true)
|
|
64
|
+
const resolved = resolveClick(el, dir, dir.value, true, true)
|
|
66
65
|
if (resolved == null)
|
|
67
66
|
return
|
|
68
67
|
|
|
69
68
|
el.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
// @ts-expect-error extra prop
|
|
71
|
+
el.watchStopHandle = watchEffect(() => {
|
|
72
72
|
const active = resolved.isActive.value
|
|
73
73
|
const current = resolved.isCurrent.value
|
|
74
74
|
const prior = active && !current
|
|
@@ -93,13 +93,14 @@ export function createVClickDirectives() {
|
|
|
93
93
|
name: 'v-click-hide',
|
|
94
94
|
|
|
95
95
|
mounted(el, dir) {
|
|
96
|
-
const resolved = resolveClick(el, dir, dir.value, false, true)
|
|
96
|
+
const resolved = resolveClick(el, dir, dir.value, true, false, true)
|
|
97
97
|
if (resolved == null)
|
|
98
98
|
return
|
|
99
99
|
|
|
100
100
|
el.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
// @ts-expect-error extra prop
|
|
103
|
+
el.watchStopHandle = watchEffect(() => {
|
|
103
104
|
const active = resolved.isActive.value
|
|
104
105
|
const current = resolved.isCurrent.value
|
|
105
106
|
const prior = active && !current
|
|
@@ -117,20 +118,20 @@ export function createVClickDirectives() {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
function
|
|
121
|
+
function isClickActive(thisClick: number | [number, number], clicks: number) {
|
|
121
122
|
return Array.isArray(thisClick)
|
|
122
123
|
? thisClick[0] <= clicks && clicks < thisClick[1]
|
|
123
124
|
: thisClick <= clicks
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
function
|
|
127
|
+
function isClickCurrent(thisClick: number | [number, number], clicks: number) {
|
|
127
128
|
return Array.isArray(thisClick)
|
|
128
129
|
? thisClick[0] === clicks
|
|
129
130
|
: thisClick === clicks
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VClickValue, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
133
|
-
const ctx =
|
|
133
|
+
export function resolveClick(el: Element | string, dir: DirectiveBinding<any>, value: VClickValue, provideVisibility = false, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
134
|
+
const ctx = directiveInject(dir, injectionClicksContext)?.value
|
|
134
135
|
|
|
135
136
|
if (!el || !ctx)
|
|
136
137
|
return null
|
|
@@ -152,29 +153,47 @@ export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VCl
|
|
|
152
153
|
if (Array.isArray(value)) {
|
|
153
154
|
// range (absolute)
|
|
154
155
|
delta = 0
|
|
155
|
-
thisClick = value
|
|
156
|
+
thisClick = [+value[0], +value[1]]
|
|
156
157
|
maxClick = +value[1]
|
|
157
158
|
}
|
|
158
159
|
else {
|
|
159
160
|
({ start: thisClick, end: maxClick, delta } = ctx.resolve(value))
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
const isActive = computed(() => isClickActive(thisClick, ctx.current))
|
|
164
|
+
const isCurrent = computed(() => isClickCurrent(thisClick, ctx.current))
|
|
165
|
+
const isShown = computed(() => flagHide ? !isActive.value : isActive.value)
|
|
166
|
+
|
|
162
167
|
const resolved: ResolvedClicksInfo = {
|
|
163
168
|
max: maxClick,
|
|
164
169
|
clicks: thisClick,
|
|
165
170
|
delta,
|
|
166
|
-
isActive
|
|
167
|
-
isCurrent
|
|
168
|
-
isShown
|
|
171
|
+
isActive,
|
|
172
|
+
isCurrent,
|
|
173
|
+
isShown,
|
|
169
174
|
flagFade,
|
|
170
175
|
flagHide,
|
|
171
176
|
}
|
|
172
177
|
ctx.register(el, resolved)
|
|
178
|
+
|
|
179
|
+
if (provideVisibility) {
|
|
180
|
+
directiveProvide(dir, injectionClickVisibility, computed(() => {
|
|
181
|
+
if (isShown.value)
|
|
182
|
+
return true
|
|
183
|
+
if (Array.isArray(thisClick))
|
|
184
|
+
return ctx.current < thisClick[0] ? 'before' : 'after'
|
|
185
|
+
else
|
|
186
|
+
return flagHide ? 'after' : 'before'
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
173
190
|
return resolved
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
function unmounted(el: HTMLElement, dir: DirectiveBinding<any>) {
|
|
177
194
|
el.classList.toggle(CLASS_VCLICK_TARGET, false)
|
|
178
|
-
const ctx =
|
|
195
|
+
const ctx = directiveInject(dir, injectionClicksContext)?.value
|
|
179
196
|
ctx?.unregister(el)
|
|
197
|
+
// @ts-expect-error extra prop
|
|
198
|
+
el.watchStopHandle?.()
|
|
180
199
|
}
|
package/modules/v-mark.ts
CHANGED
|
@@ -121,7 +121,8 @@ export function createVMarkDirective() {
|
|
|
121
121
|
return
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
// @ts-expect-error extra prop
|
|
125
|
+
el.watchStopHandle = watchEffect(() => {
|
|
125
126
|
let shouldShow: boolean | undefined
|
|
126
127
|
|
|
127
128
|
if (options.value.class)
|
|
@@ -147,6 +148,11 @@ export function createVMarkDirective() {
|
|
|
147
148
|
annotation.hide()
|
|
148
149
|
})
|
|
149
150
|
},
|
|
151
|
+
|
|
152
|
+
unmounted: (el) => {
|
|
153
|
+
// @ts-expect-error extra prop
|
|
154
|
+
el.watchStopHandle?.()
|
|
155
|
+
},
|
|
150
156
|
})
|
|
151
157
|
},
|
|
152
158
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { App, ObjectDirective } from 'vue'
|
|
2
|
+
import { watch } from 'vue'
|
|
3
|
+
import { MotionDirective } from '@vueuse/motion'
|
|
4
|
+
import type { ResolvedClicksInfo } from '@slidev/types'
|
|
5
|
+
import { injectionClickVisibility, injectionClicksContext, injectionCurrentPage, injectionRenderContext } from '../constants'
|
|
6
|
+
import { useNav } from '../composables/useNav'
|
|
7
|
+
import { makeId } from '../logic/utils'
|
|
8
|
+
import { directiveInject } from '../utils'
|
|
9
|
+
import type { VClickValue } from './v-click'
|
|
10
|
+
import { resolveClick } from './v-click'
|
|
11
|
+
|
|
12
|
+
export type MotionDirectiveValue = undefined | VClickValue | {
|
|
13
|
+
key?: string
|
|
14
|
+
at?: VClickValue
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createVMotionDirectives() {
|
|
18
|
+
return {
|
|
19
|
+
install(app: App) {
|
|
20
|
+
const original = MotionDirective() as ObjectDirective
|
|
21
|
+
app.directive<HTMLElement | SVGElement, string>('motion', {
|
|
22
|
+
// @ts-expect-error extra prop
|
|
23
|
+
name: 'v-motion',
|
|
24
|
+
mounted(el, binding, node, prevNode) {
|
|
25
|
+
const props = node.props = { ...node.props }
|
|
26
|
+
|
|
27
|
+
const variantInitial = { ...props.initial, ...props.variants?.['slidev-initial'] }
|
|
28
|
+
const variantEnter = { ...props.enter, ...props.variants?.['slidev-enter'] }
|
|
29
|
+
const variantLeave = { ...props.leave, ...props.variants?.['slidev-leave'] }
|
|
30
|
+
delete props.initial
|
|
31
|
+
delete props.enter
|
|
32
|
+
delete props.leave
|
|
33
|
+
|
|
34
|
+
const idPrefix = `${makeId()}-`
|
|
35
|
+
const clicks: {
|
|
36
|
+
id: string
|
|
37
|
+
at: number | [number, number]
|
|
38
|
+
variant: Record<string, unknown>
|
|
39
|
+
resolved: ResolvedClicksInfo | null
|
|
40
|
+
}[] = []
|
|
41
|
+
|
|
42
|
+
for (const k of Object.keys(props)) {
|
|
43
|
+
if (k.startsWith('click-')) {
|
|
44
|
+
const s = k.slice(6)
|
|
45
|
+
const at = s.includes('-') ? s.split('-').map(Number) as [number, number] : +s
|
|
46
|
+
const id = idPrefix + s
|
|
47
|
+
clicks.push({
|
|
48
|
+
id,
|
|
49
|
+
at,
|
|
50
|
+
variant: { ...props[k] },
|
|
51
|
+
resolved: resolveClick(id, binding, at),
|
|
52
|
+
})
|
|
53
|
+
delete props[k]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
clicks.sort((a, b) => (Array.isArray(a.at) ? a.at[0] : a.at) - (Array.isArray(b.at) ? b.at[0] : b.at))
|
|
58
|
+
|
|
59
|
+
original.created!(el, binding, node, prevNode)
|
|
60
|
+
original.mounted!(el, binding, node, prevNode)
|
|
61
|
+
|
|
62
|
+
const thisPage = directiveInject(binding, injectionCurrentPage)
|
|
63
|
+
const renderContext = directiveInject(binding, injectionRenderContext)
|
|
64
|
+
const clickVisibility = directiveInject(binding, injectionClickVisibility)
|
|
65
|
+
const clicksContext = directiveInject(binding, injectionClicksContext)
|
|
66
|
+
const { currentPage, clicks: currentClicks, isPrintMode } = useNav()
|
|
67
|
+
// @ts-expect-error extra prop
|
|
68
|
+
const motion = el.motionInstance
|
|
69
|
+
motion.clickIds = clicks.map(i => i.id)
|
|
70
|
+
motion.set(variantInitial)
|
|
71
|
+
motion.watchStopHandle = watch(
|
|
72
|
+
[thisPage, currentPage, currentClicks].filter(Boolean),
|
|
73
|
+
() => {
|
|
74
|
+
const visibility = clickVisibility?.value ?? true
|
|
75
|
+
if (!clicksContext?.value || !['slide', 'presenter'].includes(renderContext?.value ?? '')) {
|
|
76
|
+
const mixedVariant: Record<string, unknown> = { ...variantInitial, ...variantEnter }
|
|
77
|
+
for (const { variant } of clicks)
|
|
78
|
+
Object.assign(mixedVariant, variant)
|
|
79
|
+
|
|
80
|
+
motion.set(mixedVariant)
|
|
81
|
+
}
|
|
82
|
+
else if (isPrintMode.value || thisPage?.value === currentPage.value) {
|
|
83
|
+
if (visibility === true) {
|
|
84
|
+
const mixedVariant: Record<string, unknown> = { ...variantInitial, ...variantEnter }
|
|
85
|
+
for (const { variant, resolved: resolvedClick } of clicks) {
|
|
86
|
+
if (!resolvedClick || resolvedClick.isActive.value)
|
|
87
|
+
Object.assign(mixedVariant, variant)
|
|
88
|
+
}
|
|
89
|
+
if (isPrintMode.value)
|
|
90
|
+
motion.set(mixedVariant) // print with clicks
|
|
91
|
+
else
|
|
92
|
+
motion.apply(mixedVariant)
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
motion.apply(visibility === 'before' ? variantInitial : variantLeave)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
motion.apply((thisPage?.value ?? -1) > currentPage.value ? variantInitial : variantLeave)
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
immediate: true,
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
},
|
|
107
|
+
unmounted(el, dir) {
|
|
108
|
+
if (!directiveInject(dir, injectionClicksContext)?.value)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
const ctx = directiveInject(dir, injectionClicksContext)?.value
|
|
112
|
+
// @ts-expect-error extra prop
|
|
113
|
+
const motion = el.motionInstance
|
|
114
|
+
motion.clickIds.map((id: string) => ctx?.unregister(id))
|
|
115
|
+
motion.watchStopHandle()
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.48.
|
|
4
|
+
"version": "0.48.9",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"@iconify-json/carbon": "^1.1.31",
|
|
33
33
|
"@iconify-json/ph": "^1.1.11",
|
|
34
34
|
"@iconify-json/svg-spinners": "^1.1.2",
|
|
35
|
-
"@shikijs/monaco": "^1.2.
|
|
36
|
-
"@shikijs/vitepress-twoslash": "^1.2.
|
|
35
|
+
"@shikijs/monaco": "^1.2.3",
|
|
36
|
+
"@shikijs/vitepress-twoslash": "^1.2.3",
|
|
37
37
|
"@slidev/rough-notation": "^0.1.0",
|
|
38
38
|
"@typescript/ata": "^0.9.4",
|
|
39
|
-
"@unhead/vue": "^1.9.
|
|
40
|
-
"@unocss/reset": "^0.58.
|
|
39
|
+
"@unhead/vue": "^1.9.4",
|
|
40
|
+
"@unocss/reset": "^0.58.9",
|
|
41
41
|
"@vueuse/core": "^10.9.0",
|
|
42
42
|
"@vueuse/math": "^10.9.0",
|
|
43
43
|
"@vueuse/motion": "^2.1.0",
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
"monaco-editor": "^0.47.0",
|
|
54
54
|
"prettier": "^3.2.5",
|
|
55
55
|
"recordrtc": "^5.6.2",
|
|
56
|
-
"shiki": "^1.2.
|
|
57
|
-
"shiki-magic-move": "^0.3.
|
|
56
|
+
"shiki": "^1.2.3",
|
|
57
|
+
"shiki-magic-move": "^0.3.5",
|
|
58
58
|
"typescript": "^5.4.3",
|
|
59
|
-
"unocss": "^0.58.
|
|
59
|
+
"unocss": "^0.58.9",
|
|
60
60
|
"vue": "^3.4.21",
|
|
61
61
|
"vue-demi": "^0.14.7",
|
|
62
62
|
"vue-router": "^4.3.0",
|
|
63
|
-
"@slidev/parser": "0.48.
|
|
64
|
-
"@slidev/types": "0.48.
|
|
63
|
+
"@slidev/parser": "0.48.9",
|
|
64
|
+
"@slidev/types": "0.48.9"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"vite": "^5.2.7"
|
package/setup/code-runners.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { createSingletonPromise } from '@antfu/utils'
|
|
1
|
+
import { createSingletonPromise, ensurePrefix, slash } from '@antfu/utils'
|
|
2
2
|
import type { CodeRunner, CodeRunnerContext, CodeRunnerOutput, CodeRunnerOutputText, CodeRunnerOutputs } from '@slidev/types'
|
|
3
3
|
import type { CodeToHastOptions } from 'shiki'
|
|
4
|
+
import type ts from 'typescript'
|
|
4
5
|
import { isDark } from '../logic/dark'
|
|
5
6
|
import setups from '#slidev/setups/code-runners'
|
|
6
7
|
|
|
7
8
|
export default createSingletonPromise(async () => {
|
|
8
9
|
const runners: Record<string, CodeRunner> = {
|
|
9
|
-
javascript:
|
|
10
|
-
js:
|
|
10
|
+
javascript: runTypeScript,
|
|
11
|
+
js: runTypeScript,
|
|
11
12
|
typescript: runTypeScript,
|
|
12
13
|
ts: runTypeScript,
|
|
13
14
|
}
|
|
@@ -24,6 +25,18 @@ export default createSingletonPromise(async () => {
|
|
|
24
25
|
...options,
|
|
25
26
|
})
|
|
26
27
|
|
|
28
|
+
const resolveId = async (specifier: string) => {
|
|
29
|
+
if (!/^(@[^\/:]+?\/)?[^\/:]+$/.test(specifier))
|
|
30
|
+
return specifier
|
|
31
|
+
const res = await fetch(`/@slidev/resolve-id/${specifier}`)
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
return null
|
|
34
|
+
const id = await res.text()
|
|
35
|
+
if (!id)
|
|
36
|
+
return null
|
|
37
|
+
return `/@fs${ensurePrefix('/', slash(id))}`
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {
|
|
28
41
|
try {
|
|
29
42
|
const runner = runners[lang]
|
|
@@ -34,6 +47,7 @@ export default createSingletonPromise(async () => {
|
|
|
34
47
|
{
|
|
35
48
|
options,
|
|
36
49
|
highlight,
|
|
50
|
+
resolveId,
|
|
37
51
|
run: async (code, lang) => {
|
|
38
52
|
return await run(code, lang, options)
|
|
39
53
|
},
|
|
@@ -60,7 +74,7 @@ export default createSingletonPromise(async () => {
|
|
|
60
74
|
})
|
|
61
75
|
|
|
62
76
|
// Ported from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/playground/src/sidebar/runtime.ts
|
|
63
|
-
|
|
77
|
+
async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
|
|
64
78
|
const allLogs: CodeRunnerOutput[] = []
|
|
65
79
|
|
|
66
80
|
const replace = {} as any
|
|
@@ -159,10 +173,80 @@ export async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
|
|
|
159
173
|
let tsModule: typeof import('typescript') | undefined
|
|
160
174
|
|
|
161
175
|
export async function runTypeScript(code: string, context: CodeRunnerContext) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
tsModule ??= await import('typescript')
|
|
177
|
+
|
|
178
|
+
code = tsModule.transpileModule(code, {
|
|
179
|
+
compilerOptions: {
|
|
180
|
+
module: tsModule.ModuleKind.ESNext,
|
|
181
|
+
target: tsModule.ScriptTarget.ES2022,
|
|
182
|
+
},
|
|
183
|
+
transformers: {
|
|
184
|
+
after: [transformImports],
|
|
185
|
+
},
|
|
186
|
+
}).outputText
|
|
187
|
+
|
|
188
|
+
const importRegex = /import\s*\(\s*(['"])(.+?)['"]\s*\)/g
|
|
189
|
+
const idMap: Record<string, string> = {}
|
|
190
|
+
for (const [,,specifier] of code.matchAll(importRegex)!)
|
|
191
|
+
idMap[specifier] = await context.resolveId(specifier) ?? specifier
|
|
192
|
+
code = code.replace(importRegex, (_full, quote, specifier) => `import(${quote}${idMap[specifier] ?? specifier}${quote})`)
|
|
193
|
+
|
|
194
|
+
return await runJavaScript(code)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Transform import statements to dynamic imports
|
|
199
|
+
*/
|
|
200
|
+
function transformImports(context: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
|
201
|
+
const { factory } = context
|
|
202
|
+
const { isImportDeclaration, isNamedImports, NodeFlags } = tsModule!
|
|
203
|
+
return (sourceFile: ts.SourceFile) => {
|
|
204
|
+
const statements = [...sourceFile.statements]
|
|
205
|
+
for (let i = 0; i < statements.length; i++) {
|
|
206
|
+
const statement = statements[i]
|
|
207
|
+
if (!isImportDeclaration(statement))
|
|
208
|
+
continue
|
|
209
|
+
let bindingPattern: ts.ObjectBindingPattern | ts.Identifier
|
|
210
|
+
const namedBindings = statement.importClause?.namedBindings
|
|
211
|
+
const bindings: ts.BindingElement[] = []
|
|
212
|
+
if (statement.importClause?.name)
|
|
213
|
+
bindings.push(factory.createBindingElement(undefined, factory.createIdentifier('default'), statement.importClause.name))
|
|
214
|
+
if (namedBindings) {
|
|
215
|
+
if (isNamedImports(namedBindings)) {
|
|
216
|
+
for (const specifier of namedBindings.elements)
|
|
217
|
+
bindings.push(factory.createBindingElement(undefined, specifier.propertyName, specifier.name))
|
|
218
|
+
bindingPattern = factory.createObjectBindingPattern(bindings)
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
bindingPattern = factory.createIdentifier(namedBindings.name.text)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
bindingPattern = factory.createObjectBindingPattern(bindings)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const newStatement = factory.createVariableStatement(
|
|
229
|
+
undefined,
|
|
230
|
+
factory.createVariableDeclarationList(
|
|
231
|
+
[
|
|
232
|
+
factory.createVariableDeclaration(
|
|
233
|
+
bindingPattern,
|
|
234
|
+
undefined,
|
|
235
|
+
undefined,
|
|
236
|
+
factory.createAwaitExpression(
|
|
237
|
+
factory.createCallExpression(
|
|
238
|
+
factory.createIdentifier('import'),
|
|
239
|
+
undefined,
|
|
240
|
+
[statement.moduleSpecifier],
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
],
|
|
245
|
+
NodeFlags.Const,
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
statements[i] = newStatement
|
|
249
|
+
}
|
|
250
|
+
return factory.updateSourceFile(sourceFile, statements)
|
|
251
|
+
}
|
|
168
252
|
}
|
package/setup/main.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { AppContext } from '@slidev/types'
|
|
2
|
-
import { MotionPlugin } from '@vueuse/motion'
|
|
3
2
|
import TwoSlashFloatingVue from '@shikijs/vitepress-twoslash/client'
|
|
4
3
|
import type { App } from 'vue'
|
|
5
4
|
import { nextTick } from 'vue'
|
|
@@ -8,6 +7,7 @@ import { createHead } from '@unhead/vue'
|
|
|
8
7
|
import { routeForceRefresh } from '../logic/route'
|
|
9
8
|
import { createVClickDirectives } from '../modules/v-click'
|
|
10
9
|
import { createVMarkDirective } from '../modules/v-mark'
|
|
10
|
+
import { createVMotionDirectives } from '../modules/v-motion'
|
|
11
11
|
import { routes } from '../routes'
|
|
12
12
|
import setups from '#slidev/setups/main'
|
|
13
13
|
|
|
@@ -34,7 +34,7 @@ export default async function setupMain(app: App) {
|
|
|
34
34
|
app.use(createHead())
|
|
35
35
|
app.use(createVClickDirectives())
|
|
36
36
|
app.use(createVMarkDirective())
|
|
37
|
-
app.use(
|
|
37
|
+
app.use(createVMotionDirectives())
|
|
38
38
|
app.use(TwoSlashFloatingVue as any, { container: '#twoslash-container' })
|
|
39
39
|
|
|
40
40
|
const context: AppContext = {
|
package/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SlideRoute } from '@slidev/types'
|
|
2
|
+
import type { DirectiveBinding, InjectionKey } from 'vue'
|
|
2
3
|
import { configs } from './env'
|
|
3
4
|
|
|
4
5
|
export function getSlideClass(route?: SlideRoute, extra = '') {
|
|
@@ -22,3 +23,18 @@ export async function downloadPDF() {
|
|
|
22
23
|
`${configs.title}.pdf`,
|
|
23
24
|
)
|
|
24
25
|
}
|
|
26
|
+
|
|
27
|
+
export function directiveInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
|
|
28
|
+
return (dir.instance?.$ as any).provides[key as any] ?? defaultValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function directiveProvide<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, value?: T) {
|
|
32
|
+
const instance = dir.instance?.$ as any
|
|
33
|
+
if (instance) {
|
|
34
|
+
let provides = instance.provides
|
|
35
|
+
const parentProvides = instance.parent?.provides
|
|
36
|
+
if (provides === parentProvides)
|
|
37
|
+
provides = instance.provides = Object.create(parentProvides)
|
|
38
|
+
provides[key as any] = value
|
|
39
|
+
}
|
|
40
|
+
}
|