@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.
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 +97 -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 +139 -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
@@ -1,29 +1,24 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
2
  import { computed, reactive, shallowRef } from 'vue'
4
- import type { ClicksContext } from '@slidev/types'
5
3
  import { provideLocal } from '@vueuse/core'
6
4
  import { injectionSlidevContext } from '../constants'
7
5
  import { configs, slideHeight, slideWidth } from '../env'
8
6
  import { getSlideClass } from '../utils'
9
- import type { SlidevContextNav } from '../modules/context'
10
- import SlideWrapper from './SlideWrapper'
7
+ import type { SlidevContextNav } from '../composables/useNav'
8
+ import SlideWrapper from './SlideWrapper.vue'
11
9
 
12
- // @ts-expect-error virtual module
13
- import GlobalTop from '/@slidev/global-components/top'
10
+ import GlobalTop from '#slidev/global-components/top'
11
+ import GlobalBottom from '#slidev/global-components/bottom'
14
12
 
15
- // @ts-expect-error virtual module
16
- import GlobalBottom from '/@slidev/global-components/bottom'
17
-
18
- const props = defineProps<{
19
- clicksContext: ClicksContext
13
+ const { nav } = defineProps<{
20
14
  nav: SlidevContextNav
21
- route: RouteRecordRaw
22
15
  }>()
23
16
 
17
+ const route = computed(() => nav.currentSlideRoute.value)
18
+
24
19
  const style = computed(() => ({
25
- height: `${slideHeight}px`,
26
- width: `${slideWidth}px`,
20
+ height: `${slideHeight.value}px`,
21
+ width: `${slideWidth.value}px`,
27
22
  }))
28
23
 
29
24
  const DrawingPreview = shallowRef<any>()
@@ -31,11 +26,11 @@ if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
31
26
  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
32
27
 
33
28
  const id = computed(() =>
34
- `${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
29
+ `${route.value.no.toString().padStart(3, '0')}-${(nav.clicks.value + 1).toString().padStart(2, '0')}`,
35
30
  )
36
31
 
37
32
  provideLocal(injectionSlidevContext, reactive({
38
- nav: props.nav,
33
+ nav,
39
34
  configs,
40
35
  themeConfigs: computed(() => configs.themeConfig),
41
36
  }))
@@ -46,8 +41,8 @@ provideLocal(injectionSlidevContext, reactive({
46
41
  <GlobalBottom />
47
42
 
48
43
  <SlideWrapper
49
- :is="route?.component!"
50
- :clicks-context="clicksContext"
44
+ :is="route.component!"
45
+ :clicks-context="nav.clicksContext.value"
51
46
  :class="getSlideClass(route)"
52
47
  :route="route"
53
48
  />
@@ -58,7 +53,7 @@ provideLocal(injectionSlidevContext, reactive({
58
53
  && DrawingPreview
59
54
  "
60
55
  >
61
- <DrawingPreview :page="+route.path" />
56
+ <DrawingPreview :page="route.no" />
62
57
  </template>
63
58
 
64
59
  <GlobalTop />
@@ -1,22 +1,23 @@
1
1
  <script setup lang="ts">
2
2
  import { useEventListener, useVModel } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
- import { themeVars } from '../env'
5
4
  import { breakpoints, showOverview, windowSize } from '../state'
6
- import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
7
5
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
8
- import { useFixedClicks } from '../composables/useClicks'
6
+ import { createFixedClicks } from '../composables/useClicks'
9
7
  import { getSlideClass } from '../utils'
8
+ import { CLICKS_MAX } from '../constants'
9
+ import { useNav } from '../composables/useNav'
10
10
  import SlideContainer from './SlideContainer.vue'
11
- import SlideWrapper from './SlideWrapper'
11
+ import SlideWrapper from './SlideWrapper.vue'
12
12
  import DrawingPreview from './DrawingPreview.vue'
13
13
  import IconButton from './IconButton.vue'
14
14
 
15
15
  const props = defineProps<{ modelValue: boolean }>()
16
-
17
16
  const emit = defineEmits(['update:modelValue'])
18
17
  const value = useVModel(props, 'modelValue', emit)
19
18
 
19
+ const { currentSlideNo, go: goSlide, slides } = useNav()
20
+
20
21
  function close() {
21
22
  value.value = false
22
23
  }
@@ -78,17 +79,17 @@ useEventListener('keypress', (e) => {
78
79
  keyboardBuffer.value += String(num)
79
80
 
80
81
  // beyond the number of slides, reset
81
- if (+keyboardBuffer.value >= rawRoutes.length) {
82
+ if (+keyboardBuffer.value >= slides.value.length) {
82
83
  keyboardBuffer.value = ''
83
84
  return
84
85
  }
85
86
 
86
- const extactMatch = rawRoutes.findIndex(i => i.path === keyboardBuffer.value)
87
+ const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)
87
88
  if (extactMatch !== -1)
88
89
  currentOverviewPage.value = extactMatch + 1
89
90
 
90
91
  // When the input number is the largest at the number of digits, we go to that page directly.
91
- if (+keyboardBuffer.value * 10 > rawRoutes.length) {
92
+ if (+keyboardBuffer.value * 10 > slides.value.length) {
92
93
  go(+keyboardBuffer.value)
93
94
  keyboardBuffer.value = ''
94
95
  }
@@ -97,7 +98,7 @@ useEventListener('keypress', (e) => {
97
98
  watchEffect(() => {
98
99
  // Watch currentPage, make sure every time we open overview,
99
100
  // we focus on the right page.
100
- currentOverviewPage.value = currentPage.value
101
+ currentOverviewPage.value = currentSlideNo.value
101
102
  // Watch rowCount, make sure up and down shortcut work correctly.
102
103
  overviewRowCount.value = rowCount.value
103
104
  })
@@ -120,18 +121,17 @@ watchEffect(() => {
120
121
  :style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
121
122
  >
122
123
  <div
123
- v-for="(route, idx) of rawRoutes"
124
- :key="route.path"
124
+ v-for="(route, idx) of slides"
125
+ :key="route.no"
125
126
  class="relative"
126
127
  >
127
128
  <div
128
129
  class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
129
130
  :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
130
- :style="themeVars"
131
- @click="go(+route.path)"
131
+ @click="go(route.no)"
132
132
  >
133
133
  <SlideContainer
134
- :key="route.path"
134
+ :key="route.no"
135
135
  :width="cardWidth"
136
136
  :clicks-disabled="true"
137
137
  class="pointer-events-none"
@@ -139,12 +139,12 @@ watchEffect(() => {
139
139
  <SlideWrapper
140
140
  :is="route.component"
141
141
  v-if="route?.component"
142
- :clicks-context="useFixedClicks(route, 99999)[1]"
142
+ :clicks-context="createFixedClicks(route, CLICKS_MAX)"
143
143
  :class="getSlideClass(route)"
144
144
  :route="route"
145
145
  render-context="overview"
146
146
  />
147
- <DrawingPreview :page="+route.path" />
147
+ <DrawingPreview :page="route.no" />
148
148
  </SlideContainer>
149
149
  </div>
150
150
  <div
@@ -163,17 +163,20 @@ watchEffect(() => {
163
163
  </div>
164
164
  </div>
165
165
  </Transition>
166
- <div class="fixed top-4 right-4 text-gray-400 flex items-center gap-4">
167
- <RouterLink
166
+ <div v-if="value" class="fixed top-4 right-4 text-gray-400 flex flex-col items-center gap-2">
167
+ <IconButton title="Close" class="text-2xl" @click="close">
168
+ <carbon:close />
169
+ </IconButton>
170
+ <IconButton
168
171
  v-if="__DEV__"
169
- to="/overview"
172
+ as="a"
173
+ title="Slides Overview"
174
+ target="_blank"
175
+ href="/overview"
170
176
  tab-index="-1"
171
- class="border-main border px3 py1 rounded hover:bg-gray/5 hover:text-primary"
177
+ class="text-2xl"
172
178
  >
173
- List overview
174
- </RouterLink>
175
- <IconButton v-if="value" title="Close" class="text-2xl" @click="close">
176
- <carbon:close />
179
+ <carbon:list-boxes />
177
180
  </IconButton>
178
181
  </div>
179
182
  </template>
@@ -54,7 +54,7 @@ onMounted(() => {
54
54
  </IconButton>
55
55
  <MenuButton :disabled="recording">
56
56
  <template #button>
57
- <IconButton title="Select recording device" class="h-full !text-sm !px-0">
57
+ <IconButton title="Select recording device" class="h-full !text-sm !px-0 aspect-initial">
58
58
  <carbon:chevron-up class="opacity-50" />
59
59
  </IconButton>
60
60
  </template>
@@ -108,18 +108,18 @@ async function start() {
108
108
  }
109
109
  }
110
110
 
111
- input[type="text"] {
111
+ input[type='text'] {
112
112
  @apply border border-main rounded px-2 py-1;
113
113
  }
114
114
 
115
115
  button {
116
116
  @apply bg-orange-400 text-white px-4 py-1 rounded border-b-2 border-orange-600;
117
- @apply hover:(bg-orange-500 border-orange-700)
117
+ @apply hover:(bg-orange-500 border-orange-700);
118
118
  }
119
119
 
120
120
  button.cancel {
121
121
  @apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
122
- @apply hover:(bg-opacity-75 border-opacity-75)
122
+ @apply hover:(bg-opacity-75 border-opacity-75);
123
123
  }
124
124
  }
125
125
  </style>
@@ -1,16 +1,18 @@
1
1
  <script setup lang="ts">
2
- import { throttledWatch, useEventListener } from '@vueuse/core'
2
+ import { throttledWatch, useEventListener, watchThrottled } from '@vueuse/core'
3
3
  import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
- import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
- import { useCodeMirror } from '../setup/codemirror'
6
- import { currentSlideId, openInEditor } from '../logic/nav'
7
- import { useDynamicSlideInfo } from '../logic/note'
4
+ import { activeElement, editorHeight, editorWidth, isEditorVertical, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
+ import { useCodeMirror } from '../modules/codemirror'
6
+ import { useNav } from '../composables/useNav'
7
+ import { useDynamicSlideInfo } from '../composables/useSlideInfo'
8
8
  import IconButton from './IconButton.vue'
9
9
 
10
10
  const props = defineProps<{
11
11
  resize?: boolean
12
12
  }>()
13
13
 
14
+ const { currentSlideNo, openInEditor } = useNav()
15
+
14
16
  const tab = ref<'content' | 'note'>('content')
15
17
  const content = ref('')
16
18
  const note = ref('')
@@ -19,7 +21,7 @@ const frontmatter = ref<any>({})
19
21
  const contentInput = ref<HTMLTextAreaElement>()
20
22
  const noteInput = ref<HTMLTextAreaElement>()
21
23
 
22
- const { info, update } = useDynamicSlideInfo(currentSlideId)
24
+ const { info, update } = useDynamicSlideInfo(currentSlideNo)
23
25
 
24
26
  watch(
25
27
  info,
@@ -94,16 +96,23 @@ onMounted(async () => {
94
96
  },
95
97
  )
96
98
 
97
- watch([tab, vertical], () => {
98
- nextTick(() => {
99
- if (tab.value === 'content')
100
- contentEditor.refresh()
101
- else
102
- noteEditor.refresh()
103
- })
104
- })
99
+ watchThrottled(
100
+ [tab, vertical, isEditorVertical, editorWidth, editorHeight],
101
+ () => {
102
+ nextTick(() => {
103
+ if (tab.value === 'content')
104
+ contentEditor.refresh()
105
+ else
106
+ noteEditor.refresh()
107
+ })
108
+ },
109
+ {
110
+ throttle: 100,
111
+ flush: 'post',
112
+ },
113
+ )
105
114
 
106
- watch(currentSlideId, () => {
115
+ watch(currentSlideNo, () => {
107
116
  contentEditor.clearHistory()
108
117
  noteEditor.clearHistory()
109
118
  }, { flush: 'post' })
@@ -3,7 +3,7 @@ import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
3
3
  import { computed, ref, watchEffect } from 'vue'
4
4
  import { configs, slideAspect, slideHeight, slideWidth } from '../env'
5
5
  import { injectionSlideScale } from '../constants'
6
- import { isPrintMode } from '../logic/nav'
6
+ import { useNav } from '../composables/useNav'
7
7
 
8
8
  const props = defineProps({
9
9
  width: {
@@ -21,11 +21,13 @@ const props = defineProps({
21
21
  },
22
22
  })
23
23
 
24
+ const { clicksDirection, isPrintMode } = useNav()
25
+
24
26
  const root = ref<HTMLDivElement>()
25
27
  const element = useElementSize(root)
26
28
 
27
29
  const width = computed(() => props.width ? props.width : element.width.value)
28
- const height = computed(() => props.width ? props.width / slideAspect : element.height.value)
30
+ const height = computed(() => props.width ? props.width / slideAspect.value : element.height.value)
29
31
 
30
32
  if (props.width) {
31
33
  watchEffect(() => {
@@ -41,20 +43,22 @@ const screenAspect = computed(() => width.value / height.value)
41
43
  const scale = computed(() => {
42
44
  if (props.scale && !isPrintMode.value)
43
45
  return props.scale
44
- if (screenAspect.value < slideAspect)
45
- return width.value / slideWidth
46
- return height.value * slideAspect / slideWidth
46
+ if (screenAspect.value < slideAspect.value)
47
+ return width.value / slideWidth.value
48
+ return height.value * slideAspect.value / slideWidth.value
47
49
  })
48
50
 
49
51
  const style = computed(() => ({
50
- 'height': `${slideHeight}px`,
51
- 'width': `${slideWidth}px`,
52
+ 'height': `${slideHeight.value}px`,
53
+ 'width': `${slideWidth.value}px`,
52
54
  'transform': `translate(-50%, -50%) scale(${scale.value})`,
53
55
  '--slidev-slide-scale': scale.value,
54
56
  }))
55
57
 
56
58
  const className = computed(() => ({
57
59
  'select-none': !configs.selectable,
60
+ 'slidev-nav-go-forward': clicksDirection.value > 0,
61
+ 'slidev-nav-go-backward': clicksDirection.value < 0,
58
62
  }))
59
63
 
60
64
  if (props.isMain) {
@@ -69,8 +73,8 @@ provideLocal(injectionSlideScale, scale as any)
69
73
  </script>
70
74
 
71
75
  <template>
72
- <div id="slide-container" ref="root" :class="className">
73
- <div id="slide-content" :style="style">
76
+ <div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
77
+ <div id="slide-content" class="slidev-slide-content" :style="style">
74
78
  <slot />
75
79
  </div>
76
80
  <slot name="controls" />
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+
4
+ const timeout = ref(false)
5
+ onMounted(() => {
6
+ setTimeout(() => {
7
+ timeout.value = true
8
+ }, 200)
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <div class="h-full w-full flex items-center justify-center gap-2 slidev-slide-loading">
14
+ <template v-if="timeout">
15
+ <div class="i-svg-spinners-90-ring-with-bg text-xl" />
16
+ <div>Loading slide...</div>
17
+ </template>
18
+ </div>
19
+ </template>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { computed, defineAsyncComponent, defineComponent, h, onMounted, ref, toRef } from 'vue'
3
+ import type { PropType } from 'vue'
4
+ import { provideLocal } from '@vueuse/core'
5
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
+ import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
7
+ import SlideLoading from './SlideLoading.vue'
8
+
9
+ const props = defineProps({
10
+ clicksContext: {
11
+ type: Object as PropType<ClicksContext>,
12
+ required: true,
13
+ },
14
+ renderContext: {
15
+ type: String as PropType<RenderContext>,
16
+ default: 'slide',
17
+ },
18
+ active: {
19
+ type: Boolean,
20
+ default: false,
21
+ },
22
+ is: {
23
+ type: Function as PropType<() => any>,
24
+ required: true,
25
+ },
26
+ route: {
27
+ type: Object as PropType<SlideRoute>,
28
+ required: true,
29
+ },
30
+ })
31
+
32
+ provideLocal(injectionRoute, props.route)
33
+ provideLocal(injectionCurrentPage, ref(props.route.no))
34
+ provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
35
+ provideLocal(injectionActive, toRef(props, 'active'))
36
+ provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
37
+
38
+ const style = computed(() => {
39
+ const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
40
+ return zoom === 1
41
+ ? undefined
42
+ : {
43
+ width: `${100 / zoom}%`,
44
+ height: `${100 / zoom}%`,
45
+ transformOrigin: 'top left',
46
+ transform: `scale(${zoom})`,
47
+ }
48
+ })
49
+
50
+ const SlideComponent = defineAsyncComponent({
51
+ loader: async () => {
52
+ const component = await props.is()
53
+ return defineComponent({
54
+ setup(_, { attrs }) {
55
+ onMounted(() => {
56
+ props.clicksContext.onMounted()
57
+ })
58
+ return () => h(component.default, attrs)
59
+ },
60
+ })
61
+ },
62
+ delay: 300,
63
+ loadingComponent: SlideLoading,
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <component
69
+ :is="SlideComponent"
70
+ :style="style"
71
+ :class="{ 'disable-view-transition': !['slide', 'presenter'].includes(props.renderContext) }"
72
+ />
73
+ </template>
74
+
75
+ <style scoped>
76
+ .disable-view-transition:deep(*) {
77
+ view-transition-name: none !important;
78
+ }
79
+ </style>
@@ -1,27 +1,33 @@
1
1
  <script setup lang="ts">
2
2
  import { TransitionGroup, computed, shallowRef, watch } from 'vue'
3
- import { currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
3
+ import { recomputeAllPoppers } from 'floating-vue'
4
+ import { useNav } from '../composables/useNav'
4
5
  import { getSlideClass } from '../utils'
5
6
  import { useViewTransition } from '../composables/useViewTransition'
6
- import { skipTransition } from '../composables/hmr'
7
- import { usePrimaryClicks } from '../composables/useClicks'
8
- import SlideWrapper from './SlideWrapper'
9
-
10
- // @ts-expect-error virtual module
11
- import GlobalTop from '/@slidev/global-components/top'
12
-
13
- // @ts-expect-error virtual module
14
- import GlobalBottom from '/@slidev/global-components/bottom'
7
+ import { skipTransition } from '../logic/hmr'
8
+ import SlideWrapper from './SlideWrapper.vue'
15
9
  import PresenterMouse from './PresenterMouse.vue'
16
10
 
11
+ import GlobalTop from '#slidev/global-components/top'
12
+ import GlobalBottom from '#slidev/global-components/bottom'
13
+
17
14
  defineProps<{
18
15
  renderContext: 'slide' | 'presenter'
19
16
  }>()
20
17
 
18
+ const {
19
+ currentSlideRoute,
20
+ currentTransition,
21
+ getPrimaryClicks,
22
+ isPresenter,
23
+ nextRoute,
24
+ slides,
25
+ } = useNav()
26
+
21
27
  // preload next route
22
- watch(currentRoute, () => {
23
- if (currentRoute.value?.meta && currentRoute.value.meta.preload !== false)
24
- currentRoute.value.meta.__preloaded = true
28
+ watch(currentSlideRoute, () => {
29
+ if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
30
+ currentSlideRoute.value.meta.__preloaded = true
25
31
  if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
26
32
  nextRoute.value.meta.__preloaded = true
27
33
  }, { immediate: true })
@@ -32,12 +38,14 @@ const DrawingLayer = shallowRef<any>()
32
38
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
33
39
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
34
40
 
35
- const loadedRoutes = computed(() => rawRoutes.filter(r => r.meta?.__preloaded || r === currentRoute.value))
41
+ const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
36
42
 
37
43
  function onAfterLeave() {
38
44
  // After transition, we disable it so HMR won't trigger it again
39
45
  // We will turn it back on `nav.go` so the normal navigation would still work
40
46
  skipTransition.value = true
47
+ // recompute poppers after transition
48
+ recomputeAllPoppers()
41
49
  }
42
50
  </script>
43
51
 
@@ -48,22 +56,25 @@ function onAfterLeave() {
48
56
  <!-- Slides -->
49
57
  <component
50
58
  :is="hasViewTransition ? 'div' : TransitionGroup"
51
- v-bind="skipTransition ? {} : transition"
59
+ v-bind="skipTransition ? {} : currentTransition"
52
60
  id="slideshow"
53
61
  tag="div"
54
62
  @after-leave="onAfterLeave"
55
63
  >
56
- <template v-for="route of loadedRoutes" :key="route.path">
64
+ <div
65
+ v-for="route of loadedRoutes"
66
+ v-show="route === currentSlideRoute"
67
+ :key="route.no"
68
+ >
57
69
  <SlideWrapper
58
- :is="route?.component as any"
59
- v-show="route === currentRoute"
60
- :clicks-context="usePrimaryClicks(route)"
70
+ :is="route.component!"
71
+ :clicks-context="getPrimaryClicks(route)"
61
72
  :class="getSlideClass(route)"
62
73
  :route="route"
63
74
  :render-context="renderContext"
64
75
  class="overflow-hidden"
65
76
  />
66
- </template>
77
+ </div>
67
78
  </component>
68
79
 
69
80
  <!-- Global Top -->
@@ -77,10 +88,13 @@ function onAfterLeave() {
77
88
 
78
89
  <style scoped>
79
90
  #slideshow {
80
- @apply h-full;
91
+ height: 100%;
81
92
  }
82
93
 
83
94
  #slideshow > div {
84
- @apply h-full w-full absolute;
95
+ position: absolute;
96
+ height: 100%;
97
+ width: 100%;
85
98
  }
86
99
  </style>
100
+ ../logic/hmr
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="px-4 py-10 text-center text-red-700 dark:text-red-500 font-bold font-mono">
3
+ An error occurred on this slide. Check the terminal for more information.
4
+ </div>
5
+ </template>
@@ -51,9 +51,15 @@ const props = defineProps({
51
51
  grid-template-rows: repeat(2, 1fr);
52
52
  }
53
53
 
54
- .col-header { grid-area: 1 / 1 / 2 / 3; }
55
- .col-left { grid-area: 2 / 1 / 3 / 2; }
56
- .col-right { grid-area: 2 / 2 / 3 / 3; }
54
+ .col-header {
55
+ grid-area: 1 / 1 / 2 / 3;
56
+ }
57
+ .col-left {
58
+ grid-area: 2 / 1 / 3 / 2;
59
+ }
60
+ .col-right {
61
+ grid-area: 2 / 2 / 3 / 3;
62
+ }
57
63
  .col-bottom {
58
64
  align-self: end;
59
65
  grid-area: 3 / 1 / 3 / 3;
package/logic/overview.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { computed, ref } from 'vue'
2
- import { rawRoutes } from '../routes'
2
+ import { slides } from './slides'
3
3
 
4
4
  // To have same format(.value) as max, wrap it with ref.
5
5
  const min = ref(1)
6
- const max = computed(() => rawRoutes.length)
6
+ const max = computed(() => slides.value.length)
7
7
 
8
8
  export const currentOverviewPage = ref(0)
9
9
  export const overviewRowCount = ref(0)
package/logic/route.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { computed, nextTick, unref } from 'vue'
2
- import { router } from '../routes'
1
+ import type { WritableComputedRef } from 'vue'
2
+ import { computed, nextTick, ref, unref } from 'vue'
3
+ import { useRouter } from 'vue-router'
3
4
 
4
5
  export function useRouteQuery<T extends string | string[]>(
5
6
  name: string,
@@ -7,7 +8,9 @@ export function useRouteQuery<T extends string | string[]>(
7
8
  {
8
9
  mode = 'replace',
9
10
  } = {},
10
- ) {
11
+ ): WritableComputedRef<T> {
12
+ const router = useRouter()
13
+
11
14
  return computed<any>({
12
15
  get() {
13
16
  const data = router.currentRoute.value.query[name]
@@ -19,8 +22,16 @@ export function useRouteQuery<T extends string | string[]>(
19
22
  },
20
23
  set(v) {
21
24
  nextTick(() => {
22
- router[unref(mode) as 'replace' | 'push']({ query: { ...router.currentRoute.value.query, [name]: v } })
25
+ router[unref(mode) as 'replace' | 'push']({
26
+ query: {
27
+ ...router.currentRoute.value.query,
28
+ [name]: `${v}` === defaultValue ? undefined : v,
29
+ },
30
+ })
23
31
  })
24
32
  },
25
- })
33
+ }) as any
26
34
  }
35
+
36
+ // force update collected elements when the route is fully resolved
37
+ export const routeForceRefresh = ref(0)
@@ -0,0 +1,20 @@
1
+ import type { SlideRoute } from '@slidev/types'
2
+ import { slides } from '#slidev/slides'
3
+
4
+ export { slides }
5
+
6
+ export function getSlide(no: number | string) {
7
+ return slides.value.find(
8
+ s => (s.no === +no || s.meta.slide?.frontmatter.routeAlias === no),
9
+ )
10
+ }
11
+
12
+ export function getSlidePath(
13
+ route: SlideRoute | number | string,
14
+ presenter: boolean,
15
+ ) {
16
+ if (typeof route === 'number' || typeof route === 'string')
17
+ route = getSlide(route)!
18
+ const no = route.meta.slide?.frontmatter.routeAlias ?? route.no
19
+ return presenter ? `/presenter/${no}` : `/${no}`
20
+ }