@slidev/client 0.48.0-beta.9 → 0.48.1
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/App.vue +7 -0
- package/builtin/Arrow.vue +2 -4
- package/builtin/CodeBlockWrapper.vue +33 -28
- package/builtin/KaTexBlockWrapper.vue +1 -1
- package/builtin/Link.vue +3 -1
- package/builtin/Mermaid.vue +4 -3
- package/builtin/Monaco.vue +166 -93
- package/builtin/ShikiMagicMove.vue +103 -0
- package/builtin/SlidevVideo.vue +1 -1
- package/builtin/Toc.vue +1 -1
- package/builtin/TocList.vue +4 -3
- package/builtin/Tweet.vue +12 -9
- package/builtin/VClick.ts +2 -1
- package/composables/useClicks.ts +19 -32
- package/composables/useDarkMode.ts +9 -0
- package/composables/useDrawings.ts +181 -0
- package/composables/useNav.ts +346 -44
- package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
- package/composables/useSwipeControls.ts +43 -0
- package/composables/useTocTree.ts +81 -0
- package/composables/useViewTransition.ts +7 -4
- package/constants.ts +4 -3
- package/context.ts +13 -6
- package/env.ts +7 -16
- package/index.html +1 -0
- package/index.ts +12 -0
- package/internals/ClicksSlider.vue +97 -0
- package/internals/CodeRunner.vue +142 -0
- package/internals/Controls.vue +2 -2
- package/internals/DomElement.vue +18 -0
- package/internals/DrawingControls.vue +14 -15
- package/internals/DrawingLayer.vue +6 -5
- package/internals/DrawingPreview.vue +4 -2
- package/internals/Goto.vue +9 -6
- package/internals/IconButton.vue +3 -2
- package/internals/NavControls.vue +30 -11
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +129 -0
- package/internals/NoteStatic.vue +5 -2
- package/internals/PrintContainer.vue +11 -8
- package/internals/PrintSlide.vue +11 -12
- package/internals/PrintSlideClick.vue +14 -19
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +3 -3
- package/internals/{Editor.vue → SideEditor.vue} +24 -15
- package/internals/SlideContainer.vue +13 -9
- package/internals/SlideLoading.vue +19 -0
- package/internals/SlideWrapper.vue +79 -0
- package/internals/SlidesShow.vue +36 -22
- package/layouts/error.vue +5 -0
- package/layouts/two-cols-header.vue +9 -3
- package/logic/overview.ts +2 -2
- package/logic/route.ts +16 -5
- package/logic/slides.ts +20 -0
- package/logic/transition.ts +50 -0
- package/logic/utils.ts +24 -1
- package/main.ts +3 -15
- package/{setup → modules}/codemirror.ts +1 -3
- package/modules/context.ts +1 -46
- package/modules/mermaid.ts +9 -8
- package/package.json +21 -15
- package/pages/notes.vue +6 -3
- package/pages/overview.vue +139 -51
- package/pages/play.vue +16 -9
- package/pages/presenter/print.vue +10 -5
- package/pages/presenter.vue +122 -104
- package/pages/print.vue +4 -3
- package/routes.ts +8 -54
- package/setup/code-runners.ts +164 -0
- package/setup/main.ts +39 -9
- package/setup/mermaid.ts +5 -6
- package/setup/monaco.ts +114 -51
- package/setup/root.ts +62 -18
- package/setup/shortcuts.ts +15 -12
- package/shim-vue.d.ts +34 -0
- package/shim.d.ts +1 -13
- package/state/index.ts +2 -2
- package/styles/code.css +9 -5
- package/styles/index.css +63 -7
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +11 -8
- package/styles/shiki-twoslash.css +1 -1
- package/styles/vars.css +1 -1
- package/uno.config.ts +10 -1
- package/utils.ts +15 -2
- package/composables/useContext.ts +0 -17
- package/composables/useTweetScript.ts +0 -17
- package/iframes/monaco/index.css +0 -28
- package/iframes/monaco/index.html +0 -7
- package/iframes/monaco/index.ts +0 -260
- package/internals/NoteEditor.vue +0 -92
- package/internals/SlideWrapper.ts +0 -58
- package/logic/drawings.ts +0 -161
- package/logic/nav.ts +0 -278
- package/setup/prettier.ts +0 -43
- /package/{composables → logic}/hmr.ts +0 -0
package/builtin/VClick.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { PropType, VNode } from 'vue'
|
|
8
8
|
import { Text, defineComponent, h } from 'vue'
|
|
9
|
+
import { CLICKS_MAX } from '../constants'
|
|
9
10
|
import VClicks from './VClicks'
|
|
10
11
|
|
|
11
12
|
export default defineComponent({
|
|
@@ -31,7 +32,7 @@ export default defineComponent({
|
|
|
31
32
|
return h(
|
|
32
33
|
VClicks,
|
|
33
34
|
{
|
|
34
|
-
every:
|
|
35
|
+
every: CLICKS_MAX,
|
|
35
36
|
at: this.at,
|
|
36
37
|
hide: this.hide,
|
|
37
38
|
fade: this.fade,
|
package/composables/useClicks.ts
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
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 { ref, shallowReactive } from 'vue'
|
|
5
|
-
import type { RouteRecordRaw } from 'vue-router'
|
|
6
|
-
import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
|
|
7
5
|
import { normalizeAtProp } from '../logic/utils'
|
|
6
|
+
import { routeForceRefresh } from '../logic/route'
|
|
8
7
|
|
|
9
|
-
function
|
|
8
|
+
export function createClicksContextBase(
|
|
9
|
+
current: Ref<number>,
|
|
10
|
+
clicksOverrides?: number,
|
|
11
|
+
isDisabled?: () => boolean,
|
|
12
|
+
): ClicksContext {
|
|
10
13
|
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
11
14
|
const map: ClicksContext['map'] = shallowReactive(new Map())
|
|
12
15
|
|
|
13
16
|
return {
|
|
14
17
|
get disabled() {
|
|
15
|
-
return
|
|
18
|
+
return isDisabled ? isDisabled() : false
|
|
16
19
|
},
|
|
17
20
|
get current() {
|
|
18
|
-
return
|
|
21
|
+
return +current.value
|
|
22
|
+
},
|
|
23
|
+
set current(value) {
|
|
24
|
+
current.value = +value
|
|
19
25
|
},
|
|
20
26
|
relativeOffsets,
|
|
21
27
|
map,
|
|
28
|
+
onMounted() {},
|
|
22
29
|
resolve(at, size = 1) {
|
|
23
30
|
const [isRelative, value] = normalizeAtProp(at)
|
|
24
31
|
if (isRelative) {
|
|
@@ -53,34 +60,14 @@ function useClicksContextBase(getCurrent: () => number, clicksOverrides?: number
|
|
|
53
60
|
get total() {
|
|
54
61
|
// eslint-disable-next-line no-unused-expressions
|
|
55
62
|
routeForceRefresh.value
|
|
56
|
-
return clicksOverrides
|
|
57
|
-
?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
|
|
63
|
+
return clicksOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
|
|
58
64
|
},
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
export function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
() => {
|
|
68
|
-
const currentPath = +(currentRoute.value?.path ?? 99999)
|
|
69
|
-
if (currentPath === thisPath)
|
|
70
|
-
return queryClicks.value
|
|
71
|
-
else if (currentPath > thisPath)
|
|
72
|
-
return 99999
|
|
73
|
-
else
|
|
74
|
-
return 0
|
|
75
|
-
},
|
|
76
|
-
route?.meta?.clicks,
|
|
77
|
-
)
|
|
78
|
-
if (route?.meta)
|
|
79
|
-
route.meta.__clicksContext = context
|
|
80
|
-
return context
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function useFixedClicks(route?: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
|
|
84
|
-
const current = ref(currentInit)
|
|
85
|
-
return [current, useClicksContextBase(() => current.value, route?.meta?.clicks)]
|
|
68
|
+
export function createFixedClicks(
|
|
69
|
+
route?: SlideRoute | undefined,
|
|
70
|
+
currentInit = 0,
|
|
71
|
+
): ClicksContext {
|
|
72
|
+
return createClicksContextBase(ref(currentInit), route?.meta?.clicks)
|
|
86
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,55 +1,357 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import type {
|
|
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, WritableComputedRef } from 'vue'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
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'
|
|
8
|
+
import { getCurrentTransition } from '../logic/transition'
|
|
9
|
+
import { getSlide, getSlidePath } from '../logic/slides'
|
|
10
|
+
import { CLICKS_MAX } from '../constants'
|
|
11
|
+
import { skipTransition } from '../logic/hmr'
|
|
12
|
+
import { configs } from '../env'
|
|
13
|
+
import { useRouteQuery } from '../logic/route'
|
|
14
|
+
import { useTocTree } from './useTocTree'
|
|
15
|
+
import { createClicksContextBase } from './useClicks'
|
|
16
|
+
import { slides } from '#slidev/slides'
|
|
17
|
+
|
|
18
|
+
export interface SlidevContextNav {
|
|
19
|
+
slides: Ref<SlideRoute[]>
|
|
20
|
+
total: ComputedRef<number>
|
|
21
|
+
|
|
22
|
+
currentPath: ComputedRef<string>
|
|
23
|
+
currentPage: ComputedRef<number>
|
|
24
|
+
currentSlideNo: ComputedRef<number>
|
|
25
|
+
currentSlideRoute: ComputedRef<SlideRoute>
|
|
26
|
+
currentTransition: ComputedRef<TransitionGroupProps | undefined>
|
|
27
|
+
currentLayout: ComputedRef<string>
|
|
28
|
+
|
|
29
|
+
nextRoute: ComputedRef<SlideRoute>
|
|
30
|
+
prevRoute: ComputedRef<SlideRoute>
|
|
31
|
+
hasNext: ComputedRef<boolean>
|
|
32
|
+
hasPrev: ComputedRef<boolean>
|
|
33
|
+
|
|
34
|
+
clicksContext: ComputedRef<ClicksContext>
|
|
35
|
+
clicks: ComputedRef<number>
|
|
36
|
+
clicksTotal: ComputedRef<number>
|
|
37
|
+
|
|
38
|
+
/** The table of content tree */
|
|
39
|
+
tocTree: ComputedRef<TocItem[]>
|
|
40
|
+
/** The direction of the navigation, 1 for forward, -1 for backward */
|
|
41
|
+
navDirection: Ref<number>
|
|
42
|
+
/** The direction of the clicks, 1 for forward, -1 for backward */
|
|
43
|
+
clicksDirection: Ref<number>
|
|
44
|
+
/** Utility function for open file in editor, only avaible in dev mode */
|
|
45
|
+
openInEditor: (url?: string) => Promise<boolean>
|
|
46
|
+
|
|
47
|
+
/** Go to next click */
|
|
48
|
+
next: () => Promise<void>
|
|
49
|
+
/** Go to previous click */
|
|
50
|
+
prev: () => Promise<void>
|
|
51
|
+
/** Go to next slide */
|
|
52
|
+
nextSlide: () => Promise<void>
|
|
53
|
+
/** Go to previous slide */
|
|
54
|
+
prevSlide: (lastClicks?: boolean) => Promise<void>
|
|
55
|
+
/** Go to slide */
|
|
56
|
+
go: (page: number | string, clicks?: number) => Promise<void>
|
|
57
|
+
/** Go to the first slide */
|
|
58
|
+
goFirst: () => Promise<void>
|
|
59
|
+
/** Go to the last slide */
|
|
60
|
+
goLast: () => Promise<void>
|
|
61
|
+
}
|
|
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
|
+
|
|
84
|
+
export function useNavBase(
|
|
85
|
+
currentSlideRoute: ComputedRef<SlideRoute>,
|
|
86
|
+
clicksContext: ComputedRef<ClicksContext>,
|
|
87
|
+
queryClicks: Ref<number> = ref(0),
|
|
88
|
+
isPresenter: Ref<boolean>,
|
|
89
|
+
router?: Router,
|
|
90
|
+
): SlidevContextNav {
|
|
91
|
+
const total = computed(() => slides.value.length)
|
|
92
|
+
|
|
93
|
+
const navDirection = ref(0)
|
|
94
|
+
const clicksDirection = ref(0)
|
|
95
|
+
|
|
96
|
+
const currentPath = computed(() => getSlidePath(currentSlideRoute.value, isPresenter.value))
|
|
97
|
+
const currentSlideNo = computed(() => currentSlideRoute.value.no)
|
|
98
|
+
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))
|
|
99
|
+
|
|
100
|
+
const clicks = computed(() => clicksContext.value.current)
|
|
101
|
+
const clicksTotal = computed(() => clicksContext.value.total)
|
|
102
|
+
const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])
|
|
103
|
+
const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])
|
|
104
|
+
const hasNext = computed(() => currentSlideNo.value < slides.value.length || clicks.value < clicksTotal.value)
|
|
105
|
+
const hasPrev = computed(() => currentSlideNo.value > 1 || clicks.value > 0)
|
|
106
|
+
|
|
107
|
+
const currentTransition = computed(() => getCurrentTransition(navDirection.value, currentSlideRoute.value, prevRoute.value))
|
|
108
|
+
|
|
109
|
+
watch(currentSlideRoute, (next, prev) => {
|
|
110
|
+
navDirection.value = next.no - prev.no
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
async function openInEditor(url?: string) {
|
|
114
|
+
if (!__DEV__)
|
|
115
|
+
return false
|
|
116
|
+
if (url == null) {
|
|
117
|
+
const slide = currentSlideRoute.value?.meta?.slide
|
|
118
|
+
if (!slide)
|
|
119
|
+
return false
|
|
120
|
+
url = `${slide.filepath}:${slide.start}`
|
|
121
|
+
}
|
|
122
|
+
await fetch(`/__open-in-editor?file=${encodeURIComponent(url)}`)
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const tocTree = useTocTree(
|
|
127
|
+
slides,
|
|
128
|
+
currentSlideNo,
|
|
129
|
+
currentSlideRoute,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
async function next() {
|
|
133
|
+
clicksDirection.value = 1
|
|
134
|
+
if (clicksTotal.value <= queryClicks.value)
|
|
135
|
+
await nextSlide()
|
|
136
|
+
else
|
|
137
|
+
queryClicks.value += 1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function prev() {
|
|
141
|
+
clicksDirection.value = -1
|
|
142
|
+
if (queryClicks.value <= 0)
|
|
143
|
+
await prevSlide()
|
|
144
|
+
else
|
|
145
|
+
queryClicks.value -= 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function nextSlide() {
|
|
149
|
+
clicksDirection.value = 1
|
|
150
|
+
if (currentSlideNo.value < slides.value.length)
|
|
151
|
+
await go(currentSlideNo.value + 1)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function prevSlide(lastClicks = true) {
|
|
155
|
+
clicksDirection.value = -1
|
|
156
|
+
const next = Math.max(1, currentSlideNo.value - 1)
|
|
157
|
+
await go(
|
|
158
|
+
next,
|
|
159
|
+
lastClicks
|
|
160
|
+
? getSlide(next)?.meta.__clicksContext?.total ?? CLICKS_MAX
|
|
161
|
+
: undefined,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function goFirst() {
|
|
166
|
+
return go(1)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function goLast() {
|
|
170
|
+
return go(total.value)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function go(page: number | string, clicks?: number) {
|
|
174
|
+
skipTransition.value = false
|
|
175
|
+
await router?.push({
|
|
176
|
+
path: getSlidePath(page, isPresenter.value),
|
|
177
|
+
query: {
|
|
178
|
+
...router.currentRoute.value.query,
|
|
179
|
+
clicks: clicks || undefined,
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
}
|
|
29
183
|
|
|
30
184
|
return {
|
|
31
|
-
|
|
32
|
-
route,
|
|
33
|
-
path,
|
|
185
|
+
slides,
|
|
34
186
|
total,
|
|
35
|
-
clicksContext,
|
|
36
|
-
clicks,
|
|
37
|
-
clicksTotal,
|
|
38
|
-
currentPage,
|
|
39
187
|
currentPath,
|
|
40
|
-
|
|
41
|
-
|
|
188
|
+
currentSlideNo,
|
|
189
|
+
currentPage: currentSlideNo,
|
|
190
|
+
currentSlideRoute,
|
|
42
191
|
currentLayout,
|
|
192
|
+
currentTransition,
|
|
193
|
+
clicksDirection,
|
|
43
194
|
nextRoute,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
195
|
+
prevRoute,
|
|
196
|
+
clicksContext,
|
|
197
|
+
clicks,
|
|
198
|
+
clicksTotal,
|
|
199
|
+
hasNext,
|
|
200
|
+
hasPrev,
|
|
201
|
+
tocTree,
|
|
202
|
+
navDirection,
|
|
51
203
|
openInEditor,
|
|
204
|
+
next,
|
|
52
205
|
prev,
|
|
206
|
+
go,
|
|
207
|
+
goLast,
|
|
208
|
+
goFirst,
|
|
209
|
+
nextSlide,
|
|
53
210
|
prevSlide,
|
|
54
211
|
}
|
|
55
212
|
}
|
|
213
|
+
|
|
214
|
+
export function useFixedNav(
|
|
215
|
+
currentSlideRoute: SlideRoute,
|
|
216
|
+
clicksContext: ClicksContext,
|
|
217
|
+
): SlidevContextNav {
|
|
218
|
+
const noop = async () => { }
|
|
219
|
+
return {
|
|
220
|
+
...useNavBase(
|
|
221
|
+
computed(() => currentSlideRoute),
|
|
222
|
+
computed(() => clicksContext),
|
|
223
|
+
ref(CLICKS_MAX),
|
|
224
|
+
ref(false),
|
|
225
|
+
),
|
|
226
|
+
next: noop,
|
|
227
|
+
prev: noop,
|
|
228
|
+
nextSlide: noop,
|
|
229
|
+
prevSlide: noop,
|
|
230
|
+
goFirst: noop,
|
|
231
|
+
goLast: noop,
|
|
232
|
+
go: noop,
|
|
233
|
+
}
|
|
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
|
+
})
|