@slidev/client 0.48.0-beta.9 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/App.vue +7 -0
- package/builtin/Arrow.vue +2 -4
- package/builtin/CodeBlockWrapper.vue +33 -28
- package/builtin/KaTexBlockWrapper.vue +1 -1
- package/builtin/Link.vue +3 -1
- package/builtin/Mermaid.vue +4 -3
- package/builtin/Monaco.vue +166 -93
- package/builtin/ShikiMagicMove.vue +103 -0
- package/builtin/SlidevVideo.vue +1 -1
- package/builtin/Toc.vue +1 -1
- package/builtin/TocList.vue +4 -3
- package/builtin/Tweet.vue +12 -9
- package/builtin/VClick.ts +2 -1
- package/composables/useClicks.ts +19 -32
- package/composables/useDarkMode.ts +9 -0
- package/composables/useDrawings.ts +181 -0
- package/composables/useNav.ts +346 -44
- package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
- package/composables/useSwipeControls.ts +43 -0
- package/composables/useTocTree.ts +81 -0
- package/composables/useViewTransition.ts +7 -4
- package/constants.ts +4 -3
- package/context.ts +13 -6
- package/env.ts +7 -16
- package/index.html +1 -0
- package/index.ts +12 -0
- package/internals/ClicksSlider.vue +93 -0
- package/internals/CodeRunner.vue +142 -0
- package/internals/Controls.vue +2 -2
- package/internals/DomElement.vue +18 -0
- package/internals/DrawingControls.vue +14 -15
- package/internals/DrawingLayer.vue +6 -5
- package/internals/DrawingPreview.vue +4 -2
- package/internals/Goto.vue +9 -6
- package/internals/IconButton.vue +3 -2
- package/internals/NavControls.vue +30 -11
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +129 -0
- package/internals/NoteStatic.vue +5 -2
- package/internals/PrintContainer.vue +11 -8
- package/internals/PrintSlide.vue +11 -12
- package/internals/PrintSlideClick.vue +14 -19
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +3 -3
- package/internals/{Editor.vue → SideEditor.vue} +24 -15
- package/internals/SlideContainer.vue +13 -9
- package/internals/SlideLoading.vue +19 -0
- package/internals/SlideWrapper.vue +79 -0
- package/internals/SlidesShow.vue +36 -22
- package/layouts/error.vue +5 -0
- package/layouts/two-cols-header.vue +9 -3
- package/logic/overview.ts +2 -2
- package/logic/route.ts +16 -5
- package/logic/slides.ts +20 -0
- package/logic/transition.ts +50 -0
- package/logic/utils.ts +24 -1
- package/main.ts +3 -15
- package/{setup → modules}/codemirror.ts +1 -3
- package/modules/context.ts +1 -46
- package/modules/mermaid.ts +9 -8
- package/package.json +21 -15
- package/pages/notes.vue +6 -3
- package/pages/overview.vue +138 -51
- package/pages/play.vue +16 -9
- package/pages/presenter/print.vue +10 -5
- package/pages/presenter.vue +122 -104
- package/pages/print.vue +4 -3
- package/routes.ts +8 -54
- package/setup/code-runners.ts +164 -0
- package/setup/main.ts +39 -9
- package/setup/mermaid.ts +5 -6
- package/setup/monaco.ts +114 -51
- package/setup/root.ts +62 -18
- package/setup/shortcuts.ts +15 -12
- package/shim-vue.d.ts +34 -0
- package/shim.d.ts +1 -13
- package/state/index.ts +2 -2
- package/styles/code.css +9 -5
- package/styles/index.css +63 -7
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +11 -8
- package/styles/shiki-twoslash.css +1 -1
- package/styles/vars.css +1 -1
- package/uno.config.ts +10 -1
- package/utils.ts +15 -2
- package/composables/useContext.ts +0 -17
- package/composables/useTweetScript.ts +0 -17
- package/iframes/monaco/index.css +0 -28
- package/iframes/monaco/index.html +0 -7
- package/iframes/monaco/index.ts +0 -260
- package/internals/NoteEditor.vue +0 -92
- package/internals/SlideWrapper.ts +0 -58
- package/logic/drawings.ts +0 -161
- package/logic/nav.ts +0 -278
- package/setup/prettier.ts +0 -43
- /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 {
|
|
6
|
-
import {
|
|
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(() =>
|
|
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"
|
|
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
|
|
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>
|
package/pages/presenter.vue
CHANGED
|
@@ -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 {
|
|
5
|
+
import { useSwipeControls } from '../composables/useSwipeControls'
|
|
6
6
|
import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
|
|
7
|
-
import { configs
|
|
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 {
|
|
13
|
-
import
|
|
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
|
|
18
|
-
import
|
|
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 =
|
|
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 [
|
|
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[
|
|
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
|
-
|
|
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/
|
|
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
|
|
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
|
-
<
|
|
114
|
-
|
|
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"
|
|
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
|
|
125
|
-
:key="nextFrame[0].
|
|
126
|
-
:clicks-context="nextFrameClicksCtx
|
|
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="
|
|
133
|
-
|
|
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__ &&
|
|
138
|
-
<
|
|
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
|
-
<
|
|
153
|
+
<NoteEditable
|
|
142
154
|
v-if="__DEV__"
|
|
143
|
-
:key="`edit-${
|
|
144
|
-
:
|
|
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
|
-
:
|
|
159
|
+
:clicks-context="clicksContext"
|
|
147
160
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
148
161
|
/>
|
|
149
162
|
<NoteStatic
|
|
150
163
|
v-else
|
|
151
|
-
:key="`static-${
|
|
152
|
-
:no="
|
|
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-
|
|
180
|
-
:style="{ width: `${(
|
|
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
|
-
<
|
|
211
|
+
<QuickOverview v-model="showOverview" />
|
|
186
212
|
</template>
|
|
187
213
|
|
|
188
|
-
<style
|
|
214
|
+
<style scoped>
|
|
189
215
|
.slidev-presenter {
|
|
190
216
|
--slidev-controls-foreground: current;
|
|
191
217
|
}
|
|
192
218
|
|
|
193
|
-
.timer-btn:hover {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
227
|
+
--uno: px-4 py-2 text-xl;
|
|
204
228
|
}
|
|
205
229
|
|
|
206
230
|
.grid-container {
|
|
207
|
-
|
|
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:
|
|
240
|
+
grid-template-rows: 2fr 1fr min-content;
|
|
215
241
|
grid-template-areas:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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:
|
|
249
|
+
grid-template-rows: 2fr 1fr min-content;
|
|
225
250
|
grid-template-areas:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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:
|
|
259
|
+
grid-template-rows: 1fr 1fr 1fr min-content;
|
|
236
260
|
grid-template-areas:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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:
|
|
271
|
+
grid-template-rows: 1fr 2fr min-content;
|
|
249
272
|
grid-template-areas:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
280
|
+
--uno: fixed left-0 right-0 top-0;
|
|
259
281
|
}
|
|
260
282
|
|
|
261
283
|
.grid-section {
|
|
262
|
-
|
|
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
|
-
.
|
|
282
|
-
|
|
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]"
|
|
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
|
|
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 (!
|
|
17
|
+
if (!configs.remote || configs.remote === to.query.password)
|
|
38
18
|
return true
|
|
39
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
}
|