@slidev/client 0.48.0-beta.24 → 0.48.0-beta.26
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/Link.vue +3 -1
- package/builtin/TocList.vue +2 -2
- package/composables/useClicks.ts +13 -50
- package/composables/useDarkMode.ts +9 -0
- package/composables/useDrawings.ts +181 -0
- package/composables/useNav.ts +167 -7
- package/composables/useSwipeControls.ts +5 -2
- package/composables/useTocTree.ts +25 -7
- package/composables/useViewTransition.ts +1 -1
- package/context.ts +5 -0
- package/index.ts +12 -0
- package/internals/CodeRunner.vue +4 -1
- package/internals/DrawingControls.vue +11 -10
- package/internals/DrawingLayer.vue +4 -2
- package/internals/DrawingPreview.vue +4 -2
- package/internals/Goto.vue +4 -2
- package/internals/NavControls.vue +21 -3
- package/internals/PrintContainer.vue +3 -1
- package/internals/PrintSlide.vue +3 -3
- package/internals/QuickOverview.vue +5 -4
- package/internals/SideEditor.vue +4 -2
- package/internals/SlideContainer.vue +3 -1
- package/internals/SlidesShow.vue +16 -4
- package/logic/overview.ts +1 -1
- package/logic/route.ts +6 -9
- package/logic/slides.ts +5 -4
- package/main.ts +1 -16
- package/modules/context.ts +0 -40
- package/package.json +5 -3
- package/pages/notes.vue +3 -1
- package/pages/overview.vue +6 -3
- package/pages/play.vue +6 -3
- package/pages/presenter/print.vue +3 -1
- package/pages/presenter.vue +19 -6
- package/pages/print.vue +3 -1
- package/routes.ts +0 -8
- package/setup/code-runners.ts +6 -11
- package/setup/main.ts +39 -9
- package/setup/mermaid.ts +5 -6
- package/setup/monaco.ts +7 -9
- package/setup/root.ts +56 -11
- package/setup/shortcuts.ts +14 -12
- package/styles/shiki-twoslash.css +1 -1
- package/composables/useContext.ts +0 -12
- package/logic/drawings.ts +0 -161
- package/logic/nav-state.ts +0 -20
- package/logic/nav.ts +0 -71
- package/setup/prettier.ts +0 -43
- /package/{composables → logic}/hmr.ts +0 -0
- /package/{setup → modules}/codemirror.ts +0 -0
package/builtin/Link.vue
CHANGED
|
@@ -8,12 +8,14 @@ Usage:
|
|
|
8
8
|
<Link :to="5" title="Go to slide 5" />
|
|
9
9
|
-->
|
|
10
10
|
<script setup lang="ts">
|
|
11
|
-
import {
|
|
11
|
+
import { useNav } from '../composables/useNav'
|
|
12
12
|
|
|
13
13
|
defineProps<{
|
|
14
14
|
to: number | string
|
|
15
15
|
title?: string
|
|
16
16
|
}>()
|
|
17
|
+
|
|
18
|
+
const { isPrintMode } = useNav()
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
21
|
<template>
|
package/builtin/TocList.vue
CHANGED
|
@@ -10,7 +10,7 @@ Usage:
|
|
|
10
10
|
import { computed } from 'vue'
|
|
11
11
|
import { toArray } from '@antfu/utils'
|
|
12
12
|
import type { TocItem } from '@slidev/types'
|
|
13
|
-
import
|
|
13
|
+
import TitleRenderer from '#slidev/title-renderer'
|
|
14
14
|
|
|
15
15
|
const props = withDefaults(defineProps<{
|
|
16
16
|
level: number
|
|
@@ -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
|
-
<
|
|
51
|
+
<TitleRenderer :no="item.no" />
|
|
52
52
|
</Link>
|
|
53
53
|
<TocList
|
|
54
54
|
v-if="item.children.length > 0"
|
package/composables/useClicks.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { sum } from '@antfu/utils'
|
|
2
2
|
import type { ClicksContext, SlideRoute } from '@slidev/types'
|
|
3
3
|
import type { Ref } from 'vue'
|
|
4
|
-
import {
|
|
5
|
-
import { currentSlideNo, isPrintMode, isPrintWithClicks } from '../logic/nav'
|
|
4
|
+
import { ref, shallowReactive } from 'vue'
|
|
6
5
|
import { normalizeAtProp } from '../logic/utils'
|
|
7
|
-
import {
|
|
8
|
-
import { routeForceRefresh, useRouteQuery } from '../logic/route'
|
|
6
|
+
import { routeForceRefresh } from '../logic/route'
|
|
9
7
|
|
|
10
|
-
function
|
|
8
|
+
export function createClicksContextBase(
|
|
9
|
+
current: Ref<number>,
|
|
10
|
+
clicksOverrides?: number,
|
|
11
|
+
isDisabled?: () => boolean,
|
|
12
|
+
): ClicksContext {
|
|
11
13
|
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
12
14
|
const map: ClicksContext['map'] = shallowReactive(new Map())
|
|
13
15
|
|
|
14
16
|
return {
|
|
15
17
|
get disabled() {
|
|
16
|
-
return
|
|
18
|
+
return isDisabled ? isDisabled() : false
|
|
17
19
|
},
|
|
18
20
|
get current() {
|
|
19
21
|
return current.value
|
|
@@ -63,48 +65,9 @@ function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): C
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const thisNo = route.no
|
|
73
|
-
const current = computed({
|
|
74
|
-
get() {
|
|
75
|
-
// eslint-disable-next-line ts/no-use-before-define
|
|
76
|
-
if (context.disabled)
|
|
77
|
-
return CLICKS_MAX
|
|
78
|
-
if (currentSlideNo.value === thisNo)
|
|
79
|
-
return +(queryClicksRaw.value || 0) || 0
|
|
80
|
-
else if (currentSlideNo.value > thisNo)
|
|
81
|
-
return CLICKS_MAX
|
|
82
|
-
else
|
|
83
|
-
return 0
|
|
84
|
-
},
|
|
85
|
-
set(v) {
|
|
86
|
-
if (currentSlideNo.value === thisNo) {
|
|
87
|
-
// eslint-disable-next-line ts/no-use-before-define
|
|
88
|
-
queryClicksRaw.value = Math.min(v, context.total).toString()
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
const context = useClicksContextBase(
|
|
93
|
-
current,
|
|
94
|
-
route?.meta?.clicks,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
// On slide mounted, make sure the query is not greater than the total
|
|
98
|
-
context.onMounted = () => {
|
|
99
|
-
if (queryClicksRaw.value)
|
|
100
|
-
queryClicksRaw.value = Math.min(queryClicksRaw.value, context.total)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (route?.meta)
|
|
104
|
-
route.meta.__clicksContext = context
|
|
105
|
-
return context
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function useFixedClicks(route?: SlideRoute | undefined, currentInit = 0): ClicksContext {
|
|
109
|
-
return useClicksContextBase(ref(currentInit), route?.meta?.clicks)
|
|
68
|
+
export function createFixedClicks(
|
|
69
|
+
route?: SlideRoute | undefined,
|
|
70
|
+
currentInit = 0,
|
|
71
|
+
): ClicksContext {
|
|
72
|
+
return createClicksContextBase(ref(currentInit), route?.meta?.clicks)
|
|
110
73
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
|
|
2
|
+
import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'
|
|
3
|
+
import { createDrauu } from 'drauu'
|
|
4
|
+
import { createSharedComposable, toReactive, useLocalStorage } from '@vueuse/core'
|
|
5
|
+
import { drawingState, onPatch, patch } from '../state/drawings'
|
|
6
|
+
import { configs } from '../env'
|
|
7
|
+
import { isInputting } from '../state'
|
|
8
|
+
import { useNav } from './useNav'
|
|
9
|
+
|
|
10
|
+
export const useDrawings = createSharedComposable(() => {
|
|
11
|
+
const { currentSlideNo, isPresenter } = useNav()
|
|
12
|
+
|
|
13
|
+
const brushColors = [
|
|
14
|
+
'#ff595e',
|
|
15
|
+
'#ffca3a',
|
|
16
|
+
'#8ac926',
|
|
17
|
+
'#1982c4',
|
|
18
|
+
'#6a4c93',
|
|
19
|
+
'#ffffff',
|
|
20
|
+
'#000000',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const drawingEnabled = useLocalStorage('slidev-drawing-enabled', false)
|
|
24
|
+
const drawingPinned = useLocalStorage('slidev-drawing-pinned', false)
|
|
25
|
+
const brush = toReactive(useLocalStorage<Brush>('slidev-drawing-brush', {
|
|
26
|
+
color: brushColors[0],
|
|
27
|
+
size: 4,
|
|
28
|
+
mode: 'stylus',
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
const isDrawing = ref(false)
|
|
32
|
+
const canUndo = ref(false)
|
|
33
|
+
const canRedo = ref(false)
|
|
34
|
+
const canClear = ref(false)
|
|
35
|
+
|
|
36
|
+
const _mode = ref<DrawingMode | 'arrow'>('stylus')
|
|
37
|
+
const syncUp = computed(() => configs.drawings.syncAll || isPresenter.value)
|
|
38
|
+
let disableDump = false
|
|
39
|
+
|
|
40
|
+
const drawingMode = computed({
|
|
41
|
+
get() {
|
|
42
|
+
return _mode.value
|
|
43
|
+
},
|
|
44
|
+
set(v: DrawingMode | 'arrow') {
|
|
45
|
+
_mode.value = v
|
|
46
|
+
if (v === 'arrow') {
|
|
47
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
48
|
+
drauu.mode = 'line'
|
|
49
|
+
brush.arrowEnd = true
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
53
|
+
drauu.mode = v
|
|
54
|
+
brush.arrowEnd = false
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const drauuOptions: DrauuOptions = reactive({
|
|
60
|
+
brush,
|
|
61
|
+
acceptsInputTypes: computed(() => (drawingEnabled.value && (!configs.drawings.presenterOnly || isPresenter.value)) ? undefined : ['pen' as const]),
|
|
62
|
+
coordinateTransform: false,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const drauu = markRaw(createDrauu(drauuOptions))
|
|
66
|
+
|
|
67
|
+
function clearDrauu() {
|
|
68
|
+
drauu.clear()
|
|
69
|
+
if (syncUp.value)
|
|
70
|
+
patch(currentSlideNo.value, '')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function updateState() {
|
|
74
|
+
canRedo.value = drauu.canRedo()
|
|
75
|
+
canUndo.value = drauu.canUndo()
|
|
76
|
+
canClear.value = !!drauu.el?.children.length
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function loadCanvas(page?: number) {
|
|
80
|
+
disableDump = true
|
|
81
|
+
const data = drawingState[page || currentSlideNo.value]
|
|
82
|
+
if (data != null)
|
|
83
|
+
drauu.load(data)
|
|
84
|
+
else
|
|
85
|
+
drauu.clear()
|
|
86
|
+
updateState()
|
|
87
|
+
disableDump = false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
drauu.on('changed', () => {
|
|
91
|
+
updateState()
|
|
92
|
+
if (!disableDump) {
|
|
93
|
+
const dump = drauu.dump()
|
|
94
|
+
const key = currentSlideNo.value
|
|
95
|
+
if ((drawingState[key] || '') !== dump && syncUp.value)
|
|
96
|
+
patch(key, drauu.dump())
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
onPatch((state) => {
|
|
101
|
+
disableDump = true
|
|
102
|
+
if (state[currentSlideNo.value] != null)
|
|
103
|
+
drauu.load(state[currentSlideNo.value] || '')
|
|
104
|
+
disableDump = false
|
|
105
|
+
updateState()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
nextTick(() => {
|
|
109
|
+
watch(currentSlideNo, () => {
|
|
110
|
+
if (!drauu.mounted)
|
|
111
|
+
return
|
|
112
|
+
loadCanvas()
|
|
113
|
+
}, { immediate: true })
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
drauu.on('start', () => isDrawing.value = true)
|
|
117
|
+
drauu.on('end', () => isDrawing.value = false)
|
|
118
|
+
|
|
119
|
+
window.addEventListener('keydown', (e) => {
|
|
120
|
+
if (!drawingEnabled.value || isInputting.value)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
const noModifier = !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
|
|
124
|
+
let handled = true
|
|
125
|
+
if (e.code === 'KeyZ' && (e.ctrlKey || e.metaKey)) {
|
|
126
|
+
if (e.shiftKey)
|
|
127
|
+
drauu.redo()
|
|
128
|
+
else
|
|
129
|
+
drauu.undo()
|
|
130
|
+
}
|
|
131
|
+
else if (e.code === 'Escape') {
|
|
132
|
+
drawingEnabled.value = false
|
|
133
|
+
}
|
|
134
|
+
else if (e.code === 'KeyL' && noModifier) {
|
|
135
|
+
drawingMode.value = 'line'
|
|
136
|
+
}
|
|
137
|
+
else if (e.code === 'KeyA' && noModifier) {
|
|
138
|
+
drawingMode.value = 'arrow'
|
|
139
|
+
}
|
|
140
|
+
else if (e.code === 'KeyS' && noModifier) {
|
|
141
|
+
drawingMode.value = 'stylus'
|
|
142
|
+
}
|
|
143
|
+
else if (e.code === 'KeyR' && noModifier) {
|
|
144
|
+
drawingMode.value = 'rectangle'
|
|
145
|
+
}
|
|
146
|
+
else if (e.code === 'KeyE' && noModifier) {
|
|
147
|
+
drawingMode.value = 'ellipse'
|
|
148
|
+
}
|
|
149
|
+
else if (e.code === 'KeyC' && noModifier) {
|
|
150
|
+
clearDrauu()
|
|
151
|
+
}
|
|
152
|
+
else if (e.code.startsWith('Digit') && noModifier && +e.code[5] <= brushColors.length) {
|
|
153
|
+
brush.color = brushColors[+e.code[5] - 1]
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
handled = false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (handled) {
|
|
160
|
+
e.preventDefault()
|
|
161
|
+
e.stopPropagation()
|
|
162
|
+
}
|
|
163
|
+
}, false)
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
brush,
|
|
167
|
+
brushColors,
|
|
168
|
+
canClear,
|
|
169
|
+
canRedo,
|
|
170
|
+
canUndo,
|
|
171
|
+
clear: clearDrauu,
|
|
172
|
+
drauu,
|
|
173
|
+
drauuOptions,
|
|
174
|
+
drawingEnabled,
|
|
175
|
+
drawingMode,
|
|
176
|
+
drawingPinned,
|
|
177
|
+
drawingState,
|
|
178
|
+
isDrawing,
|
|
179
|
+
loadCanvas,
|
|
180
|
+
}
|
|
181
|
+
})
|
package/composables/useNav.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { ClicksContext, SlideRoute, TocItem } from '@slidev/types'
|
|
2
|
-
import type { ComputedRef, Ref, TransitionGroupProps } from 'vue'
|
|
2
|
+
import type { ComputedRef, Ref, TransitionGroupProps, WritableComputedRef } from 'vue'
|
|
3
3
|
import { computed, ref, watch } from 'vue'
|
|
4
|
-
import
|
|
4
|
+
import { useRouter } from 'vue-router'
|
|
5
|
+
import type { RouteLocationNormalized, Router } from 'vue-router'
|
|
6
|
+
import { createSharedComposable } from '@vueuse/core'
|
|
7
|
+
import { logicOr } from '@vueuse/math'
|
|
5
8
|
import { getCurrentTransition } from '../logic/transition'
|
|
6
9
|
import { getSlide, getSlidePath } from '../logic/slides'
|
|
7
10
|
import { CLICKS_MAX } from '../constants'
|
|
11
|
+
import { skipTransition } from '../logic/hmr'
|
|
12
|
+
import { configs } from '../env'
|
|
13
|
+
import { useRouteQuery } from '../logic/route'
|
|
8
14
|
import { useTocTree } from './useTocTree'
|
|
9
|
-
import {
|
|
15
|
+
import { createClicksContextBase } from './useClicks'
|
|
10
16
|
import { slides } from '#slidev/slides'
|
|
11
17
|
|
|
12
18
|
export interface SlidevContextNav {
|
|
@@ -54,10 +60,32 @@ export interface SlidevContextNav {
|
|
|
54
60
|
goLast: () => Promise<void>
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
export interface SlidevContextNavState {
|
|
64
|
+
router: Router
|
|
65
|
+
currentRoute: ComputedRef<RouteLocationNormalized>
|
|
66
|
+
isPrintMode: ComputedRef<boolean>
|
|
67
|
+
isPrintWithClicks: ComputedRef<boolean>
|
|
68
|
+
isEmbedded: ComputedRef<boolean>
|
|
69
|
+
isPlaying: ComputedRef<boolean>
|
|
70
|
+
isPresenter: ComputedRef<boolean>
|
|
71
|
+
isNotesViewer: ComputedRef<boolean>
|
|
72
|
+
isPresenterAvailable: ComputedRef<boolean>
|
|
73
|
+
hasPrimarySlide: ComputedRef<boolean>
|
|
74
|
+
currentSlideNo: ComputedRef<number>
|
|
75
|
+
currentSlideRoute: ComputedRef<SlideRoute>
|
|
76
|
+
clicksContext: ComputedRef<ClicksContext>
|
|
77
|
+
queryClicksRaw: Ref<string>
|
|
78
|
+
queryClicks: WritableComputedRef<number>
|
|
79
|
+
getPrimaryClicks: (route: SlideRoute) => ClicksContext
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SlidevContextNavFull extends SlidevContextNav, SlidevContextNavState {}
|
|
83
|
+
|
|
57
84
|
export function useNavBase(
|
|
58
85
|
currentSlideRoute: ComputedRef<SlideRoute>,
|
|
59
86
|
clicksContext: ComputedRef<ClicksContext>,
|
|
60
87
|
queryClicks: Ref<number> = ref(0),
|
|
88
|
+
isPresenter: Ref<boolean>,
|
|
61
89
|
router?: Router,
|
|
62
90
|
): SlidevContextNav {
|
|
63
91
|
const total = computed(() => slides.value.length)
|
|
@@ -65,7 +93,7 @@ export function useNavBase(
|
|
|
65
93
|
const navDirection = ref(0)
|
|
66
94
|
const clicksDirection = ref(0)
|
|
67
95
|
|
|
68
|
-
const currentPath = computed(() => getSlidePath(currentSlideRoute.value))
|
|
96
|
+
const currentPath = computed(() => getSlidePath(currentSlideRoute.value, isPresenter.value))
|
|
69
97
|
const currentSlideNo = computed(() => currentSlideRoute.value.no)
|
|
70
98
|
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))
|
|
71
99
|
|
|
@@ -95,7 +123,11 @@ export function useNavBase(
|
|
|
95
123
|
return true
|
|
96
124
|
}
|
|
97
125
|
|
|
98
|
-
const tocTree = useTocTree(
|
|
126
|
+
const tocTree = useTocTree(
|
|
127
|
+
slides,
|
|
128
|
+
currentSlideNo,
|
|
129
|
+
currentSlideRoute,
|
|
130
|
+
)
|
|
99
131
|
|
|
100
132
|
async function next() {
|
|
101
133
|
clicksDirection.value = 1
|
|
@@ -141,7 +173,7 @@ export function useNavBase(
|
|
|
141
173
|
async function go(page: number | string, clicks?: number) {
|
|
142
174
|
skipTransition.value = false
|
|
143
175
|
await router?.push({
|
|
144
|
-
path: getSlidePath(page),
|
|
176
|
+
path: getSlidePath(page, isPresenter.value),
|
|
145
177
|
query: {
|
|
146
178
|
...router.currentRoute.value.query,
|
|
147
179
|
clicks: clicks || undefined,
|
|
@@ -185,7 +217,12 @@ export function useFixedNav(
|
|
|
185
217
|
): SlidevContextNav {
|
|
186
218
|
const noop = async () => { }
|
|
187
219
|
return {
|
|
188
|
-
...useNavBase(
|
|
220
|
+
...useNavBase(
|
|
221
|
+
computed(() => currentSlideRoute),
|
|
222
|
+
computed(() => clicksContext),
|
|
223
|
+
ref(CLICKS_MAX),
|
|
224
|
+
ref(false),
|
|
225
|
+
),
|
|
189
226
|
next: noop,
|
|
190
227
|
prev: noop,
|
|
191
228
|
nextSlide: noop,
|
|
@@ -195,3 +232,126 @@ export function useFixedNav(
|
|
|
195
232
|
go: noop,
|
|
196
233
|
}
|
|
197
234
|
}
|
|
235
|
+
|
|
236
|
+
const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
237
|
+
const router = useRouter()
|
|
238
|
+
|
|
239
|
+
const currentRoute = computed(() => router.currentRoute.value)
|
|
240
|
+
const isPrintMode = computed(() => currentRoute.value.query.print !== undefined)
|
|
241
|
+
const isPrintWithClicks = computed(() => currentRoute.value.query.print === 'clicks')
|
|
242
|
+
const isEmbedded = computed(() => currentRoute.value.query.embedded !== undefined)
|
|
243
|
+
const isPlaying = computed(() => currentRoute.value.name === 'play')
|
|
244
|
+
const isPresenter = computed(() => currentRoute.value.name === 'presenter')
|
|
245
|
+
const isNotesViewer = computed(() => currentRoute.value.name === 'notes')
|
|
246
|
+
const isPresenterAvailable = computed(() => !isPresenter.value && (!configs.remote || currentRoute.value.query.password === configs.remote))
|
|
247
|
+
const hasPrimarySlide = logicOr(isPlaying, isPresenter)
|
|
248
|
+
|
|
249
|
+
const currentSlideNo = computed(() => hasPrimarySlide.value ? getSlide(currentRoute.value.params.no as string)?.no ?? 1 : 1)
|
|
250
|
+
const currentSlideRoute = computed(() => slides.value[currentSlideNo.value - 1])
|
|
251
|
+
|
|
252
|
+
const queryClicksRaw = useRouteQuery<string>('clicks', '0')
|
|
253
|
+
|
|
254
|
+
const clicksContext = computed(() => getPrimaryClicks(currentSlideRoute.value))
|
|
255
|
+
|
|
256
|
+
const queryClicks = computed({
|
|
257
|
+
get() {
|
|
258
|
+
if (clicksContext.value.disabled)
|
|
259
|
+
return CLICKS_MAX
|
|
260
|
+
let v = +(queryClicksRaw.value || 0)
|
|
261
|
+
if (Number.isNaN(v))
|
|
262
|
+
v = 0
|
|
263
|
+
return v
|
|
264
|
+
},
|
|
265
|
+
set(v) {
|
|
266
|
+
queryClicksRaw.value = v.toString()
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
function getPrimaryClicks(
|
|
271
|
+
route: SlideRoute,
|
|
272
|
+
): ClicksContext {
|
|
273
|
+
if (route?.meta?.__clicksContext)
|
|
274
|
+
return route.meta.__clicksContext
|
|
275
|
+
|
|
276
|
+
const thisNo = route.no
|
|
277
|
+
const context = createClicksContextBase(
|
|
278
|
+
computed({
|
|
279
|
+
get() {
|
|
280
|
+
if (context.disabled)
|
|
281
|
+
return CLICKS_MAX
|
|
282
|
+
if (currentSlideNo.value === thisNo)
|
|
283
|
+
return +(queryClicksRaw.value || 0) || 0
|
|
284
|
+
else if (currentSlideNo.value > thisNo)
|
|
285
|
+
return CLICKS_MAX
|
|
286
|
+
else
|
|
287
|
+
return 0
|
|
288
|
+
},
|
|
289
|
+
set(v) {
|
|
290
|
+
if (currentSlideNo.value === thisNo)
|
|
291
|
+
queryClicksRaw.value = Math.min(v, context.total).toString()
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
route?.meta?.clicks,
|
|
295
|
+
() => isPrintMode.value && !isPrintWithClicks.value,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
// On slide mounted, make sure the query is not greater than the total
|
|
299
|
+
context.onMounted = () => {
|
|
300
|
+
if (queryClicksRaw.value)
|
|
301
|
+
queryClicksRaw.value = Math.min(+queryClicksRaw.value, context.total).toString()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (route?.meta)
|
|
305
|
+
route.meta.__clicksContext = context
|
|
306
|
+
|
|
307
|
+
return context
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
router,
|
|
312
|
+
currentRoute,
|
|
313
|
+
isPrintMode,
|
|
314
|
+
isPrintWithClicks,
|
|
315
|
+
isEmbedded,
|
|
316
|
+
isPlaying,
|
|
317
|
+
isPresenter,
|
|
318
|
+
isNotesViewer,
|
|
319
|
+
isPresenterAvailable,
|
|
320
|
+
hasPrimarySlide,
|
|
321
|
+
currentSlideNo,
|
|
322
|
+
currentSlideRoute,
|
|
323
|
+
clicksContext,
|
|
324
|
+
queryClicksRaw,
|
|
325
|
+
queryClicks,
|
|
326
|
+
getPrimaryClicks,
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
export const useNav = createSharedComposable((): SlidevContextNavFull => {
|
|
331
|
+
const state = useNavState()
|
|
332
|
+
const router = useRouter()
|
|
333
|
+
|
|
334
|
+
const nav = useNavBase(
|
|
335
|
+
state.currentSlideRoute,
|
|
336
|
+
state.clicksContext,
|
|
337
|
+
state.queryClicks,
|
|
338
|
+
state.isPresenter,
|
|
339
|
+
router,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
watch(
|
|
343
|
+
[nav.total, state.currentRoute],
|
|
344
|
+
async () => {
|
|
345
|
+
if (state.hasPrimarySlide.value && !getSlide(state.currentRoute.value.params.no as string)) {
|
|
346
|
+
// The current slide may has been removed. Redirect to the last slide.
|
|
347
|
+
await nav.goLast()
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{ flush: 'pre', immediate: true },
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
...nav,
|
|
355
|
+
...state,
|
|
356
|
+
}
|
|
357
|
+
})
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Ref } from 'vue'
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import { timestamp, usePointerSwipe } from '@vueuse/core'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { useNav } from '../composables/useNav'
|
|
5
|
+
import { useDrawings } from './useDrawings'
|
|
6
6
|
|
|
7
7
|
export function useSwipeControls(root: Ref<HTMLElement | undefined>) {
|
|
8
|
+
const { next, nextSlide, prev, prevSlide } = useNav()
|
|
9
|
+
const { isDrawing } = useDrawings()
|
|
10
|
+
|
|
8
11
|
const swipeBegin = ref(0)
|
|
9
12
|
const { direction, distanceX, distanceY } = usePointerSwipe(root, {
|
|
10
13
|
pointerTypes: ['touch'],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SlideRoute, TocItem } from '@slidev/types'
|
|
2
2
|
import type { ComputedRef, Ref } from 'vue'
|
|
3
3
|
import { computed } from 'vue'
|
|
4
|
-
import {
|
|
4
|
+
import { getSlidePath } from '../logic/slides'
|
|
5
5
|
|
|
6
6
|
function addToTree(tree: TocItem[], route: SlideRoute, level = 1) {
|
|
7
7
|
const titleLevel = route.meta?.slide?.level
|
|
@@ -13,7 +13,7 @@ function addToTree(tree: TocItem[], route: SlideRoute, level = 1) {
|
|
|
13
13
|
no: route.no,
|
|
14
14
|
children: [],
|
|
15
15
|
level,
|
|
16
|
-
path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no),
|
|
16
|
+
path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no, false),
|
|
17
17
|
hideInToc: Boolean(route.meta?.slide?.frontmatter?.hideInToc),
|
|
18
18
|
title: route.meta?.slide?.title,
|
|
19
19
|
})
|
|
@@ -25,15 +25,23 @@ function getTreeWithActiveStatuses(
|
|
|
25
25
|
currentRoute?: SlideRoute,
|
|
26
26
|
hasActiveParent = false,
|
|
27
27
|
parent?: TocItem,
|
|
28
|
+
currentSlideNo?: Ref<number>,
|
|
28
29
|
): TocItem[] {
|
|
29
30
|
return tree.map((item: TocItem) => {
|
|
30
31
|
const clone = {
|
|
31
32
|
...item,
|
|
32
|
-
active: item.no === currentSlideNo
|
|
33
|
+
active: item.no === currentSlideNo?.value,
|
|
33
34
|
hasActiveParent,
|
|
34
35
|
}
|
|
35
|
-
if (clone.children.length > 0)
|
|
36
|
-
clone.children = getTreeWithActiveStatuses(
|
|
36
|
+
if (clone.children.length > 0) {
|
|
37
|
+
clone.children = getTreeWithActiveStatuses(
|
|
38
|
+
clone.children,
|
|
39
|
+
currentRoute,
|
|
40
|
+
clone.active || clone.hasActiveParent,
|
|
41
|
+
clone,
|
|
42
|
+
currentSlideNo,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
37
45
|
if (parent && (clone.active || clone.activeParent))
|
|
38
46
|
parent.activeParent = true
|
|
39
47
|
return clone
|
|
@@ -49,7 +57,11 @@ function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
|
49
57
|
}))
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
export function useTocTree(
|
|
60
|
+
export function useTocTree(
|
|
61
|
+
slides: Ref<SlideRoute[]>,
|
|
62
|
+
currentSlideNo: Ref<number>,
|
|
63
|
+
currentSlideRoute: Ref<SlideRoute>,
|
|
64
|
+
): ComputedRef<TocItem[]> {
|
|
53
65
|
const rawTree = computed(() => slides.value
|
|
54
66
|
.filter((route: SlideRoute) => route.meta?.slide?.title)
|
|
55
67
|
.reduce((acc: TocItem[], route: SlideRoute) => {
|
|
@@ -57,7 +69,13 @@ export function useTocTree(slides: Ref<SlideRoute[]>): ComputedRef<TocItem[]> {
|
|
|
57
69
|
return acc
|
|
58
70
|
}, []))
|
|
59
71
|
|
|
60
|
-
const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(
|
|
72
|
+
const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(
|
|
73
|
+
rawTree.value,
|
|
74
|
+
currentSlideRoute.value,
|
|
75
|
+
undefined,
|
|
76
|
+
undefined,
|
|
77
|
+
currentSlideNo,
|
|
78
|
+
))
|
|
61
79
|
|
|
62
80
|
return computed(() => filterTree(treeWithActiveStatuses.value))
|
|
63
81
|
}
|
package/context.ts
CHANGED
|
@@ -41,6 +41,9 @@ export function useSlideContext() {
|
|
|
41
41
|
|
|
42
42
|
export type SlideContext = ReturnType<typeof useSlideContext>
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
44
47
|
export function provideFrontmatter(frontmatter: Record<string, any>) {
|
|
45
48
|
provideLocal(injectionFrontmatter, frontmatter)
|
|
46
49
|
|
|
@@ -63,6 +66,8 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
|
|
|
63
66
|
/**
|
|
64
67
|
* Convert frontmatter options to props for v-bind
|
|
65
68
|
* It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
66
71
|
*/
|
|
67
72
|
export function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {
|
|
68
73
|
return {
|
package/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is the public APIs that you might use in your app.
|
|
3
|
+
*
|
|
4
|
+
* The other files despite they are accessable, are not meant to be used directly, breaking changes might happen.
|
|
5
|
+
*/
|
|
6
|
+
export { useSlideContext } from './context'
|
|
7
|
+
export { useNav } from './composables/useNav'
|
|
8
|
+
export { useDrawings } from './composables/useDrawings'
|
|
9
|
+
export { useDarkMode } from './composables/useDarkMode'
|
|
10
|
+
|
|
11
|
+
export * from './layoutHelper'
|
|
12
|
+
export * from './env'
|
package/internals/CodeRunner.vue
CHANGED
|
@@ -3,9 +3,9 @@ import { debounce, toArray } from '@antfu/utils'
|
|
|
3
3
|
import { useVModel } from '@vueuse/core'
|
|
4
4
|
import type { CodeRunnerOutput } from '@slidev/types'
|
|
5
5
|
import { computed, ref, shallowRef, watch } from 'vue'
|
|
6
|
-
import { isPrintMode } from '../logic/nav'
|
|
7
6
|
import { useSlideContext } from '../context'
|
|
8
7
|
import setupCodeRunners from '../setup/code-runners'
|
|
8
|
+
import { useNav } from '../composables/useNav'
|
|
9
9
|
import IconButton from './IconButton.vue'
|
|
10
10
|
import DomElement from './DomElement.vue'
|
|
11
11
|
|
|
@@ -19,6 +19,9 @@ const props = defineProps<{
|
|
|
19
19
|
}>()
|
|
20
20
|
|
|
21
21
|
const emit = defineEmits(['update:modelValue'])
|
|
22
|
+
|
|
23
|
+
const { isPrintMode } = useNav()
|
|
24
|
+
|
|
22
25
|
const code = useVModel(props, 'modelValue', emit)
|
|
23
26
|
|
|
24
27
|
const { $renderContext } = useSlideContext()
|