@slidev/client 0.48.0-beta.2 → 0.48.0-beta.21

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 (91) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +14 -6
  4. package/builtin/KaTexBlockWrapper.vue +5 -4
  5. package/builtin/Mermaid.vue +4 -3
  6. package/builtin/Monaco.vue +109 -92
  7. package/builtin/RenderWhen.vue +3 -3
  8. package/builtin/ShikiMagicMove.vue +50 -0
  9. package/builtin/SlideCurrentNo.vue +2 -3
  10. package/builtin/SlidesTotal.vue +3 -4
  11. package/builtin/SlidevVideo.vue +9 -7
  12. package/builtin/Toc.vue +4 -4
  13. package/builtin/TocList.vue +4 -3
  14. package/builtin/Tweet.vue +3 -22
  15. package/builtin/VClick.ts +2 -1
  16. package/builtin/VClickGap.vue +3 -5
  17. package/builtin/VClicks.ts +1 -1
  18. package/composables/useClicks.ts +39 -20
  19. package/composables/useContext.ts +4 -9
  20. package/composables/useNav.ts +182 -44
  21. package/composables/useSwipeControls.ts +40 -0
  22. package/composables/useTocTree.ts +63 -0
  23. package/constants.ts +59 -10
  24. package/context.ts +73 -0
  25. package/env.ts +3 -12
  26. package/internals/ClicksSlider.vue +93 -0
  27. package/internals/Controls.vue +2 -2
  28. package/internals/DrawingControls.vue +39 -9
  29. package/internals/DrawingLayer.vue +3 -3
  30. package/internals/Goto.vue +7 -6
  31. package/internals/IconButton.vue +7 -3
  32. package/internals/InfoDialog.vue +1 -1
  33. package/internals/Modal.vue +1 -1
  34. package/internals/NavControls.vue +11 -10
  35. package/internals/NoteDisplay.vue +131 -8
  36. package/internals/NoteEditable.vue +128 -0
  37. package/internals/NoteStatic.vue +8 -6
  38. package/internals/PrintContainer.vue +8 -6
  39. package/internals/PrintSlide.vue +10 -11
  40. package/internals/PrintSlideClick.vue +14 -18
  41. package/internals/{SlidesOverview.vue → QuickOverview.vue} +31 -20
  42. package/internals/RecordingControls.vue +1 -1
  43. package/internals/RecordingDialog.vue +5 -6
  44. package/internals/{Editor.vue → SideEditor.vue} +9 -5
  45. package/internals/SlideContainer.vue +12 -9
  46. package/internals/SlideLoading.vue +19 -0
  47. package/internals/SlideWrapper.ts +32 -16
  48. package/internals/SlidesShow.vue +20 -18
  49. package/layouts/error.vue +5 -0
  50. package/layouts/two-cols-header.vue +9 -3
  51. package/logic/drawings.ts +13 -10
  52. package/logic/nav-state.ts +20 -0
  53. package/logic/nav.ts +51 -258
  54. package/logic/note.ts +9 -9
  55. package/logic/overview.ts +2 -2
  56. package/logic/route.ts +10 -1
  57. package/logic/slides.ts +19 -0
  58. package/logic/transition.ts +50 -0
  59. package/main.ts +8 -4
  60. package/modules/context.ts +7 -13
  61. package/modules/mermaid.ts +6 -7
  62. package/modules/{directives.ts → v-click.ts} +15 -15
  63. package/modules/v-mark.ts +159 -0
  64. package/package.json +27 -16
  65. package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
  66. package/{internals/NotesView.vue → pages/notes.vue} +7 -6
  67. package/pages/overview.vue +227 -0
  68. package/{internals/Play.vue → pages/play.vue} +17 -13
  69. package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +13 -8
  70. package/{internals/Presenter.vue → pages/presenter.vue} +114 -105
  71. package/{internals/Print.vue → pages/print.vue} +3 -4
  72. package/routes.ts +28 -60
  73. package/setup/codemirror.ts +8 -3
  74. package/setup/monaco.ts +108 -44
  75. package/setup/root.ts +8 -9
  76. package/setup/shortcuts.ts +2 -1
  77. package/shim-vue.d.ts +38 -0
  78. package/shim.d.ts +1 -13
  79. package/state/index.ts +10 -10
  80. package/styles/code.css +7 -3
  81. package/styles/index.css +68 -7
  82. package/styles/katex.css +1 -1
  83. package/styles/layouts-base.css +17 -12
  84. package/styles/monaco.css +27 -0
  85. package/styles/vars.css +1 -0
  86. package/uno.config.ts +14 -2
  87. package/utils.ts +15 -2
  88. package/iframes/monaco/index.css +0 -28
  89. package/iframes/monaco/index.html +0 -7
  90. package/iframes/monaco/index.ts +0 -260
  91. package/internals/NoteEditor.vue +0 -88
@@ -1,20 +1,22 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue'
3
- import { currentRoute } from '../logic/nav'
2
+ import type { ClicksContext } from '@slidev/types'
3
+ import { useSlideInfo } from '../logic/note'
4
4
  import NoteDisplay from './NoteDisplay.vue'
5
5
 
6
6
  const props = defineProps<{
7
+ no?: number
7
8
  class?: string
9
+ clicksContext?: ClicksContext
8
10
  }>()
9
11
 
10
- const note = computed(() => currentRoute.value?.meta?.slide?.note)
11
- const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
12
+ const { info } = useSlideInfo(props.no)
12
13
  </script>
13
14
 
14
15
  <template>
15
16
  <NoteDisplay
16
17
  :class="props.class"
17
- :note="note"
18
- :note-html="noteHtml"
18
+ :note="info?.note"
19
+ :note-html="info?.noteHTML"
20
+ :clicks-context="clicksContext"
19
21
  />
20
22
  </template>
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { parseRangeString } from '@slidev/parser/core'
3
- import { computed, provide } from 'vue'
3
+ import { computed } from 'vue'
4
+ import { provideLocal } from '@vueuse/core'
4
5
  import { configs, slideAspect, slideWidth } from '../env'
5
6
  import { injectionSlideScale } from '../constants'
6
- import { route as currentRoute, rawRoutes } from '../logic/nav'
7
+ import { currentRoute, slides } from '../logic/nav'
7
8
  import PrintSlide from './PrintSlide.vue'
8
9
 
9
10
  const props = defineProps<{
@@ -21,7 +22,8 @@ const scale = computed(() => {
21
22
  return (height.value * slideAspect) / slideWidth
22
23
  })
23
24
 
24
- let routes = rawRoutes
25
+ // In print mode, the routes will never change. So we don't need reactivity here.
26
+ let routes = slides.value
25
27
  if (currentRoute.value.query.range) {
26
28
  const r = parseRangeString(routes.length, currentRoute.value.query.range as string)
27
29
  routes = r.map(i => routes[i - 1])
@@ -31,13 +33,13 @@ const className = computed(() => ({
31
33
  'select-none': !configs.selectable,
32
34
  }))
33
35
 
34
- provide(injectionSlideScale, scale)
36
+ provideLocal(injectionSlideScale, scale)
35
37
  </script>
36
38
 
37
39
  <template>
38
40
  <div id="print-container" :class="className">
39
41
  <div id="print-content">
40
- <PrintSlide v-for="route of routes" :key="route.path" :route="route" />
42
+ <PrintSlide v-for="route of routes" :key="route.no" :route="route" />
41
43
  </div>
42
44
  <slot name="controls" />
43
45
  </div>
@@ -49,6 +51,6 @@ provide(injectionSlideScale, scale)
49
51
  }
50
52
 
51
53
  .print-slide-container {
52
- @apply relative overflow-hidden break-after-page;
54
+ @apply relative overflow-hidden break-after-page translate-0;
53
55
  }
54
56
  </style>
@@ -1,24 +1,23 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
- import { computed } from 'vue'
4
- import { useNav } from '../composables/useNav'
2
+ import type { SlideRoute } from '@slidev/types'
3
+ import { useFixedNav } from '../composables/useNav'
5
4
  import { useFixedClicks } from '../composables/useClicks'
6
5
  import PrintSlideClick from './PrintSlideClick.vue'
7
6
 
8
- const props = defineProps<{ route: RouteRecordRaw }>()
9
-
10
- const route = computed(() => props.route)
11
- const nav = useNav(route)
12
- const clicks0 = useFixedClicks(route.value, 0)[1]
7
+ const { route } = defineProps<{ route: SlideRoute }>()
8
+ const clicks0 = useFixedClicks(route, 0)
13
9
  </script>
14
10
 
15
11
  <template>
16
12
  <PrintSlideClick
17
13
  :clicks-context="clicks0"
18
- :nav="nav"
19
- :route="route"
14
+ :nav="useFixedNav(route, clicks0)"
20
15
  />
21
16
  <template v-if="!clicks0.disabled">
22
- <PrintSlideClick v-for="i of clicks0.total" :key="i" :clicks-context="useFixedClicks(route, i)[1]" :nav="nav" :route="route" />
17
+ <PrintSlideClick
18
+ v-for="i of clicks0.total"
19
+ :key="i"
20
+ :nav="useFixedNav(route, useFixedClicks(route, i))"
21
+ />
23
22
  </template>
24
23
  </template>
@@ -1,25 +1,21 @@
1
1
  <script setup lang="ts">
2
- import type { RouteRecordRaw } from 'vue-router'
3
- import { computed, provide, reactive, shallowRef } from 'vue'
4
- import type { ClicksContext } from '@slidev/types'
2
+ import { computed, reactive, shallowRef } from 'vue'
3
+ import { provideLocal } from '@vueuse/core'
5
4
  import { injectionSlidevContext } from '../constants'
6
5
  import { configs, slideHeight, slideWidth } from '../env'
7
6
  import { getSlideClass } from '../utils'
8
- import type { SlidevContextNav } from '../modules/context'
7
+ import type { SlidevContextNav } from '../composables/useNav'
9
8
  import SlideWrapper from './SlideWrapper'
10
9
 
11
- // @ts-expect-error virtual module
12
- import GlobalTop from '/@slidev/global-components/top'
10
+ import GlobalTop from '#slidev/global-components/top'
11
+ import GlobalBottom from '#slidev/global-components/bottom'
13
12
 
14
- // @ts-expect-error virtual module
15
- import GlobalBottom from '/@slidev/global-components/bottom'
16
-
17
- const props = defineProps<{
18
- clicksContext: ClicksContext
13
+ const { nav } = defineProps<{
19
14
  nav: SlidevContextNav
20
- route: RouteRecordRaw
21
15
  }>()
22
16
 
17
+ const route = computed(() => nav.currentSlideRoute.value)
18
+
23
19
  const style = computed(() => ({
24
20
  height: `${slideHeight}px`,
25
21
  width: `${slideWidth}px`,
@@ -30,11 +26,11 @@ if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
30
26
  import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
31
27
 
32
28
  const id = computed(() =>
33
- `${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')}`,
34
30
  )
35
31
 
36
- provide(injectionSlidevContext, reactive({
37
- nav: props.nav,
32
+ provideLocal(injectionSlidevContext, reactive({
33
+ nav,
38
34
  configs,
39
35
  themeConfigs: computed(() => configs.themeConfig),
40
36
  }))
@@ -45,8 +41,8 @@ provide(injectionSlidevContext, reactive({
45
41
  <GlobalBottom />
46
42
 
47
43
  <SlideWrapper
48
- :is="route?.component!"
49
- :clicks-context="clicksContext"
44
+ :is="route.component!"
45
+ :clicks-context="nav.clicksContext.value"
50
46
  :class="getSlideClass(route)"
51
47
  :route="route"
52
48
  />
@@ -57,7 +53,7 @@ provide(injectionSlidevContext, reactive({
57
53
  && DrawingPreview
58
54
  "
59
55
  >
60
- <DrawingPreview :page="+route.path" />
56
+ <DrawingPreview :page="route.no" />
61
57
  </template>
62
58
 
63
59
  <GlobalTop />
@@ -1,12 +1,12 @@
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'
5
+ import { currentSlideNo, go as goSlide, slides } from '../logic/nav'
7
6
  import { currentOverviewPage, overviewRowCount } from '../logic/overview'
8
7
  import { useFixedClicks } from '../composables/useClicks'
9
8
  import { getSlideClass } from '../utils'
9
+ import { CLICKS_MAX } from '../constants'
10
10
  import SlideContainer from './SlideContainer.vue'
11
11
  import SlideWrapper from './SlideWrapper'
12
12
  import DrawingPreview from './DrawingPreview.vue'
@@ -14,7 +14,7 @@ import IconButton from './IconButton.vue'
14
14
 
15
15
  const props = defineProps<{ modelValue: boolean }>()
16
16
 
17
- const emit = defineEmits([])
17
+ const emit = defineEmits(['update:modelValue'])
18
18
  const value = useVModel(props, 'modelValue', emit)
19
19
 
20
20
  function close() {
@@ -78,17 +78,17 @@ useEventListener('keypress', (e) => {
78
78
  keyboardBuffer.value += String(num)
79
79
 
80
80
  // beyond the number of slides, reset
81
- if (+keyboardBuffer.value >= rawRoutes.length) {
81
+ if (+keyboardBuffer.value >= slides.value.length) {
82
82
  keyboardBuffer.value = ''
83
83
  return
84
84
  }
85
85
 
86
- const extactMatch = rawRoutes.findIndex(i => i.path === keyboardBuffer.value)
86
+ const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)
87
87
  if (extactMatch !== -1)
88
88
  currentOverviewPage.value = extactMatch + 1
89
89
 
90
90
  // 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) {
91
+ if (+keyboardBuffer.value * 10 > slides.value.length) {
92
92
  go(+keyboardBuffer.value)
93
93
  keyboardBuffer.value = ''
94
94
  }
@@ -97,7 +97,7 @@ useEventListener('keypress', (e) => {
97
97
  watchEffect(() => {
98
98
  // Watch currentPage, make sure every time we open overview,
99
99
  // we focus on the right page.
100
- currentOverviewPage.value = currentPage.value
100
+ currentOverviewPage.value = currentSlideNo.value
101
101
  // Watch rowCount, make sure up and down shortcut work correctly.
102
102
  overviewRowCount.value = rowCount.value
103
103
  })
@@ -112,7 +112,7 @@ watchEffect(() => {
112
112
  >
113
113
  <div
114
114
  v-show="value"
115
- class="bg-main !bg-opacity-75 p-16 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
115
+ class="bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
116
116
  @click="close()"
117
117
  >
118
118
  <div
@@ -120,18 +120,17 @@ watchEffect(() => {
120
120
  :style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
121
121
  >
122
122
  <div
123
- v-for="(route, idx) of rawRoutes"
124
- :key="route.path"
123
+ v-for="(route, idx) of slides"
124
+ :key="route.no"
125
125
  class="relative"
126
126
  >
127
127
  <div
128
- class="inline-block border rounded border-opacity-50 overflow-hidden bg-main hover:border-$slidev-theme-primary transition"
129
- :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-$slidev-theme-primary' : 'border-gray-400'"
130
- :style="themeVars"
131
- @click="go(+route.path)"
128
+ class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
129
+ :class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
130
+ @click="go(route.no)"
132
131
  >
133
132
  <SlideContainer
134
- :key="route.path"
133
+ :key="route.no"
135
134
  :width="cardWidth"
136
135
  :clicks-disabled="true"
137
136
  class="pointer-events-none"
@@ -139,12 +138,12 @@ watchEffect(() => {
139
138
  <SlideWrapper
140
139
  :is="route.component"
141
140
  v-if="route?.component"
142
- :clicks-context="useFixedClicks(route, 99999)[1]"
141
+ :clicks-context="useFixedClicks(route, CLICKS_MAX)"
143
142
  :class="getSlideClass(route)"
144
143
  :route="route"
145
144
  render-context="overview"
146
145
  />
147
- <DrawingPreview :page="+route.path" />
146
+ <DrawingPreview :page="route.no" />
148
147
  </SlideContainer>
149
148
  </div>
150
149
  <div
@@ -163,7 +162,19 @@ watchEffect(() => {
163
162
  </div>
164
163
  </div>
165
164
  </Transition>
166
- <IconButton v-if="value" title="Close" class="fixed text-2xl top-4 right-4 text-gray-400" @click="close">
167
- <carbon:close />
168
- </IconButton>
165
+ <div v-if="value" class="fixed top-4 right-4 text-gray-400 flex flex-col items-center gap-2">
166
+ <IconButton title="Close" class="text-2xl" @click="close">
167
+ <carbon:close />
168
+ </IconButton>
169
+ <IconButton
170
+ as="a"
171
+ title="Slides Overview"
172
+ target="_blank"
173
+ href="/overview"
174
+ tab-index="-1"
175
+ class="text-2xl"
176
+ >
177
+ <carbon:list-boxes />
178
+ </IconButton>
179
+ </div>
169
180
  </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,19 +108,18 @@ async function start() {
108
108
  }
109
109
  }
110
110
 
111
- input[type="text"] {
112
- @apply border border-gray-400 rounded px-2 py-1;
111
+ input[type='text'] {
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
- @apply bg-gray-400 text-white px-4 py-1 rounded border-b-2 border-gray-500;
122
- @apply bg-opacity-50 border-opacity-50;
123
- @apply hover:(bg-opacity-75 border-opacity-75)
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);
124
123
  }
125
124
  }
126
125
  </style>
@@ -3,7 +3,7 @@ import { throttledWatch, useEventListener } from '@vueuse/core'
3
3
  import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
4
  import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
5
  import { useCodeMirror } from '../setup/codemirror'
6
- import { currentSlideId, openInEditor } from '../logic/nav'
6
+ import { currentSlideNo, openInEditor } from '../logic/nav'
7
7
  import { useDynamicSlideInfo } from '../logic/note'
8
8
  import IconButton from './IconButton.vue'
9
9
 
@@ -19,7 +19,7 @@ const frontmatter = ref<any>({})
19
19
  const contentInput = ref<HTMLTextAreaElement>()
20
20
  const noteInput = ref<HTMLTextAreaElement>()
21
21
 
22
- const { info, update } = useDynamicSlideInfo(currentSlideId)
22
+ const { info, update } = useDynamicSlideInfo(currentSlideNo)
23
23
 
24
24
  watch(
25
25
  info,
@@ -38,7 +38,6 @@ watch(
38
38
  async function save() {
39
39
  dirty.value = false
40
40
  await update({
41
- raw: null!,
42
41
  note: note.value || undefined,
43
42
  content: content.value,
44
43
  // frontmatter: frontmatter.value,
@@ -103,6 +102,11 @@ onMounted(async () => {
103
102
  noteEditor.refresh()
104
103
  })
105
104
  })
105
+
106
+ watch(currentSlideNo, () => {
107
+ contentEditor.clearHistory()
108
+ noteEditor.clearHistory()
109
+ }, { flush: 'post' })
106
110
  })
107
111
 
108
112
  const handlerDown = ref(false)
@@ -168,13 +172,13 @@ throttledWatch(
168
172
  <div class="flex pb-2 text-xl -mt-1">
169
173
  <div class="mr-4 rounded flex">
170
174
  <IconButton
171
- title="Switch to content tab" :class="tab === 'content' ? 'text-$slidev-theme-primary' : ''"
175
+ title="Switch to content tab" :class="tab === 'content' ? 'text-primary' : ''"
172
176
  @click="switchTab('content')"
173
177
  >
174
178
  <carbon:account />
175
179
  </IconButton>
176
180
  <IconButton
177
- title="Switch to notes tab" :class="tab === 'note' ? 'text-$slidev-theme-primary' : ''"
181
+ title="Switch to notes tab" :class="tab === 'note' ? 'text-primary' : ''"
178
182
  @click="switchTab('note')"
179
183
  >
180
184
  <carbon:align-box-bottom-right />
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { useElementSize, useStyleTag } from '@vueuse/core'
3
- import { computed, provide, ref, watchEffect } from 'vue'
2
+ import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
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 { clicksDirection, isPrintMode } from '../logic/nav'
7
7
 
8
8
  const props = defineProps({
9
9
  width: {
@@ -47,13 +47,16 @@ const scale = computed(() => {
47
47
  })
48
48
 
49
49
  const style = computed(() => ({
50
- height: `${slideHeight}px`,
51
- width: `${slideWidth}px`,
52
- transform: `translate(-50%, -50%) scale(${scale.value})`,
50
+ 'height': `${slideHeight}px`,
51
+ 'width': `${slideWidth}px`,
52
+ 'transform': `translate(-50%, -50%) scale(${scale.value})`,
53
+ '--slidev-slide-scale': scale.value,
53
54
  }))
54
55
 
55
56
  const className = computed(() => ({
56
57
  'select-none': !configs.selectable,
58
+ 'slidev-nav-go-forward': clicksDirection.value > 0,
59
+ 'slidev-nav-go-backward': clicksDirection.value < 0,
57
60
  }))
58
61
 
59
62
  if (props.isMain) {
@@ -64,12 +67,12 @@ if (props.isMain) {
64
67
  `))
65
68
  }
66
69
 
67
- provide(injectionSlideScale, scale as any)
70
+ provideLocal(injectionSlideScale, scale as any)
68
71
  </script>
69
72
 
70
73
  <template>
71
- <div id="slide-container" ref="root" :class="className">
72
- <div id="slide-content" :style="style">
74
+ <div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
75
+ <div id="slide-content" class="slidev-slide-content" :style="style">
73
76
  <slot />
74
77
  </div>
75
78
  <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">
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>
@@ -1,7 +1,9 @@
1
- import { defineComponent, h, provide, ref, toRef } from 'vue'
1
+ import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
2
2
  import type { PropType } from 'vue'
3
- import type { ClicksContext, RenderContext } from '@slidev/types'
3
+ import { provideLocal } from '@vueuse/core'
4
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
4
5
  import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
6
+ import SlideLoading from './SlideLoading.vue'
5
7
 
6
8
  export default defineComponent({
7
9
  name: 'SlideWrapper',
@@ -19,24 +21,38 @@ export default defineComponent({
19
21
  default: false,
20
22
  },
21
23
  is: {
22
- type: Object,
23
- default: undefined,
24
+ required: true,
24
25
  },
25
26
  route: {
26
- type: Object,
27
- default: undefined,
27
+ type: Object as PropType<SlideRoute>,
28
+ required: true,
28
29
  },
29
30
  },
30
31
  setup(props) {
31
- provide(injectionRoute, props.route as any)
32
- provide(injectionCurrentPage, ref(+props.route?.path))
33
- provide(injectionRenderContext, ref(props.renderContext as RenderContext))
34
- provide(injectionActive, toRef(props, 'active'))
35
- provide(injectionClicksContext, toRef(props, 'clicksContext'))
36
- },
37
- render() {
38
- if (this.$props.is)
39
- return h(this.$props.is)
40
- return this.$slots?.default?.()
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: (props.is as any),
52
+ delay: 300,
53
+ loadingComponent: SlideLoading,
54
+ })
55
+
56
+ return () => h(SlideComponent, { style: style.value })
41
57
  },
42
58
  })
@@ -1,27 +1,24 @@
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 { currentSlideRoute, currentTransition, isPresenter, nextRoute, slides } from '../logic/nav'
4
4
  import { getSlideClass } from '../utils'
5
5
  import { useViewTransition } from '../composables/useViewTransition'
6
6
  import { skipTransition } from '../composables/hmr'
7
7
  import { usePrimaryClicks } from '../composables/useClicks'
8
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'
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
 
21
18
  // preload next route
22
- watch(currentRoute, () => {
23
- if (currentRoute.value?.meta && currentRoute.value.meta.preload !== false)
24
- currentRoute.value.meta.__preloaded = true
19
+ watch(currentSlideRoute, () => {
20
+ if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
21
+ currentSlideRoute.value.meta.__preloaded = true
25
22
  if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
26
23
  nextRoute.value.meta.__preloaded = true
27
24
  }, { immediate: true })
@@ -32,7 +29,7 @@ const DrawingLayer = shallowRef<any>()
32
29
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
33
30
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
34
31
 
35
- const loadedRoutes = computed(() => rawRoutes.filter(r => r.meta?.__preloaded || r === currentRoute.value))
32
+ const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
36
33
 
37
34
  function onAfterLeave() {
38
35
  // After transition, we disable it so HMR won't trigger it again
@@ -48,22 +45,25 @@ function onAfterLeave() {
48
45
  <!-- Slides -->
49
46
  <component
50
47
  :is="hasViewTransition ? 'div' : TransitionGroup"
51
- v-bind="skipTransition ? {} : transition"
48
+ v-bind="skipTransition ? {} : currentTransition"
52
49
  id="slideshow"
53
50
  tag="div"
54
51
  @after-leave="onAfterLeave"
55
52
  >
56
- <template v-for="route of loadedRoutes" :key="route.path">
53
+ <div
54
+ v-for="route of loadedRoutes"
55
+ v-show="route === currentSlideRoute"
56
+ :key="route.no"
57
+ >
57
58
  <SlideWrapper
58
- :is="route?.component as any"
59
- v-show="route === currentRoute"
59
+ :is="route.component!"
60
60
  :clicks-context="usePrimaryClicks(route)"
61
61
  :class="getSlideClass(route)"
62
62
  :route="route"
63
63
  :render-context="renderContext"
64
64
  class="overflow-hidden"
65
65
  />
66
- </template>
66
+ </div>
67
67
  </component>
68
68
 
69
69
  <!-- Global Top -->
@@ -77,10 +77,12 @@ function onAfterLeave() {
77
77
 
78
78
  <style scoped>
79
79
  #slideshow {
80
- @apply h-full;
80
+ height: 100%;
81
81
  }
82
82
 
83
83
  #slideshow > div {
84
- @apply h-full w-full absolute;
84
+ position: absolute;
85
+ height: 100%;
86
+ width: 100%;
85
87
  }
86
88
  </style>
@@ -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;