@slidev/client 0.48.0-beta.20 → 0.48.0-beta.22
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/CodeBlockWrapper.vue +23 -25
- package/builtin/ShikiMagicMove.vue +63 -13
- package/builtin/SlidevVideo.vue +1 -1
- package/builtin/Toc.vue +1 -1
- package/builtin/TocList.vue +1 -1
- package/composables/useClicks.ts +16 -15
- package/composables/useContext.ts +4 -9
- package/composables/useNav.ts +182 -44
- package/composables/useSwipeControls.ts +40 -0
- package/composables/useTocTree.ts +63 -0
- package/constants.ts +2 -3
- package/context.ts +3 -6
- package/env.ts +4 -4
- package/internals/Goto.vue +2 -2
- package/internals/NavControls.vue +7 -5
- package/internals/NoteStatic.vue +1 -1
- package/internals/PrintContainer.vue +8 -7
- package/internals/PrintSlide.vue +6 -13
- package/internals/PrintSlideClick.vue +11 -13
- package/internals/QuickOverview.vue +10 -10
- package/internals/SideEditor.vue +3 -3
- package/internals/SlideContainer.vue +6 -6
- package/internals/SlideLoading.vue +19 -0
- package/internals/SlideWrapper.ts +12 -12
- package/internals/SlidesShow.vue +13 -10
- package/layouts/error.vue +5 -0
- package/logic/drawings.ts +7 -7
- package/logic/nav-state.ts +20 -0
- package/logic/nav.ts +49 -259
- package/logic/note.ts +2 -2
- package/logic/overview.ts +2 -2
- package/logic/route.ts +10 -1
- package/logic/slides.ts +19 -0
- package/logic/transition.ts +50 -0
- package/logic/utils.ts +24 -0
- package/modules/context.ts +7 -12
- package/package.json +8 -7
- package/pages/notes.vue +2 -3
- package/pages/overview.vue +19 -21
- package/pages/play.vue +2 -1
- package/pages/presenter/print.vue +2 -2
- package/pages/presenter.vue +15 -14
- package/routes.ts +6 -14
- package/setup/root.ts +6 -7
- package/setup/shortcuts.ts +2 -1
- package/shim-vue.d.ts +3 -0
- package/state/index.ts +1 -1
- package/styles/code.css +2 -2
- package/utils.ts +15 -2
|
@@ -12,13 +12,12 @@ Learn more: https://sli.dev/guide/syntax.html#line-highlighting
|
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
|
-
import { parseRangeString } from '@slidev/parser/core'
|
|
16
15
|
import { useClipboard } from '@vueuse/core'
|
|
17
16
|
import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
|
18
17
|
import type { PropType } from 'vue'
|
|
19
18
|
import { configs } from '../env'
|
|
20
|
-
import { makeId } from '../logic/utils'
|
|
21
|
-
import { CLASS_VCLICK_HIDDEN
|
|
19
|
+
import { makeId, updateCodeHighlightRange } from '../logic/utils'
|
|
20
|
+
import { CLASS_VCLICK_HIDDEN } from '../constants'
|
|
22
21
|
import { useSlideContext } from '../context'
|
|
23
22
|
|
|
24
23
|
const props = defineProps({
|
|
@@ -87,28 +86,27 @@ onMounted(() => {
|
|
|
87
86
|
if (hide)
|
|
88
87
|
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
89
88
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
89
|
+
const pre = el.value.querySelector('.shiki')!
|
|
90
|
+
const lines = Array.from(pre.querySelectorAll('code > .line'))
|
|
91
|
+
const linesCount = lines.length
|
|
92
|
+
|
|
93
|
+
updateCodeHighlightRange(
|
|
94
|
+
rangeStr,
|
|
95
|
+
linesCount,
|
|
96
|
+
props.startLine,
|
|
97
|
+
no => [lines[no]],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Scroll to the highlighted line if `maxHeight` is set
|
|
101
|
+
if (props.maxHeight) {
|
|
102
|
+
const highlightedEls = Array.from(pre.querySelectorAll('.line.highlighted')) as HTMLElement[]
|
|
103
|
+
const height = highlightedEls.reduce((acc, el) => el.offsetHeight + acc, 0)
|
|
104
|
+
if (height > el.value.offsetHeight) {
|
|
105
|
+
highlightedEls[0].scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
106
|
+
}
|
|
107
|
+
else if (highlightedEls.length > 0) {
|
|
108
|
+
const middleEl = highlightedEls[Math.round((highlightedEls.length - 1) / 2)]
|
|
109
|
+
middleEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
112
110
|
}
|
|
113
111
|
}
|
|
114
112
|
})
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
|
|
3
3
|
import type { KeyedTokensInfo } from 'shiki-magic-move/types'
|
|
4
|
-
import { onMounted, onUnmounted, ref,
|
|
4
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
5
5
|
import lz from 'lz-string'
|
|
6
6
|
import { useSlideContext } from '../context'
|
|
7
|
-
import { makeId } from '../logic/utils'
|
|
7
|
+
import { makeId, updateCodeHighlightRange } from '../logic/utils'
|
|
8
8
|
|
|
9
9
|
import 'shiki-magic-move/style.css'
|
|
10
10
|
|
|
11
11
|
const props = defineProps<{
|
|
12
|
-
stepsLz: string
|
|
13
12
|
at?: string | number
|
|
13
|
+
stepsLz: string
|
|
14
|
+
stepRanges: string[][]
|
|
14
15
|
}>()
|
|
15
16
|
|
|
16
17
|
const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
|
|
17
18
|
const { $clicksContext: clicks, $scale: scale } = useSlideContext()
|
|
18
19
|
const id = makeId()
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
const stepIndex = ref(0)
|
|
22
|
+
const container = ref<HTMLElement>()
|
|
23
|
+
|
|
24
|
+
// Normalized the ranges, to at least have one range
|
|
25
|
+
const ranges = computed(() => props.stepRanges.map(i => i.length ? i : ['all']))
|
|
20
26
|
|
|
21
27
|
onUnmounted(() => {
|
|
22
28
|
clicks!.unregister(id)
|
|
@@ -26,24 +32,68 @@ onMounted(() => {
|
|
|
26
32
|
if (!clicks || clicks.disabled)
|
|
27
33
|
return
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
if (ranges.value.length !== steps.length)
|
|
36
|
+
throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')
|
|
37
|
+
|
|
38
|
+
const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)
|
|
39
|
+
const { start, end, delta } = clicks.resolve(props.at ?? '+1', clickCounts - 1)
|
|
30
40
|
clicks.register(id, { max: end, delta })
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
watch(
|
|
43
|
+
() => clicks.current,
|
|
44
|
+
() => {
|
|
45
|
+
// Calculate the step and rangeStr based on the current click count
|
|
46
|
+
const clickCount = clicks.current - start
|
|
47
|
+
let step = steps.length - 1
|
|
48
|
+
let _currentClickSum = 0
|
|
49
|
+
let rangeStr = 'all'
|
|
50
|
+
for (let i = 0; i < ranges.value.length; i++) {
|
|
51
|
+
const current = ranges.value[i]
|
|
52
|
+
if (clickCount < _currentClickSum + current.length - 1) {
|
|
53
|
+
step = i
|
|
54
|
+
rangeStr = current[clickCount - _currentClickSum + 1]
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
_currentClickSum += current.length || 1
|
|
58
|
+
}
|
|
59
|
+
stepIndex.value = step
|
|
60
|
+
|
|
61
|
+
const pre = container.value?.querySelector('.shiki') as HTMLElement
|
|
62
|
+
if (!pre)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
const children = (Array.from(pre.children) as HTMLElement[])
|
|
66
|
+
.slice(1) // Remove the first anchor
|
|
67
|
+
.filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
|
|
68
|
+
|
|
69
|
+
// Group to lines between `<br>`
|
|
70
|
+
const lines = children.reduce((acc, el) => {
|
|
71
|
+
if (el.tagName === 'BR')
|
|
72
|
+
acc.push([])
|
|
73
|
+
else
|
|
74
|
+
acc[acc.length - 1].push(el)
|
|
75
|
+
return acc
|
|
76
|
+
}, [[]] as HTMLElement[][])
|
|
77
|
+
|
|
78
|
+
// Update highlight range
|
|
79
|
+
updateCodeHighlightRange(
|
|
80
|
+
rangeStr,
|
|
81
|
+
lines.length,
|
|
82
|
+
1,
|
|
83
|
+
no => lines[no],
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
{ immediate: true },
|
|
87
|
+
)
|
|
38
88
|
})
|
|
39
89
|
</script>
|
|
40
90
|
|
|
41
91
|
<template>
|
|
42
|
-
<div class="slidev-code-wrapper slidev-code-magic-move">
|
|
92
|
+
<div ref="container" class="slidev-code-wrapper slidev-code-magic-move relative">
|
|
43
93
|
<ShikiMagicMovePrecompiled
|
|
44
94
|
class="slidev-code relative shiki overflow-visible"
|
|
45
95
|
:steps="steps"
|
|
46
|
-
:step="
|
|
96
|
+
:step="stepIndex"
|
|
47
97
|
:options="{ globalScale: scale }"
|
|
48
98
|
/>
|
|
49
99
|
</div>
|
package/builtin/SlidevVideo.vue
CHANGED
|
@@ -22,7 +22,7 @@ const ended = ref(false)
|
|
|
22
22
|
const matchRoute = computed(() => {
|
|
23
23
|
if (!video.value || currentContext?.value !== 'slide')
|
|
24
24
|
return false
|
|
25
|
-
return route === $slidev?.nav.
|
|
25
|
+
return route && route.no === $slidev?.nav.currentSlideNo
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
const matchClick = computed(() => {
|
package/builtin/Toc.vue
CHANGED
package/builtin/TocList.vue
CHANGED
|
@@ -48,7 +48,7 @@ const styles = computed(() => {
|
|
|
48
48
|
:class="[{ 'slidev-toc-item-active': item.active }, { 'slidev-toc-item-parent-active': item.activeParent }]"
|
|
49
49
|
>
|
|
50
50
|
<Link :to="item.path">
|
|
51
|
-
<Titles :no="item.
|
|
51
|
+
<Titles :no="item.no" />
|
|
52
52
|
</Link>
|
|
53
53
|
<TocList
|
|
54
54
|
v-if="item.children.length > 0"
|
package/composables/useClicks.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { sum } from '@antfu/utils'
|
|
2
|
-
import type { ClicksContext } from '@slidev/types'
|
|
2
|
+
import type { ClicksContext, SlideRoute } from '@slidev/types'
|
|
3
3
|
import type { Ref } from 'vue'
|
|
4
4
|
import { computed, ref, shallowReactive } from 'vue'
|
|
5
|
-
import
|
|
6
|
-
import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
|
|
5
|
+
import { currentSlideNo, isPrintMode, isPrintWithClicks } from '../logic/nav'
|
|
7
6
|
import { normalizeAtProp } from '../logic/utils'
|
|
8
7
|
import { CLICKS_MAX } from '../constants'
|
|
8
|
+
import { routeForceRefresh, useRouteQuery } from '../logic/route'
|
|
9
9
|
|
|
10
10
|
function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): ClicksContext {
|
|
11
11
|
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
@@ -63,27 +63,28 @@ function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): C
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
const queryClicksRaw = useRouteQuery('clicks', '0')
|
|
67
|
+
|
|
68
|
+
export function usePrimaryClicks(route: SlideRoute): ClicksContext {
|
|
67
69
|
if (route?.meta?.__clicksContext)
|
|
68
70
|
return route.meta.__clicksContext
|
|
69
|
-
const
|
|
71
|
+
const thisNo = route.no
|
|
70
72
|
const current = computed({
|
|
71
73
|
get() {
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
return
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
77
|
-
else if (
|
|
74
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
75
|
+
if (context.disabled)
|
|
76
|
+
return CLICKS_MAX
|
|
77
|
+
if (currentSlideNo.value === thisNo)
|
|
78
|
+
return +(queryClicksRaw.value || 0) || 0
|
|
79
|
+
else if (currentSlideNo.value > thisNo)
|
|
78
80
|
return CLICKS_MAX
|
|
79
81
|
else
|
|
80
82
|
return 0
|
|
81
83
|
},
|
|
82
84
|
set(v) {
|
|
83
|
-
|
|
84
|
-
if (currentPath === thisPath) {
|
|
85
|
+
if (currentSlideNo.value === thisNo) {
|
|
85
86
|
// eslint-disable-next-line ts/no-use-before-define
|
|
86
|
-
|
|
87
|
+
queryClicksRaw.value = Math.min(v, context.total).toString()
|
|
87
88
|
}
|
|
88
89
|
},
|
|
89
90
|
})
|
|
@@ -96,6 +97,6 @@ export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksConte
|
|
|
96
97
|
return context
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
export function useFixedClicks(route?:
|
|
100
|
+
export function useFixedClicks(route?: SlideRoute | undefined, currentInit = 0): ClicksContext {
|
|
100
101
|
return useClicksContextBase(ref(currentInit), route?.meta?.clicks)
|
|
101
102
|
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import type { ComputedRef } from 'vue'
|
|
2
1
|
import { computed } from 'vue'
|
|
3
|
-
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
4
|
-
import type { SlidevContext } from '../modules/context'
|
|
5
2
|
import { configs } from '../env'
|
|
6
|
-
import {
|
|
3
|
+
import type { SlidevContext } from '../modules/context'
|
|
4
|
+
import * as nav from '../logic/nav'
|
|
7
5
|
|
|
8
|
-
export function useContext(
|
|
9
|
-
route: ComputedRef<RouteLocationNormalizedLoaded>,
|
|
10
|
-
): SlidevContext {
|
|
11
|
-
const nav = useNav(route)
|
|
6
|
+
export function useContext(): SlidevContext {
|
|
12
7
|
return {
|
|
13
|
-
nav,
|
|
8
|
+
nav: { ...nav }, // Convert the module to a plain object
|
|
14
9
|
configs,
|
|
15
10
|
themeConfigs: computed(() => configs.themeConfig),
|
|
16
11
|
}
|
package/composables/useNav.ts
CHANGED
|
@@ -1,55 +1,193 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
import type { ClicksContext, SlideRoute, TocItem } from '@slidev/types'
|
|
2
|
+
import type { ComputedRef, Ref, TransitionGroupProps } from 'vue'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
import type { Router } from 'vue-router'
|
|
5
|
+
import { getCurrentTransition } from '../logic/transition'
|
|
6
|
+
import { getSlidePath } from '../logic/slides'
|
|
7
|
+
import { useTocTree } from './useTocTree'
|
|
8
|
+
import { skipTransition } from './hmr'
|
|
9
|
+
import { slides } from '#slidev/slides'
|
|
10
|
+
|
|
11
|
+
export interface SlidevContextNav {
|
|
12
|
+
slides: Ref<SlideRoute[]>
|
|
13
|
+
total: ComputedRef<number>
|
|
14
|
+
|
|
15
|
+
currentPath: ComputedRef<string>
|
|
16
|
+
currentPage: ComputedRef<number>
|
|
17
|
+
currentSlideNo: ComputedRef<number>
|
|
18
|
+
currentSlideRoute: ComputedRef<SlideRoute>
|
|
19
|
+
currentTransition: ComputedRef<TransitionGroupProps | undefined>
|
|
20
|
+
currentLayout: ComputedRef<string>
|
|
21
|
+
|
|
22
|
+
nextRoute: ComputedRef<SlideRoute>
|
|
23
|
+
prevRoute: ComputedRef<SlideRoute>
|
|
24
|
+
hasNext: ComputedRef<boolean>
|
|
25
|
+
hasPrev: ComputedRef<boolean>
|
|
26
|
+
|
|
27
|
+
clicksContext: ComputedRef<ClicksContext>
|
|
28
|
+
clicks: ComputedRef<number>
|
|
29
|
+
clicksTotal: ComputedRef<number>
|
|
30
|
+
|
|
31
|
+
/** The table of content tree */
|
|
32
|
+
tocTree: ComputedRef<TocItem[]>
|
|
33
|
+
/** The direction of the navigation, 1 for forward, -1 for backward */
|
|
34
|
+
navDirection: Ref<number>
|
|
35
|
+
/** The direction of the clicks, 1 for forward, -1 for backward */
|
|
36
|
+
clicksDirection: Ref<number>
|
|
37
|
+
/** Utility function for open file in editor, only avaible in dev mode */
|
|
38
|
+
openInEditor: (url?: string) => Promise<boolean>
|
|
39
|
+
|
|
40
|
+
/** Go to next click */
|
|
41
|
+
next: () => Promise<void>
|
|
42
|
+
/** Go to previous click */
|
|
43
|
+
prev: () => Promise<void>
|
|
44
|
+
/** Go to next slide */
|
|
45
|
+
nextSlide: () => Promise<void>
|
|
46
|
+
/** Go to previous slide */
|
|
47
|
+
prevSlide: (lastClicks?: boolean) => Promise<void>
|
|
48
|
+
/** Go to slide */
|
|
49
|
+
go: (page: number | string, clicks?: number) => Promise<void>
|
|
50
|
+
/** Go to the first slide */
|
|
51
|
+
goFirst: () => Promise<void>
|
|
52
|
+
/** Go to the last slide */
|
|
53
|
+
goLast: () => Promise<void>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useNavBase(
|
|
57
|
+
currentSlideRoute: ComputedRef<SlideRoute>,
|
|
58
|
+
clicksContext: ComputedRef<ClicksContext>,
|
|
59
|
+
queryClicks: Ref<number> = ref(0),
|
|
60
|
+
router?: Router,
|
|
61
|
+
): SlidevContextNav {
|
|
62
|
+
const total = computed(() => slides.value.length)
|
|
63
|
+
|
|
64
|
+
const navDirection = ref(0)
|
|
65
|
+
const clicksDirection = ref(0)
|
|
66
|
+
|
|
67
|
+
const currentPath = computed(() => getSlidePath(currentSlideRoute.value))
|
|
68
|
+
const currentSlideNo = computed(() => currentSlideRoute.value.no)
|
|
69
|
+
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))
|
|
70
|
+
|
|
71
|
+
const clicks = computed(() => clicksContext.value.current)
|
|
72
|
+
const clicksTotal = computed(() => clicksContext.value.total)
|
|
73
|
+
const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])
|
|
74
|
+
const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])
|
|
75
|
+
const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)
|
|
76
|
+
const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)
|
|
77
|
+
|
|
78
|
+
const currentTransition = computed(() => getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
|
|
79
|
+
|
|
80
|
+
watch(currentSlideRoute, (next, prev) => {
|
|
81
|
+
navDirection.value = next.no - prev.no
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
async function openInEditor(url?: string) {
|
|
85
|
+
if (!__DEV__)
|
|
86
|
+
return false
|
|
87
|
+
if (url == null) {
|
|
88
|
+
const slide = currentSlideRoute.value?.meta?.slide
|
|
89
|
+
if (!slide)
|
|
90
|
+
return false
|
|
91
|
+
url = `${slide.filepath}:${slide.start}`
|
|
92
|
+
}
|
|
93
|
+
await fetch(`/__open-in-editor?file=${encodeURIComponent(url)}`)
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const tocTree = useTocTree(slides)
|
|
98
|
+
|
|
99
|
+
async function next() {
|
|
100
|
+
clicksDirection.value = 1
|
|
101
|
+
if (clicksTotal.value <= queryClicks.value)
|
|
102
|
+
await nextSlide()
|
|
103
|
+
else
|
|
104
|
+
queryClicks.value += 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function prev() {
|
|
108
|
+
clicksDirection.value = -1
|
|
109
|
+
if (queryClicks.value <= 0)
|
|
110
|
+
await prevSlide()
|
|
111
|
+
else
|
|
112
|
+
queryClicks.value -= 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function nextSlide() {
|
|
116
|
+
clicksDirection.value = 1
|
|
117
|
+
if (currentSlideNo.value < slides.value.length)
|
|
118
|
+
await go(currentSlideNo.value + 1)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function prevSlide(lastClicks = true) {
|
|
122
|
+
clicksDirection.value = -1
|
|
123
|
+
const next = Math.max(1, currentSlideNo.value - 1)
|
|
124
|
+
await go(next)
|
|
125
|
+
if (lastClicks && clicksTotal.value) {
|
|
126
|
+
router?.replace({
|
|
127
|
+
query: { ...router.currentRoute.value.query, clicks: clicksTotal.value },
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function goFirst() {
|
|
133
|
+
return go(1)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function goLast() {
|
|
137
|
+
return go(total.value)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function go(page: number | string, clicks?: number) {
|
|
141
|
+
skipTransition.value = false
|
|
142
|
+
await router?.push({
|
|
143
|
+
path: getSlidePath(page),
|
|
144
|
+
query: { ...router.currentRoute.value.query, clicks },
|
|
145
|
+
})
|
|
146
|
+
}
|
|
29
147
|
|
|
30
148
|
return {
|
|
31
|
-
|
|
32
|
-
route,
|
|
33
|
-
path,
|
|
149
|
+
slides,
|
|
34
150
|
total,
|
|
35
|
-
clicksContext,
|
|
36
|
-
clicks,
|
|
37
|
-
clicksTotal,
|
|
38
|
-
currentPage,
|
|
39
151
|
currentPath,
|
|
40
|
-
|
|
41
|
-
|
|
152
|
+
currentSlideNo,
|
|
153
|
+
currentPage: currentSlideNo,
|
|
154
|
+
currentSlideRoute,
|
|
42
155
|
currentLayout,
|
|
156
|
+
currentTransition,
|
|
157
|
+
clicksDirection,
|
|
43
158
|
nextRoute,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
159
|
+
prevRoute,
|
|
160
|
+
clicksContext,
|
|
161
|
+
clicks,
|
|
162
|
+
clicksTotal,
|
|
163
|
+
hasNext,
|
|
164
|
+
hasPrev,
|
|
165
|
+
tocTree,
|
|
166
|
+
navDirection,
|
|
51
167
|
openInEditor,
|
|
168
|
+
next,
|
|
52
169
|
prev,
|
|
170
|
+
go,
|
|
171
|
+
goLast,
|
|
172
|
+
goFirst,
|
|
173
|
+
nextSlide,
|
|
53
174
|
prevSlide,
|
|
54
175
|
}
|
|
55
176
|
}
|
|
177
|
+
|
|
178
|
+
export function useFixedNav(
|
|
179
|
+
currentSlideRoute: SlideRoute,
|
|
180
|
+
clicksContext: ClicksContext,
|
|
181
|
+
): SlidevContextNav {
|
|
182
|
+
const noop = async () => { }
|
|
183
|
+
return {
|
|
184
|
+
...useNavBase(computed(() => currentSlideRoute), computed(() => clicksContext)),
|
|
185
|
+
next: noop,
|
|
186
|
+
prev: noop,
|
|
187
|
+
nextSlide: noop,
|
|
188
|
+
prevSlide: noop,
|
|
189
|
+
goFirst: noop,
|
|
190
|
+
goLast: noop,
|
|
191
|
+
go: noop,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import { timestamp, usePointerSwipe } from '@vueuse/core'
|
|
4
|
+
import { isDrawing } from '../logic/drawings'
|
|
5
|
+
import { next, nextSlide, prev, prevSlide } from '../logic/nav'
|
|
6
|
+
|
|
7
|
+
export function useSwipeControls(root: Ref<HTMLElement | undefined>) {
|
|
8
|
+
const swipeBegin = ref(0)
|
|
9
|
+
const { direction, distanceX, distanceY } = usePointerSwipe(root, {
|
|
10
|
+
pointerTypes: ['touch'],
|
|
11
|
+
onSwipeStart() {
|
|
12
|
+
if (isDrawing.value)
|
|
13
|
+
return
|
|
14
|
+
swipeBegin.value = timestamp()
|
|
15
|
+
},
|
|
16
|
+
onSwipeEnd() {
|
|
17
|
+
if (!swipeBegin.value)
|
|
18
|
+
return
|
|
19
|
+
if (isDrawing.value)
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
const x = Math.abs(distanceX.value)
|
|
23
|
+
const y = Math.abs(distanceY.value)
|
|
24
|
+
if (x / window.innerWidth > 0.3 || x > 75) {
|
|
25
|
+
if (direction.value === 'left')
|
|
26
|
+
next()
|
|
27
|
+
|
|
28
|
+
else
|
|
29
|
+
prev()
|
|
30
|
+
}
|
|
31
|
+
else if (y / window.innerHeight > 0.4 || y > 200) {
|
|
32
|
+
if (direction.value === 'down')
|
|
33
|
+
prevSlide()
|
|
34
|
+
|
|
35
|
+
else
|
|
36
|
+
nextSlide()
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SlideRoute, TocItem } from '@slidev/types'
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { currentSlideNo, currentSlideRoute, getSlidePath } from '../logic/nav'
|
|
5
|
+
|
|
6
|
+
function addToTree(tree: TocItem[], route: SlideRoute, level = 1) {
|
|
7
|
+
const titleLevel = route.meta?.slide?.level
|
|
8
|
+
if (titleLevel && titleLevel > level && tree.length > 0) {
|
|
9
|
+
addToTree(tree[tree.length - 1].children, route, level + 1)
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
tree.push({
|
|
13
|
+
no: route.no,
|
|
14
|
+
children: [],
|
|
15
|
+
level,
|
|
16
|
+
path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no),
|
|
17
|
+
hideInToc: Boolean(route.meta?.slide?.frontmatter?.hideInToc),
|
|
18
|
+
title: route.meta?.slide?.title,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getTreeWithActiveStatuses(
|
|
24
|
+
tree: TocItem[],
|
|
25
|
+
currentRoute?: SlideRoute,
|
|
26
|
+
hasActiveParent = false,
|
|
27
|
+
parent?: TocItem,
|
|
28
|
+
): TocItem[] {
|
|
29
|
+
return tree.map((item: TocItem) => {
|
|
30
|
+
const clone = {
|
|
31
|
+
...item,
|
|
32
|
+
active: item.no === currentSlideNo.value,
|
|
33
|
+
hasActiveParent,
|
|
34
|
+
}
|
|
35
|
+
if (clone.children.length > 0)
|
|
36
|
+
clone.children = getTreeWithActiveStatuses(clone.children, currentRoute, clone.active || clone.hasActiveParent, clone)
|
|
37
|
+
if (parent && (clone.active || clone.activeParent))
|
|
38
|
+
parent.activeParent = true
|
|
39
|
+
return clone
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
44
|
+
return tree
|
|
45
|
+
.filter((item: TocItem) => !item.hideInToc)
|
|
46
|
+
.map((item: TocItem) => ({
|
|
47
|
+
...item,
|
|
48
|
+
children: filterTree(item.children, level + 1),
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useTocTree(slides: Ref<SlideRoute[]>): ComputedRef<TocItem[]> {
|
|
53
|
+
const rawTree = computed(() => slides.value
|
|
54
|
+
.filter((route: SlideRoute) => route.meta?.slide?.title)
|
|
55
|
+
.reduce((acc: TocItem[], route: SlideRoute) => {
|
|
56
|
+
addToTree(acc, route)
|
|
57
|
+
return acc
|
|
58
|
+
}, []))
|
|
59
|
+
|
|
60
|
+
const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value, currentSlideRoute.value))
|
|
61
|
+
|
|
62
|
+
return computed(() => filterTree(treeWithActiveStatuses.value))
|
|
63
|
+
}
|
package/constants.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey, Ref, UnwrapNestedRefs } from 'vue'
|
|
2
|
-
import type {
|
|
3
|
-
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
2
|
+
import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
|
|
4
3
|
import type { SlidevContext } from './modules/context'
|
|
5
4
|
|
|
6
5
|
// Here we use string literal instead of symbols to make HMR more stable
|
|
@@ -9,7 +8,7 @@ export const injectionClicksContext = '$$slidev-clicks-context' as unknown as In
|
|
|
9
8
|
export const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>
|
|
10
9
|
export const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>
|
|
11
10
|
export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>
|
|
12
|
-
export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<
|
|
11
|
+
export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<SlideRoute>
|
|
13
12
|
export const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey<Ref<RenderContext>>
|
|
14
13
|
export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
|
|
15
14
|
export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
|