@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
@@ -2,10 +2,12 @@
2
2
  import { computed } from 'vue'
3
3
  import { useStyleTag } from '@vueuse/core'
4
4
  import { useHead } from '@unhead/vue'
5
- import { configs, themeVars } from '../../env'
6
- import { rawRoutes, total } from '../../logic/nav'
5
+ import { useNav } from '../../composables/useNav'
6
+ import { configs } from '../../env'
7
7
  import NoteDisplay from '../../internals/NoteDisplay.vue'
8
8
 
9
+ const { slides, total } = useNav()
10
+
9
11
  useStyleTag(`
10
12
  @page {
11
13
  size: A4;
@@ -28,13 +30,13 @@ useHead({
28
30
  title: `Notes - ${configs.title}`,
29
31
  })
30
32
 
31
- const slidesWithNote = computed(() => rawRoutes
33
+ const slidesWithNote = computed(() => slides.value
32
34
  .map(route => route.meta?.slide)
33
35
  .filter(slide => slide !== undefined && slide.noteHTML !== ''))
34
36
  </script>
35
37
 
36
38
  <template>
37
- <div id="page-root" :style="themeVars">
39
+ <div id="page-root">
38
40
  <div class="m-4">
39
41
  <div class="mb-10">
40
42
  <h1 class="text-4xl font-bold mt-2">
@@ -56,7 +58,10 @@ const slidesWithNote = computed(() => rawRoutes
56
58
  <div class="flex-auto" />
57
59
  </div>
58
60
  </h2>
59
- <NoteDisplay :note-html="slide!.noteHTML" class="max-w-full" />
61
+ <NoteDisplay
62
+ :note-html="slide!.noteHTML"
63
+ class="max-w-full"
64
+ />
60
65
  </div>
61
66
  <hr v-if="index < slidesWithNote.length - 1" class="border-main mb-8">
62
67
  </div>
@@ -2,31 +2,46 @@
2
2
  import { useHead } from '@unhead/vue'
3
3
  import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
4
4
  import { useMouse, useWindowFocus } from '@vueuse/core'
5
- import { clicksContext, currentPage, currentRoute, currentSlideId, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
5
+ import { useSwipeControls } from '../composables/useSwipeControls'
6
6
  import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
7
- import { configs, themeVars } from '../env'
7
+ import { configs } from '../env'
8
8
  import { sharedState } from '../state/shared'
9
9
  import { registerShortcuts } from '../logic/shortcuts'
10
10
  import { getSlideClass } from '../utils'
11
11
  import { useTimer } from '../logic/utils'
12
- import { isDrawing } from '../logic/drawings'
13
- import { useFixedClicks } from '../composables/useClicks'
14
- import SlideWrapper from '../internals/SlideWrapper'
12
+ import { createFixedClicks } from '../composables/useClicks'
13
+ import SlideWrapper from '../internals/SlideWrapper.vue'
15
14
  import SlideContainer from '../internals/SlideContainer.vue'
16
15
  import NavControls from '../internals/NavControls.vue'
17
- import SlidesOverview from '../internals/SlidesOverview.vue'
18
- import NoteEditor from '../internals/NoteEditor.vue'
16
+ import QuickOverview from '../internals/QuickOverview.vue'
17
+ import NoteEditable from '../internals/NoteEditable.vue'
19
18
  import NoteStatic from '../internals/NoteStatic.vue'
20
19
  import Goto from '../internals/Goto.vue'
21
20
  import SlidesShow from '../internals/SlidesShow.vue'
22
21
  import DrawingControls from '../internals/DrawingControls.vue'
23
22
  import IconButton from '../internals/IconButton.vue'
23
+ import ClicksSlider from '../internals/ClicksSlider.vue'
24
+ import { useNav } from '../composables/useNav'
25
+ import { useDrawings } from '../composables/useDrawings'
24
26
 
25
27
  const main = ref<HTMLDivElement>()
26
28
 
27
29
  registerShortcuts()
28
30
  useSwipeControls(main)
29
31
 
32
+ const {
33
+ clicksContext,
34
+ currentSlideNo,
35
+ currentSlideRoute,
36
+ hasNext,
37
+ nextRoute,
38
+ slides,
39
+ queryClicks,
40
+ getPrimaryClicks,
41
+ total,
42
+ } = useNav()
43
+ const { isDrawing } = useDrawings()
44
+
30
45
  const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
31
46
  useHead({
32
47
  title: `Presenter - ${slideTitle}`,
@@ -36,25 +51,32 @@ const notesEditing = ref(false)
36
51
 
37
52
  const { timer, resetTimer } = useTimer()
38
53
 
39
- const clicksCtxMap = rawRoutes.map(route => useFixedClicks(route))
54
+ const clicksCtxMap = computed(() => slides.value.map(route => createFixedClicks(route)))
40
55
  const nextFrame = computed(() => {
41
56
  if (clicksContext.value.current < clicksContext.value.total)
42
- return [currentRoute.value!, clicksContext.value.current + 1] as const
57
+ return [currentSlideRoute.value!, clicksContext.value.current + 1] as const
43
58
  else if (hasNext.value)
44
59
  return [nextRoute.value!, 0] as const
45
60
  else
46
61
  return null
47
62
  })
63
+
48
64
  const nextFrameClicksCtx = computed(() => {
49
- return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
65
+ return nextFrame.value && clicksCtxMap.value[nextFrame.value[0].no - 1]
50
66
  })
51
- watch([currentRoute, queryClicks], () => {
52
- nextFrameClicksCtx.value && (nextFrameClicksCtx.value[0].value = nextFrame.value![1])
53
- }, { immediate: true })
54
67
 
55
- const Editor = shallowRef<any>()
68
+ watch(
69
+ [currentSlideRoute, queryClicks],
70
+ () => {
71
+ if (nextFrameClicksCtx.value)
72
+ nextFrameClicksCtx.value.current = nextFrame.value![1]
73
+ },
74
+ { immediate: true },
75
+ )
76
+
77
+ const SideEditor = shallowRef<any>()
56
78
  if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
57
- import('../internals/Editor.vue').then(v => Editor.value = v.default)
79
+ import('../internals/SideEditor.vue').then(v => SideEditor.value = v.default)
58
80
 
59
81
  // sync presenter cursor
60
82
  onMounted(() => {
@@ -86,72 +108,64 @@ onMounted(() => {
86
108
  <template>
87
109
  <div class="bg-main h-full slidev-presenter">
88
110
  <div class="grid-container" :class="`layout${presenterLayout}`">
89
- <div class="grid-section top flex">
90
- <img src="../assets/logo-title-horizontal.png" class="ml-2 my-auto h-10 py-1 lg:h-14 lg:py-2" style="height: 3.5rem;" alt="Slidev logo">
91
- <div class="flex-auto" />
92
- <div
93
- class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
94
- opacity="50 hover:100"
95
- @click="resetTimer"
96
- >
97
- <carbon:time class="absolute" />
98
- <carbon:renew class="absolute opacity-0" />
99
- </div>
100
- <div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
101
- {{ timer }}
102
- </div>
103
- </div>
104
- <div ref="main" class="relative grid-section main flex flex-col p-2 lg:p-4" :style="themeVars">
111
+ <div ref="main" class="relative grid-section main flex flex-col">
105
112
  <SlideContainer
106
113
  key="main"
107
- class="h-full w-full"
114
+ class="h-full w-full p-2 lg:p-4 flex-auto"
108
115
  >
109
116
  <template #default>
110
117
  <SlidesShow render-context="presenter" />
111
118
  </template>
112
119
  </SlideContainer>
113
- <div class="context">
114
- current
120
+ <ClicksSlider
121
+ :key="currentSlideRoute?.no"
122
+ :clicks-context="getPrimaryClicks(currentSlideRoute)"
123
+ class="w-full pb2 px4 flex-none"
124
+ />
125
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
126
+ Current
115
127
  </div>
116
128
  </div>
117
- <div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
129
+ <div class="relative grid-section next flex flex-col p-2 lg:p-4">
118
130
  <SlideContainer
119
131
  v-if="nextFrame && nextFrameClicksCtx"
120
132
  key="next"
121
133
  class="h-full w-full"
122
134
  >
123
135
  <SlideWrapper
124
- :is="nextFrame[0].component as any"
125
- :key="nextFrame[0].path"
126
- :clicks-context="nextFrameClicksCtx[1]"
136
+ :is="nextFrame[0].component!"
137
+ :key="nextFrame[0].no"
138
+ :clicks-context="nextFrameClicksCtx"
127
139
  :class="getSlideClass(nextFrame[0])"
128
140
  :route="nextFrame[0]"
129
141
  render-context="previewNext"
130
142
  />
131
143
  </SlideContainer>
132
- <div class="context">
133
- next
144
+ <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
145
+ Next
134
146
  </div>
135
147
  </div>
136
148
  <!-- Notes -->
137
- <div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && Editor && showEditor" class="grid-section note of-auto">
138
- <Editor />
149
+ <div v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__ && SideEditor && showEditor" class="grid-section note of-auto">
150
+ <SideEditor />
139
151
  </div>
140
152
  <div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
141
- <NoteEditor
153
+ <NoteEditable
142
154
  v-if="__DEV__"
143
- :key="`edit-${currentSlideId}`"
144
- :no="currentSlideId"
155
+ :key="`edit-${currentSlideNo}`"
156
+ v-model:editing="notesEditing"
157
+ :no="currentSlideNo"
145
158
  class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
146
- :editing="notesEditing"
159
+ :clicks-context="clicksContext"
147
160
  :style="{ fontSize: `${presenterNotesFontSize}em` }"
148
161
  />
149
162
  <NoteStatic
150
163
  v-else
151
- :key="`static-${currentSlideId}`"
152
- :no="currentSlideId"
164
+ :key="`static-${currentSlideNo}`"
165
+ :no="currentSlideNo"
153
166
  class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
154
167
  :style="{ fontSize: `${presenterNotesFontSize}em` }"
168
+ :clicks-context="clicksContext"
155
169
  />
156
170
  <div class="border-t border-main py-1 px-2 text-sm">
157
171
  <IconButton title="Increase font size" @click="increasePresenterFontSize">
@@ -169,116 +183,120 @@ onMounted(() => {
169
183
  </IconButton>
170
184
  </div>
171
185
  </div>
172
- <div class="grid-section bottom">
186
+ <div class="grid-section bottom flex">
173
187
  <NavControls :persist="true" />
188
+ <div flex-auto />
189
+ <div
190
+ class="timer-btn my-auto relative w-22px h-22px cursor-pointer text-lg"
191
+ opacity="50 hover:100"
192
+ @click="resetTimer"
193
+ >
194
+ <carbon:time class="absolute" />
195
+ <carbon:renew class="absolute opacity-0" />
196
+ </div>
197
+ <div class="text-2xl pl-2 pr-6 my-auto tabular-nums">
198
+ {{ timer }}
199
+ </div>
174
200
  </div>
175
201
  <DrawingControls v-if="__SLIDEV_FEATURE_DRAWINGS__" />
176
202
  </div>
177
203
  <div class="progress-bar">
178
204
  <div
179
- class="progress h-2px bg-primary transition-all"
180
- :style="{ width: `${(currentPage - 1) / (total - 1) * 100}%` }"
205
+ class="progress h-3px bg-primary transition-all"
206
+ :style="{ width: `${(currentSlideNo - 1) / (total - 1) * 100}%` }"
181
207
  />
182
208
  </div>
183
209
  </div>
184
210
  <Goto />
185
- <SlidesOverview v-model="showOverview" />
211
+ <QuickOverview v-model="showOverview" />
186
212
  </template>
187
213
 
188
- <style lang="postcss" scoped>
214
+ <style scoped>
189
215
  .slidev-presenter {
190
216
  --slidev-controls-foreground: current;
191
217
  }
192
218
 
193
- .timer-btn:hover {
194
- & > :first-child {
195
- @apply opacity-0;
196
- }
197
- & > :last-child {
198
- @apply opacity-100;
199
- }
219
+ .timer-btn:hover > :first-child {
220
+ opacity: 0;
221
+ }
222
+ .timer-btn:hover > :last-child {
223
+ opacity: 1;
200
224
  }
201
225
 
202
226
  .section-title {
203
- @apply px-4 py-2 text-xl;
227
+ --uno: px-4 py-2 text-xl;
204
228
  }
205
229
 
206
230
  .grid-container {
207
- @apply h-full w-full bg-gray-400 bg-opacity-15;
231
+ --uno: bg-gray/20;
232
+ height: 100%;
233
+ width: 100%;
208
234
  display: grid;
209
235
  gap: 1px 1px;
210
236
  }
211
237
 
212
238
  .grid-container.layout1 {
213
239
  grid-template-columns: 1fr 1fr;
214
- grid-template-rows: min-content 2fr 1fr min-content;
240
+ grid-template-rows: 2fr 1fr min-content;
215
241
  grid-template-areas:
216
- "top top"
217
- "main main"
218
- "note next"
219
- "bottom bottom";
242
+ 'main main'
243
+ 'note next'
244
+ 'bottom bottom';
220
245
  }
221
246
 
222
247
  .grid-container.layout2 {
223
248
  grid-template-columns: 3fr 2fr;
224
- grid-template-rows: min-content 2fr 1fr min-content;
249
+ grid-template-rows: 2fr 1fr min-content;
225
250
  grid-template-areas:
226
- "top top"
227
- "note main"
228
- "note next"
229
- "bottom bottom";
251
+ 'note main'
252
+ 'note next'
253
+ 'bottom bottom';
230
254
  }
231
255
 
232
256
  @media (max-aspect-ratio: 3/5) {
233
257
  .grid-container.layout1 {
234
258
  grid-template-columns: 1fr;
235
- grid-template-rows: min-content 1fr 1fr 1fr min-content;
259
+ grid-template-rows: 1fr 1fr 1fr min-content;
236
260
  grid-template-areas:
237
- "top"
238
- "main"
239
- "note"
240
- "next"
241
- "bottom";
261
+ 'main'
262
+ 'note'
263
+ 'next'
264
+ 'bottom';
242
265
  }
243
266
  }
244
267
 
245
268
  @media (min-aspect-ratio: 1/1) {
246
269
  .grid-container.layout1 {
247
270
  grid-template-columns: 1fr 1.1fr 0.9fr;
248
- grid-template-rows: min-content 1fr 2fr min-content;
271
+ grid-template-rows: 1fr 2fr min-content;
249
272
  grid-template-areas:
250
- "top top top"
251
- "main main next"
252
- "main main note"
253
- "bottom bottom bottom";
273
+ 'main main next'
274
+ 'main main note'
275
+ 'bottom bottom bottom';
254
276
  }
255
277
  }
256
278
 
257
279
  .progress-bar {
258
- @apply fixed left-0 right-0 bottom-0;
280
+ --uno: fixed left-0 right-0 top-0;
259
281
  }
260
282
 
261
283
  .grid-section {
262
- @apply bg-main;
263
-
264
- &.top {
265
- grid-area: top;
266
- }
267
- &.main {
268
- grid-area: main;
269
- }
270
- &.next {
271
- grid-area: next;
272
- }
273
- &.note {
274
- grid-area: note;
275
- }
276
- &.bottom {
277
- grid-area: bottom;
278
- }
284
+ --uno: bg-main;
279
285
  }
280
286
 
281
- .context {
282
- @apply absolute top-0 left-0 px-1 text-xs bg-gray-400 bg-opacity-50 opacity-75 rounded-br-md;
287
+ .grid-section.top {
288
+ grid-area: top;
289
+ }
290
+ .grid-section.main {
291
+ grid-area: main;
292
+ }
293
+ .grid-section.next {
294
+ grid-area: next;
295
+ }
296
+ .grid-section.note {
297
+ grid-area: note;
298
+ }
299
+ .grid-section.bottom {
300
+ grid-area: bottom;
283
301
  }
284
- </style>
302
+ </style>../composables/drawings
package/pages/print.vue CHANGED
@@ -1,10 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { watchEffect } from 'vue'
3
3
  import { windowSize } from '../state'
4
- import { isPrintMode } from '../logic/nav'
5
- import { themeVars } from '../env'
6
4
  import PrintContainer from '../internals/PrintContainer.vue'
7
5
  import PrintStyle from '../internals/PrintStyle.vue'
6
+ import { useNav } from '../composables/useNav'
7
+
8
+ const { isPrintMode } = useNav()
8
9
 
9
10
  watchEffect(() => {
10
11
  if (isPrintMode)
@@ -16,7 +17,7 @@ watchEffect(() => {
16
17
 
17
18
  <template>
18
19
  <PrintStyle v-if="isPrintMode" />
19
- <div id="page-root" class="grid grid-cols-[1fr_max-content]" :style="themeVars">
20
+ <div id="page-root" class="grid grid-cols-[1fr_max-content]">
20
21
  <PrintContainer
21
22
  class="w-full h-full"
22
23
  :style="{ background: 'var(--slidev-slide-container-background, black)' }"
package/routes.ts CHANGED
@@ -1,26 +1,7 @@
1
1
  import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
2
- import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
3
- import type { TransitionGroupProps } from 'vue'
4
- import type { ClicksContext, SlideInfo } from '@slidev/types'
5
-
6
- // @ts-expect-error missing types
7
- import _rawRoutes, { redirects } from '/@slidev/routes'
8
-
9
- // @ts-expect-error missing types
10
- import _configs from '/@slidev/configs'
11
-
12
- export const rawRoutes = _rawRoutes as RouteRecordRaw[]
2
+ import configs from '#slidev/configs'
13
3
 
14
4
  export const routes: RouteRecordRaw[] = [
15
- {
16
- name: 'play',
17
- path: '/',
18
- component: () => import('./pages/play.vue'),
19
- children: [
20
- ...rawRoutes,
21
- ...redirects,
22
- ],
23
- },
24
5
  {
25
6
  name: 'print',
26
7
  path: '/print',
@@ -29,17 +10,16 @@ export const routes: RouteRecordRaw[] = [
29
10
 
30
11
  // Redirects
31
12
  { path: '', redirect: { path: '/1' } },
32
- { path: '/:pathMatch(.*)', redirect: { path: '/1' } },
33
13
  ]
34
14
 
35
15
  if (__SLIDEV_FEATURE_PRESENTER__) {
36
16
  function passwordGuard(to: RouteLocationNormalized) {
37
- if (!_configs.remote || _configs.remote === to.query.password)
17
+ if (!configs.remote || configs.remote === to.query.password)
38
18
  return true
39
- if (_configs.remote && to.query.password === undefined) {
19
+ if (configs.remote && to.query.password === undefined) {
40
20
  // eslint-disable-next-line no-alert
41
21
  const password = prompt('Enter password')
42
- if (_configs.remote === password)
22
+ if (configs.remote === password)
43
23
  return true
44
24
  }
45
25
  if (to.params.no)
@@ -81,34 +61,8 @@ if (__SLIDEV_FEATURE_PRESENTER__) {
81
61
  })
82
62
  }
83
63
 
84
- export const router = createRouter({
85
- history: __SLIDEV_HASH_ROUTE__
86
- ? createWebHashHistory(import.meta.env.BASE_URL)
87
- : createWebHistory(import.meta.env.BASE_URL),
88
- routes,
64
+ routes.push({
65
+ name: 'play',
66
+ path: '/:no',
67
+ component: () => import('./pages/play.vue'),
89
68
  })
90
-
91
- declare module 'vue-router' {
92
- interface RouteMeta {
93
- // inherited from frontmatter
94
- layout: string
95
- name?: string
96
- class?: string
97
- clicks?: number
98
- transition?: string | TransitionGroupProps | undefined
99
- preload?: boolean
100
-
101
- // slide info
102
- slide?: Omit<SlideInfo, 'source'> & {
103
- noteHTML: string
104
- filepath: string
105
- start: number
106
- id: number
107
- no: number
108
- }
109
-
110
- // private fields
111
- __clicksContext: null | ClicksContext
112
- __preloaded?: boolean
113
- }
114
- }
@@ -0,0 +1,164 @@
1
+ import { createSingletonPromise } from '@antfu/utils'
2
+ import type { CodeRunner, CodeRunnerContext, CodeRunnerOutput, CodeRunnerOutputText, CodeRunnerOutputs } from '@slidev/types'
3
+ import type { CodeToHastOptions } from 'shiki'
4
+ import { isDark } from '../logic/dark'
5
+ import setups from '#slidev/setups/code-runners'
6
+
7
+ export default createSingletonPromise(async () => {
8
+ const runners: Record<string, CodeRunner> = {
9
+ javascript: runJavaScript,
10
+ js: runJavaScript,
11
+ typescript: runTypeScript,
12
+ ts: runTypeScript,
13
+ }
14
+
15
+ const { shiki, themes } = await import('#slidev/shiki')
16
+ const highlighter = await shiki
17
+ const highlight = (code: string, lang: string, options: Partial<CodeToHastOptions> = {}) => highlighter.codeToHtml(code, {
18
+ lang,
19
+ theme: typeof themes === 'string'
20
+ ? themes
21
+ : isDark.value
22
+ ? themes.dark || 'vitesse-dark'
23
+ : themes.light || 'vitesse-light',
24
+ ...options,
25
+ })
26
+
27
+ const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {
28
+ try {
29
+ const runner = runners[lang]
30
+ if (!runner)
31
+ throw new Error(`Runner for language "${lang}" not found`)
32
+ return await runner(
33
+ code,
34
+ {
35
+ options,
36
+ highlight,
37
+ run: async (code, lang) => {
38
+ return await run(code, lang, options)
39
+ },
40
+ },
41
+ )
42
+ }
43
+ catch (e) {
44
+ console.error(e)
45
+ return {
46
+ error: `${e}`,
47
+ }
48
+ }
49
+ }
50
+
51
+ for (const setup of setups) {
52
+ const result = await setup(runners)
53
+ Object.assign(runners, result)
54
+ }
55
+
56
+ return {
57
+ highlight,
58
+ run,
59
+ }
60
+ })
61
+
62
+ // Ported from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/playground/src/sidebar/runtime.ts
63
+ export async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
64
+ const allLogs: CodeRunnerOutput[] = []
65
+
66
+ const replace = {} as any
67
+ const logger = function (...objs: any[]) {
68
+ allLogs.push(objs.map(printObject))
69
+ }
70
+ replace.info = replace.log = replace.debug = replace.warn = replace.error = logger
71
+ replace.clear = () => allLogs.length = 0
72
+ const vmConsole = Object.assign({}, console, replace)
73
+ try {
74
+ const safeJS = `return async (console) => {${sanitizeJS(code)}}`
75
+ // eslint-disable-next-line no-new-func
76
+ await (new Function(safeJS)())(vmConsole)
77
+ }
78
+ catch (error) {
79
+ return {
80
+ error: `ERROR: ${error}`,
81
+ }
82
+ }
83
+
84
+ function printObject(arg: any): CodeRunnerOutputText {
85
+ if (typeof arg === 'string') {
86
+ return {
87
+ text: arg,
88
+ }
89
+ }
90
+ return {
91
+ text: objectToText(arg),
92
+ highlightLang: 'javascript',
93
+ }
94
+ }
95
+
96
+ function objectToText(arg: any): string {
97
+ let textRep = ''
98
+ if (arg instanceof Error) {
99
+ textRep = `Error: ${JSON.stringify(arg.message)}`
100
+ }
101
+ else if (arg === null || arg === undefined || typeof arg === 'symbol') {
102
+ textRep = String(arg)
103
+ }
104
+ else if (Array.isArray(arg)) {
105
+ textRep = `[${arg.map(objectToText).join(', ')}]`
106
+ }
107
+ else if (arg instanceof Set) {
108
+ const setIter = [...arg]
109
+ textRep = `Set (${arg.size}) {${setIter.map(objectToText).join(', ')}}`
110
+ }
111
+ else if (arg instanceof Map) {
112
+ const mapIter = [...arg.entries()]
113
+ textRep
114
+ = `Map (${arg.size}) {${mapIter
115
+ .map(([k, v]) => `${objectToText(k)} => ${objectToText(v)}`)
116
+ .join(', ')
117
+ }}`
118
+ }
119
+ else if (arg instanceof RegExp) {
120
+ textRep = arg.toString()
121
+ }
122
+ else if (typeof arg === 'string') {
123
+ textRep = JSON.stringify(arg)
124
+ }
125
+ else if (typeof arg === 'object') {
126
+ const name = arg.constructor?.name ?? ''
127
+ // No one needs to know an obj is an obj
128
+ const nameWithoutObject = name && name === 'Object' ? '' : name
129
+ const prefix = nameWithoutObject ? `${nameWithoutObject}: ` : ''
130
+
131
+ // JSON.stringify omits any keys with a value of undefined. To get around this, we replace undefined with the text __undefined__ and then do a global replace using regex back to keyword undefined
132
+ textRep
133
+ = prefix
134
+ + JSON.stringify(arg, (_, value) => (value === undefined ? '__undefined__' : value), 2).replace(
135
+ /"__undefined__"/g,
136
+ 'undefined',
137
+ )
138
+
139
+ textRep = String(textRep)
140
+ }
141
+ else {
142
+ textRep = String(arg)
143
+ }
144
+ return textRep
145
+ }
146
+
147
+ // The reflect-metadata runtime is available, so allow that to go through
148
+ function sanitizeJS(code: string) {
149
+ return code.replace(`import "reflect-metadata"`, '').replace(`require("reflect-metadata")`, '')
150
+ }
151
+
152
+ return allLogs
153
+ }
154
+
155
+ let tsModule: typeof import('typescript') | undefined
156
+
157
+ export async function runTypeScript(code: string, context: CodeRunnerContext) {
158
+ const { transpile } = tsModule ??= await import('typescript')
159
+ code = transpile(code, {
160
+ module: tsModule.ModuleKind.ESNext,
161
+ target: tsModule.ScriptTarget.ES2022,
162
+ })
163
+ return await context.run(code, 'javascript')
164
+ }