@slidev/client 0.48.0-beta.24 → 0.48.0-beta.25
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/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/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 +3 -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/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()
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Menu } from 'floating-vue'
|
|
3
|
-
import '
|
|
4
|
-
import
|
|
3
|
+
import { useDrawings } from '../composables/useDrawings'
|
|
4
|
+
import VerticalDivider from './VerticalDivider.vue'
|
|
5
|
+
import Draggable from './Draggable.vue'
|
|
6
|
+
import IconButton from './IconButton.vue'
|
|
7
|
+
|
|
8
|
+
const {
|
|
5
9
|
brush,
|
|
6
|
-
brushColors,
|
|
7
10
|
canClear,
|
|
8
11
|
canRedo,
|
|
9
12
|
canUndo,
|
|
10
|
-
|
|
13
|
+
clear,
|
|
11
14
|
drauu,
|
|
12
15
|
drawingEnabled,
|
|
13
16
|
drawingMode,
|
|
14
17
|
drawingPinned,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import Draggable from './Draggable.vue'
|
|
18
|
-
import IconButton from './IconButton.vue'
|
|
18
|
+
brushColors,
|
|
19
|
+
} = useDrawings()
|
|
19
20
|
|
|
20
21
|
function undo() {
|
|
21
22
|
drauu.undo()
|
|
@@ -110,7 +111,7 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
110
111
|
<IconButton title="Redo" :class="{ disabled: !canRedo }" @click="redo()">
|
|
111
112
|
<carbon:redo />
|
|
112
113
|
</IconButton>
|
|
113
|
-
<IconButton title="Delete" :class="{ disabled: !canClear }" @click="
|
|
114
|
+
<IconButton title="Delete" :class="{ disabled: !canClear }" @click="clear()">
|
|
114
115
|
<carbon:trash-can />
|
|
115
116
|
</IconButton>
|
|
116
117
|
|
|
@@ -135,4 +136,4 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
135
136
|
.v-popper--theme-menu .v-popper__arrow-inner {
|
|
136
137
|
--uno: border-main;
|
|
137
138
|
}
|
|
138
|
-
</style
|
|
139
|
+
</style>../composables/drawings
|