@slidev/client 0.48.0-beta.23 → 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.
Files changed (48) hide show
  1. package/builtin/Link.vue +3 -1
  2. package/builtin/TocList.vue +2 -2
  3. package/composables/useClicks.ts +13 -50
  4. package/composables/useDrawings.ts +181 -0
  5. package/composables/useNav.ts +167 -7
  6. package/composables/useSwipeControls.ts +5 -2
  7. package/composables/useTocTree.ts +25 -7
  8. package/composables/useViewTransition.ts +1 -1
  9. package/internals/CodeRunner.vue +4 -1
  10. package/internals/DrawingControls.vue +11 -10
  11. package/internals/DrawingLayer.vue +4 -2
  12. package/internals/DrawingPreview.vue +4 -2
  13. package/internals/Goto.vue +4 -2
  14. package/internals/NavControls.vue +21 -3
  15. package/internals/PrintContainer.vue +3 -1
  16. package/internals/PrintSlide.vue +3 -3
  17. package/internals/QuickOverview.vue +5 -4
  18. package/internals/SideEditor.vue +4 -2
  19. package/internals/SlideContainer.vue +3 -1
  20. package/internals/SlidesShow.vue +16 -4
  21. package/logic/overview.ts +1 -1
  22. package/logic/route.ts +6 -9
  23. package/logic/slides.ts +5 -4
  24. package/logic/utils.ts +0 -1
  25. package/main.ts +1 -16
  26. package/modules/context.ts +0 -40
  27. package/package.json +3 -3
  28. package/pages/notes.vue +3 -1
  29. package/pages/overview.vue +6 -3
  30. package/pages/play.vue +6 -3
  31. package/pages/presenter/print.vue +3 -1
  32. package/pages/presenter.vue +19 -6
  33. package/pages/print.vue +3 -1
  34. package/routes.ts +0 -8
  35. package/setup/code-runners.ts +6 -11
  36. package/setup/main.ts +39 -9
  37. package/setup/mermaid.ts +5 -6
  38. package/setup/monaco.ts +7 -9
  39. package/setup/root.ts +56 -11
  40. package/setup/shortcuts.ts +14 -12
  41. package/styles/shiki-twoslash.css +1 -1
  42. package/composables/useContext.ts +0 -12
  43. package/logic/drawings.ts +0 -161
  44. package/logic/nav-state.ts +0 -20
  45. package/logic/nav.ts +0 -71
  46. package/setup/prettier.ts +0 -43
  47. /package/{composables → logic}/hmr.ts +0 -0
  48. /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 { isPrintMode } from '../logic/nav'
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>
@@ -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 Titles from '#slidev/titles.md'
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
- <Titles :no="item.no" />
51
+ <TitleRenderer :no="item.no" />
52
52
  </Link>
53
53
  <TocList
54
54
  v-if="item.children.length > 0"
@@ -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 { computed, ref, shallowReactive } from 'vue'
5
- import { currentSlideNo, isPrintMode, isPrintWithClicks } from '../logic/nav'
4
+ import { ref, shallowReactive } from 'vue'
6
5
  import { normalizeAtProp } from '../logic/utils'
7
- import { CLICKS_MAX } from '../constants'
8
- import { routeForceRefresh, useRouteQuery } from '../logic/route'
6
+ import { routeForceRefresh } from '../logic/route'
9
7
 
10
- function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): ClicksContext {
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 isPrintMode.value && !isPrintWithClicks.value
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
- const queryClicksRaw = useRouteQuery('clicks', '0')
67
-
68
- export function usePrimaryClicks(route: SlideRoute): ClicksContext {
69
- if (route?.meta?.__clicksContext)
70
- return route.meta.__clicksContext
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
+ })
@@ -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 type { Router } from 'vue-router'
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 { skipTransition } from './hmr'
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(slides)
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(computed(() => currentSlideRoute), computed(() => clicksContext)),
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 { isDrawing } from '../logic/drawings'
5
- import { next, nextSlide, prev, prevSlide } from '../logic/nav'
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 { currentSlideNo, currentSlideRoute, getSlidePath } from '../logic/nav'
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.value,
33
+ active: item.no === currentSlideNo?.value,
33
34
  hasActiveParent,
34
35
  }
35
- if (clone.children.length > 0)
36
- clone.children = getTreeWithActiveStatuses(clone.children, currentRoute, clone.active || clone.hasActiveParent, clone)
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(slides: Ref<SlideRoute[]>): ComputedRef<TocItem[]> {
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(rawTree.value, currentSlideRoute.value))
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
  }
@@ -1,6 +1,6 @@
1
1
  import { ref } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
- import { getSlide } from '../logic/nav'
3
+ import { getSlide } from '../logic/slides'
4
4
 
5
5
  export function useViewTransition() {
6
6
  const router = useRouter()
@@ -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 'floating-vue/dist/style.css'
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
- clearDrauu,
13
+ clear,
11
14
  drauu,
12
15
  drawingEnabled,
13
16
  drawingMode,
14
17
  drawingPinned,
15
- } from '../logic/drawings'
16
- import VerticalDivider from './VerticalDivider.vue'
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="clearDrauu()">
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