@slidev/client 0.48.0-beta.9 → 0.48.0

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.
Files changed (97) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +33 -28
  4. package/builtin/KaTexBlockWrapper.vue +1 -1
  5. package/builtin/Link.vue +3 -1
  6. package/builtin/Mermaid.vue +4 -3
  7. package/builtin/Monaco.vue +166 -93
  8. package/builtin/ShikiMagicMove.vue +103 -0
  9. package/builtin/SlidevVideo.vue +1 -1
  10. package/builtin/Toc.vue +1 -1
  11. package/builtin/TocList.vue +4 -3
  12. package/builtin/Tweet.vue +12 -9
  13. package/builtin/VClick.ts +2 -1
  14. package/composables/useClicks.ts +19 -32
  15. package/composables/useDarkMode.ts +9 -0
  16. package/composables/useDrawings.ts +181 -0
  17. package/composables/useNav.ts +346 -44
  18. package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
  19. package/composables/useSwipeControls.ts +43 -0
  20. package/composables/useTocTree.ts +81 -0
  21. package/composables/useViewTransition.ts +7 -4
  22. package/constants.ts +4 -3
  23. package/context.ts +13 -6
  24. package/env.ts +7 -16
  25. package/index.html +1 -0
  26. package/index.ts +12 -0
  27. package/internals/ClicksSlider.vue +93 -0
  28. package/internals/CodeRunner.vue +142 -0
  29. package/internals/Controls.vue +2 -2
  30. package/internals/DomElement.vue +18 -0
  31. package/internals/DrawingControls.vue +14 -15
  32. package/internals/DrawingLayer.vue +6 -5
  33. package/internals/DrawingPreview.vue +4 -2
  34. package/internals/Goto.vue +9 -6
  35. package/internals/IconButton.vue +3 -2
  36. package/internals/NavControls.vue +30 -11
  37. package/internals/NoteDisplay.vue +131 -8
  38. package/internals/NoteEditable.vue +129 -0
  39. package/internals/NoteStatic.vue +5 -2
  40. package/internals/PrintContainer.vue +11 -8
  41. package/internals/PrintSlide.vue +11 -12
  42. package/internals/PrintSlideClick.vue +14 -19
  43. package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
  44. package/internals/RecordingControls.vue +1 -1
  45. package/internals/RecordingDialog.vue +3 -3
  46. package/internals/{Editor.vue → SideEditor.vue} +24 -15
  47. package/internals/SlideContainer.vue +13 -9
  48. package/internals/SlideLoading.vue +19 -0
  49. package/internals/SlideWrapper.vue +79 -0
  50. package/internals/SlidesShow.vue +36 -22
  51. package/layouts/error.vue +5 -0
  52. package/layouts/two-cols-header.vue +9 -3
  53. package/logic/overview.ts +2 -2
  54. package/logic/route.ts +16 -5
  55. package/logic/slides.ts +20 -0
  56. package/logic/transition.ts +50 -0
  57. package/logic/utils.ts +24 -1
  58. package/main.ts +3 -15
  59. package/{setup → modules}/codemirror.ts +1 -3
  60. package/modules/context.ts +1 -46
  61. package/modules/mermaid.ts +9 -8
  62. package/package.json +21 -15
  63. package/pages/notes.vue +6 -3
  64. package/pages/overview.vue +138 -51
  65. package/pages/play.vue +16 -9
  66. package/pages/presenter/print.vue +10 -5
  67. package/pages/presenter.vue +122 -104
  68. package/pages/print.vue +4 -3
  69. package/routes.ts +8 -54
  70. package/setup/code-runners.ts +164 -0
  71. package/setup/main.ts +39 -9
  72. package/setup/mermaid.ts +5 -6
  73. package/setup/monaco.ts +114 -51
  74. package/setup/root.ts +62 -18
  75. package/setup/shortcuts.ts +15 -12
  76. package/shim-vue.d.ts +34 -0
  77. package/shim.d.ts +1 -13
  78. package/state/index.ts +2 -2
  79. package/styles/code.css +9 -5
  80. package/styles/index.css +63 -7
  81. package/styles/katex.css +1 -1
  82. package/styles/layouts-base.css +11 -8
  83. package/styles/shiki-twoslash.css +1 -1
  84. package/styles/vars.css +1 -1
  85. package/uno.config.ts +10 -1
  86. package/utils.ts +15 -2
  87. package/composables/useContext.ts +0 -17
  88. package/composables/useTweetScript.ts +0 -17
  89. package/iframes/monaco/index.css +0 -28
  90. package/iframes/monaco/index.html +0 -7
  91. package/iframes/monaco/index.ts +0 -260
  92. package/internals/NoteEditor.vue +0 -92
  93. package/internals/SlideWrapper.ts +0 -58
  94. package/logic/drawings.ts +0 -161
  95. package/logic/nav.ts +0 -278
  96. package/setup/prettier.ts +0 -43
  97. /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: 99999,
35
+ every: CLICKS_MAX,
35
36
  at: this.at,
36
37
  hide: this.hide,
37
38
  fade: this.fade,
@@ -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 useClicksContextBase(getCurrent: () => number, clicksOverrides?: number): ClicksContext {
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 isPrintMode.value && !isPrintWithClicks.value
18
+ return isDisabled ? isDisabled() : false
16
19
  },
17
20
  get current() {
18
- return getCurrent()
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 usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksContext {
63
- if (route?.meta?.__clicksContext)
64
- return route.meta.__clicksContext
65
- const thisPath = +(route?.path ?? 99999)
66
- const context = useClicksContextBase(
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,9 @@
1
+ import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
2
+
3
+ export function useDarkMode() {
4
+ return {
5
+ isColorSchemaConfigured,
6
+ isDark,
7
+ toggleDark,
8
+ }
9
+ }
@@ -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
+ })
@@ -1,55 +1,357 @@
1
- import type { ComputedRef } from 'vue'
2
- import { computed } from 'vue'
3
- import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
4
- import type { TocItem } from '@slidev/types'
5
- import type { SlidevContextNav } from '../modules/context'
6
- import { addToTree, clicks, clicksContext, clicksTotal, downloadPDF, filterTree, getPath, getTreeWithActiveStatuses, go, next, nextSlide, openInEditor, prev, prevSlide } from '../logic/nav'
7
- import { rawRoutes } from '../routes'
8
-
9
- export function useNav(route: ComputedRef<RouteRecordRaw | RouteLocationNormalizedLoaded>): SlidevContextNav {
10
- const path = computed(() => route.value.path)
11
- const total = computed(() => rawRoutes.length)
12
-
13
- const currentPage = computed(() => Number.parseInt(path.value.split(/\//g).slice(-1)[0]) || 1)
14
- const currentPath = computed(() => getPath(currentPage.value))
15
- const currentRoute = computed(() => rawRoutes.find(i => i.path === `${currentPage.value}`) ?? rawRoutes.at(-1) ?? rawRoutes[0])
16
- const currentSlideId = computed(() => currentRoute.value?.meta?.slide?.id)
17
- const currentLayout = computed(() => currentRoute.value?.meta?.layout || (currentPage.value === 1 ? 'cover' : 'default'))
18
-
19
- const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length, currentPage.value + 1)}`))
20
-
21
- const rawTree = computed(() => rawRoutes
22
- .filter((route: RouteRecordRaw) => route.meta?.slide?.title)
23
- .reduce((acc: TocItem[], route: RouteRecordRaw) => {
24
- addToTree(acc, route)
25
- return acc
26
- }, []))
27
- const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value, currentRoute.value))
28
- const tree = computed(() => filterTree(treeWithActiveStatuses.value))
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
- rawRoutes,
32
- route,
33
- path,
185
+ slides,
34
186
  total,
35
- clicksContext,
36
- clicks,
37
- clicksTotal,
38
- currentPage,
39
187
  currentPath,
40
- currentRoute,
41
- currentSlideId,
188
+ currentSlideNo,
189
+ currentPage: currentSlideNo,
190
+ currentSlideRoute,
42
191
  currentLayout,
192
+ currentTransition,
193
+ clicksDirection,
43
194
  nextRoute,
44
- rawTree,
45
- treeWithActiveStatuses,
46
- tree,
47
- go,
48
- downloadPDF,
49
- next,
50
- nextSlide,
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
+ })