@slidev/client 0.48.7 → 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/composables/useClicks.ts +14 -6
- package/composables/useNav.ts +15 -7
- package/constants.ts +2 -0
- package/internals/ClicksSlider.vue +10 -7
- package/internals/Goto.vue +2 -2
- package/internals/PresenterMouse.vue +6 -2
- package/internals/PrintSlide.vue +6 -2
- 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 +13 -13
- package/pages/print.vue +1 -1
- package/setup/code-runners.ts +100 -12
- 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/composables/useClicks.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sum } from '@antfu/utils'
|
|
1
|
+
import { clamp, sum } from '@antfu/utils'
|
|
2
2
|
import type { ClicksContext, SlideRoute } from '@slidev/types'
|
|
3
3
|
import type { Ref } from 'vue'
|
|
4
4
|
import { ref, shallowReactive } from 'vue'
|
|
@@ -7,18 +7,21 @@ import { routeForceRefresh } from '../logic/route'
|
|
|
7
7
|
|
|
8
8
|
export function createClicksContextBase(
|
|
9
9
|
current: Ref<number>,
|
|
10
|
-
|
|
10
|
+
clicksStart = 0,
|
|
11
|
+
clicksTotalOverrides?: number,
|
|
11
12
|
): ClicksContext {
|
|
12
13
|
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
13
14
|
const map: ClicksContext['map'] = shallowReactive(new Map())
|
|
14
15
|
|
|
15
16
|
return {
|
|
16
17
|
get current() {
|
|
17
|
-
|
|
18
|
+
// Here we haven't know clicksTotal yet.
|
|
19
|
+
return clamp(+current.value, clicksStart, this.total)
|
|
18
20
|
},
|
|
19
21
|
set current(value) {
|
|
20
|
-
current.value = +value
|
|
22
|
+
current.value = clamp(+value, clicksStart, this.total)
|
|
21
23
|
},
|
|
24
|
+
clicksStart,
|
|
22
25
|
relativeOffsets,
|
|
23
26
|
map,
|
|
24
27
|
onMounted() { },
|
|
@@ -56,7 +59,7 @@ export function createClicksContextBase(
|
|
|
56
59
|
get total() {
|
|
57
60
|
// eslint-disable-next-line no-unused-expressions
|
|
58
61
|
routeForceRefresh.value
|
|
59
|
-
return
|
|
62
|
+
return clicksTotalOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
|
|
60
63
|
},
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -65,5 +68,10 @@ export function createFixedClicks(
|
|
|
65
68
|
route?: SlideRoute | undefined,
|
|
66
69
|
currentInit = 0,
|
|
67
70
|
): ClicksContext {
|
|
68
|
-
|
|
71
|
+
const clicksStart = route?.meta.slide?.frontmatter.clicksStart ?? 0
|
|
72
|
+
return createClicksContextBase(
|
|
73
|
+
ref(Math.max(currentInit, clicksStart)),
|
|
74
|
+
clicksStart,
|
|
75
|
+
route?.meta?.clicks,
|
|
76
|
+
)
|
|
69
77
|
}
|
package/composables/useNav.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { useRouter } from 'vue-router'
|
|
|
5
5
|
import type { RouteLocationNormalized, Router } from 'vue-router'
|
|
6
6
|
import { createSharedComposable } from '@vueuse/core'
|
|
7
7
|
import { logicOr } from '@vueuse/math'
|
|
8
|
+
import { clamp } from '@antfu/utils'
|
|
8
9
|
import { getCurrentTransition } from '../logic/transition'
|
|
9
10
|
import { getSlide, getSlidePath } from '../logic/slides'
|
|
10
11
|
import { CLICKS_MAX } from '../constants'
|
|
@@ -33,6 +34,7 @@ export interface SlidevContextNav {
|
|
|
33
34
|
|
|
34
35
|
clicksContext: ComputedRef<ClicksContext>
|
|
35
36
|
clicks: ComputedRef<number>
|
|
37
|
+
clicksStart: ComputedRef<number>
|
|
36
38
|
clicksTotal: ComputedRef<number>
|
|
37
39
|
|
|
38
40
|
/** The table of content tree */
|
|
@@ -99,6 +101,7 @@ export function useNavBase(
|
|
|
99
101
|
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))
|
|
100
102
|
|
|
101
103
|
const clicks = computed(() => clicksContext.value.current)
|
|
104
|
+
const clicksStart = computed(() => clicksContext.value.clicksStart)
|
|
102
105
|
const clicksTotal = computed(() => clicksContext.value.total)
|
|
103
106
|
const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])
|
|
104
107
|
const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])
|
|
@@ -140,7 +143,7 @@ export function useNavBase(
|
|
|
140
143
|
|
|
141
144
|
async function prev() {
|
|
142
145
|
clicksDirection.value = -1
|
|
143
|
-
if (queryClicks.value <=
|
|
146
|
+
if (queryClicks.value <= clicksStart.value)
|
|
144
147
|
await prevSlide()
|
|
145
148
|
else
|
|
146
149
|
queryClicks.value -= 1
|
|
@@ -177,6 +180,9 @@ export function useNavBase(
|
|
|
177
180
|
skipTransition.value = false
|
|
178
181
|
const pageChanged = currentSlideNo.value !== page
|
|
179
182
|
const clicksChanged = clicks !== queryClicks.value
|
|
183
|
+
const meta = getSlide(page)?.meta
|
|
184
|
+
const clicksStart = meta?.slide?.frontmatter.clicksStart ?? 0
|
|
185
|
+
clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)
|
|
180
186
|
if (pageChanged || clicksChanged) {
|
|
181
187
|
await router?.push({
|
|
182
188
|
path: getSlidePath(page, isPresenter.value),
|
|
@@ -202,6 +208,7 @@ export function useNavBase(
|
|
|
202
208
|
prevRoute,
|
|
203
209
|
clicksContext,
|
|
204
210
|
clicks,
|
|
211
|
+
clicksStart,
|
|
205
212
|
clicksTotal,
|
|
206
213
|
hasNext,
|
|
207
214
|
hasPrev,
|
|
@@ -289,24 +296,25 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
|
289
296
|
computed({
|
|
290
297
|
get() {
|
|
291
298
|
if (currentSlideNo.value === thisNo)
|
|
292
|
-
return +(queryClicksRaw.value
|
|
299
|
+
return Math.max(+(queryClicksRaw.value ?? 0), context.clicksStart)
|
|
293
300
|
else if (currentSlideNo.value > thisNo)
|
|
294
301
|
return CLICKS_MAX
|
|
295
302
|
else
|
|
296
|
-
return
|
|
303
|
+
return context.clicksStart
|
|
297
304
|
},
|
|
298
305
|
set(v) {
|
|
299
306
|
if (currentSlideNo.value === thisNo)
|
|
300
|
-
queryClicksRaw.value =
|
|
307
|
+
queryClicksRaw.value = clamp(v, context.clicksStart, context.total).toString()
|
|
301
308
|
},
|
|
302
309
|
}),
|
|
303
|
-
route?.meta?.
|
|
310
|
+
route?.meta.slide?.frontmatter.clicksStart ?? 0,
|
|
311
|
+
route?.meta.clicks,
|
|
304
312
|
)
|
|
305
313
|
|
|
306
314
|
// On slide mounted, make sure the query is not greater than the total
|
|
307
315
|
context.onMounted = () => {
|
|
308
|
-
if (
|
|
309
|
-
queryClicksRaw.value =
|
|
316
|
+
if (currentSlideNo.value === thisNo)
|
|
317
|
+
queryClicksRaw.value = clamp(+queryClicksRaw.value, context.clicksStart, context.total).toString()
|
|
310
318
|
}
|
|
311
319
|
|
|
312
320
|
if (route?.meta)
|
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'
|
|
@@ -31,6 +32,7 @@ export const TRUST_ORIGINS = [
|
|
|
31
32
|
|
|
32
33
|
export const FRONTMATTER_FIELDS = [
|
|
33
34
|
'clicks',
|
|
35
|
+
'clicksStart',
|
|
34
36
|
'disabled',
|
|
35
37
|
'hide',
|
|
36
38
|
'hideInToc',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ClicksContext } from '@slidev/types'
|
|
3
|
+
import { clamp, range } from '@antfu/utils'
|
|
3
4
|
import { computed } from 'vue'
|
|
4
5
|
import { CLICKS_MAX } from '../constants'
|
|
5
6
|
|
|
@@ -8,6 +9,8 @@ const props = defineProps<{
|
|
|
8
9
|
}>()
|
|
9
10
|
|
|
10
11
|
const total = computed(() => props.clicksContext.total)
|
|
12
|
+
const start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))
|
|
13
|
+
const length = computed(() => total.value - start.value + 1)
|
|
11
14
|
const current = computed({
|
|
12
15
|
get() {
|
|
13
16
|
return props.clicksContext.current > total.value ? -1 : props.clicksContext.current
|
|
@@ -18,7 +21,7 @@ const current = computed({
|
|
|
18
21
|
},
|
|
19
22
|
})
|
|
20
23
|
|
|
21
|
-
const
|
|
24
|
+
const clicksRange = computed(() => range(start.value, total.value + 1))
|
|
22
25
|
|
|
23
26
|
function onMousedown() {
|
|
24
27
|
if (current.value < 0 || current.value > total.value)
|
|
@@ -29,8 +32,8 @@ function onMousedown() {
|
|
|
29
32
|
<template>
|
|
30
33
|
<div
|
|
31
34
|
class="flex gap-1 items-center select-none"
|
|
32
|
-
:title="`Clicks in this slide: ${
|
|
33
|
-
:class="
|
|
35
|
+
:title="`Clicks in this slide: ${length}`"
|
|
36
|
+
:class="length ? '' : 'op50'"
|
|
34
37
|
>
|
|
35
38
|
<div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
|
|
36
39
|
<carbon:cursor-1 text-sm op50 />
|
|
@@ -46,13 +49,13 @@ function onMousedown() {
|
|
|
46
49
|
@dblclick="current = clicksContext.total"
|
|
47
50
|
>
|
|
48
51
|
<div
|
|
49
|
-
v-for="i of
|
|
52
|
+
v-for="i of clicksRange" :key="i"
|
|
50
53
|
border="y main" of-hidden relative
|
|
51
54
|
:class="[
|
|
52
55
|
i === 0 ? 'rounded-l border-l' : '',
|
|
53
56
|
i === total ? 'rounded-r border-r' : '',
|
|
54
57
|
]"
|
|
55
|
-
:style="{ width:
|
|
58
|
+
:style="{ width: length > 0 ? `${1 / length * 100}%` : '100%' }"
|
|
56
59
|
>
|
|
57
60
|
<div absolute inset-0 :class="i <= current ? 'bg-primary op15' : ''" />
|
|
58
61
|
<div
|
|
@@ -69,8 +72,8 @@ function onMousedown() {
|
|
|
69
72
|
<input
|
|
70
73
|
v-model="current"
|
|
71
74
|
class="range" absolute inset-0
|
|
72
|
-
type="range" :min="
|
|
73
|
-
:style="{ '--thumb-width': `${1 / (
|
|
75
|
+
type="range" :min="start" :max="total" :step="1" z-10 op0
|
|
76
|
+
:style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
|
|
74
77
|
@mousedown="onMousedown"
|
|
75
78
|
@focus="event => (event.currentTarget as HTMLElement)?.blur()"
|
|
76
79
|
>
|
package/internals/Goto.vue
CHANGED
|
@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
3
3
|
import Fuse from 'fuse.js'
|
|
4
4
|
import { activeElement, showGotoDialog } from '../state'
|
|
5
5
|
import { useNav } from '../composables/useNav'
|
|
6
|
-
import
|
|
6
|
+
import TitleRenderer from '#slidev/title-renderer'
|
|
7
7
|
|
|
8
8
|
const container = ref<HTMLDivElement>()
|
|
9
9
|
const input = ref<HTMLInputElement>()
|
|
@@ -161,7 +161,7 @@ watch(activeElement, () => {
|
|
|
161
161
|
<div w-4 text-right op50 text-sm>
|
|
162
162
|
{{ item.no }}
|
|
163
163
|
</div>
|
|
164
|
-
<
|
|
164
|
+
<TitleRenderer :no="item.no" />
|
|
165
165
|
</li>
|
|
166
166
|
</ul>
|
|
167
167
|
</div>
|
|
@@ -8,8 +8,12 @@ import { sharedState } from '../state/shared'
|
|
|
8
8
|
class="absolute top-0 left-0 right-0 bottom-0 pointer-events-none text-xl"
|
|
9
9
|
>
|
|
10
10
|
<ph-cursor-fill
|
|
11
|
-
class="absolute"
|
|
12
|
-
:style="{
|
|
11
|
+
class="absolute stroke-white dark:stroke-black"
|
|
12
|
+
:style="{
|
|
13
|
+
left: `${sharedState.cursor.x}%`,
|
|
14
|
+
top: `${sharedState.cursor.y}%`,
|
|
15
|
+
strokeWidth: 16,
|
|
16
|
+
}"
|
|
13
17
|
/>
|
|
14
18
|
</div>
|
|
15
19
|
</template>
|
package/internals/PrintSlide.vue
CHANGED
|
@@ -16,10 +16,14 @@ const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MA
|
|
|
16
16
|
:nav="useFixedNav(route, clicks0)"
|
|
17
17
|
/>
|
|
18
18
|
<template v-if="isPrintWithClicks">
|
|
19
|
+
<!--
|
|
20
|
+
clicks0.total can be any number >=0 when rendering.
|
|
21
|
+
So total-clicksStart can be negative in intermediate states.
|
|
22
|
+
-->
|
|
19
23
|
<PrintSlideClick
|
|
20
|
-
v-for="i
|
|
24
|
+
v-for="i in Math.max(0, clicks0.total - clicks0.clicksStart)"
|
|
21
25
|
:key="i"
|
|
22
|
-
:nav="useFixedNav(route, createFixedClicks(route, i))"
|
|
26
|
+
:nav="useFixedNav(route, createFixedClicks(route, i + clicks0.clicksStart))"
|
|
23
27
|
/>
|
|
24
28
|
</template>
|
|
25
29
|
</template>
|
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.
|
|
36
|
-
"@shikijs/vitepress-twoslash": "^1.
|
|
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.
|
|
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",
|
|
@@ -47,23 +47,23 @@
|
|
|
47
47
|
"floating-vue": "^5.2.2",
|
|
48
48
|
"fuse.js": "^7.0.0",
|
|
49
49
|
"js-yaml": "^4.1.0",
|
|
50
|
-
"katex": "^0.16.
|
|
50
|
+
"katex": "^0.16.10",
|
|
51
51
|
"lz-string": "^1.5.0",
|
|
52
52
|
"mermaid": "^10.9.0",
|
|
53
53
|
"monaco-editor": "^0.47.0",
|
|
54
54
|
"prettier": "^3.2.5",
|
|
55
55
|
"recordrtc": "^5.6.2",
|
|
56
|
-
"shiki": "^1.
|
|
57
|
-
"shiki-magic-move": "^0.3.
|
|
58
|
-
"typescript": "^5.4.
|
|
59
|
-
"unocss": "^0.58.
|
|
56
|
+
"shiki": "^1.2.3",
|
|
57
|
+
"shiki-magic-move": "^0.3.5",
|
|
58
|
+
"typescript": "^5.4.3",
|
|
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/
|
|
64
|
-
"@slidev/
|
|
63
|
+
"@slidev/parser": "0.48.9",
|
|
64
|
+
"@slidev/types": "0.48.9"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"vite": "^5.
|
|
67
|
+
"vite": "^5.2.7"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/pages/print.vue
CHANGED
|
@@ -28,8 +28,8 @@ onMounted(() => {
|
|
|
28
28
|
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
|
|
29
29
|
:width="windowSize.width.value"
|
|
30
30
|
/>
|
|
31
|
+
<div id="twoslash-container" />
|
|
31
32
|
</div>
|
|
32
|
-
<div id="twoslash-container" />
|
|
33
33
|
</template>
|
|
34
34
|
|
|
35
35
|
<style lang="postcss">
|
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
|
|
@@ -144,9 +158,13 @@ export async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
|
|
|
144
158
|
return textRep
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
// The reflect-metadata runtime is available, so allow that to go through
|
|
148
161
|
function sanitizeJS(code: string) {
|
|
149
|
-
|
|
162
|
+
// The reflect-metadata runtime is available, so allow that to go through
|
|
163
|
+
code = code.replace(`import "reflect-metadata"`, '').replace(`require("reflect-metadata")`, '')
|
|
164
|
+
// Transpiled typescript sometimes contains an empty export, remove it.
|
|
165
|
+
code = code.replace('export {};', '')
|
|
166
|
+
|
|
167
|
+
return code
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
return allLogs
|
|
@@ -155,10 +173,80 @@ export async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
|
|
|
155
173
|
let tsModule: typeof import('typescript') | undefined
|
|
156
174
|
|
|
157
175
|
export async function runTypeScript(code: string, context: CodeRunnerContext) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
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
|
+
}
|