@slidev/client 52.15.2 → 52.17.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 +2 -0
- package/builtin/AutoFitText.vue +54 -35
- package/builtin/KaTexBlockWrapper.test.ts +41 -0
- package/builtin/KaTexBlockWrapper.vue +4 -19
- package/builtin/ShikiMagicMove.vue +2 -2
- package/builtin/TocList.vue +1 -1
- package/builtin/katex-lines.ts +22 -0
- package/composables/useEmbeddedCtrl.ts +4 -2
- package/composables/useNav.ts +1 -1
- package/composables/usePreloadImages.ts +40 -1
- package/constants.ts +1 -0
- package/env.ts +1 -1
- package/internals/ClicksSlider.vue +99 -48
- package/internals/CodeRunner.vue +2 -2
- package/internals/Goto.vue +2 -2
- package/internals/NavControls.vue +1 -1
- package/internals/NoteDisplay.vue +5 -0
- package/internals/NoteEditable.vue +2 -3
- package/internals/PrintSlideClick.vue +1 -1
- package/internals/PwaIndicator.vue +50 -0
- package/internals/SideEditor.vue +2 -7
- package/internals/SlideWrapper.vue +1 -1
- package/internals/SlidesShow.vue +1 -1
- package/logic/sideEditor.ts +14 -0
- package/logic/slidePath.test.ts +29 -0
- package/logic/slidePath.ts +10 -0
- package/logic/slides.ts +3 -3
- package/modules/mermaid.ts +1 -1
- package/package.json +26 -25
- package/pages/overview.vue +293 -64
- package/pages/presenter.vue +1 -4
- package/setup/code-runners.ts +2 -2
- package/setup/context-menu.ts +1 -1
- package/setup/main.ts +11 -5
- package/setup/mermaid.ts +1 -1
- package/setup/monaco.ts +4 -4
- package/setup/pwa.ts +28 -0
- package/setup/root.ts +1 -1
- package/setup/shiki.ts +1 -1
- package/setup/shortcuts.ts +1 -1
- package/shim-vue.d.ts +1 -0
- package/shim.d.ts +2 -0
- package/styles/index.ts +1 -1
package/pages/overview.vue
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ClicksContext, SlideRoute } from '@slidev/types'
|
|
3
3
|
import { useHead } from '@unhead/vue'
|
|
4
|
-
import { computed, nextTick, onMounted, reactive, ref, shallowRef } from 'vue'
|
|
4
|
+
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue'
|
|
5
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
5
6
|
import { createFixedClicks } from '../composables/useClicks'
|
|
6
7
|
import { useNav } from '../composables/useNav'
|
|
7
8
|
import { CLICKS_MAX } from '../constants'
|
|
8
|
-
import { pathPrefix, slidesTitle } from '../env'
|
|
9
|
+
import { pathPrefix, slideAspect, slidesTitle } from '../env'
|
|
9
10
|
import ClicksSlider from '../internals/ClicksSlider.vue'
|
|
10
11
|
import DrawingPreview from '../internals/DrawingPreview.vue'
|
|
11
12
|
import IconButton from '../internals/IconButton.vue'
|
|
@@ -14,19 +15,38 @@ import SlideContainer from '../internals/SlideContainer.vue'
|
|
|
14
15
|
import SlideWrapper from '../internals/SlideWrapper.vue'
|
|
15
16
|
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
|
|
16
17
|
import { getSlidePath } from '../logic/slides'
|
|
18
|
+
import { windowSize } from '../state'
|
|
17
19
|
|
|
18
20
|
const cardWidth = 450
|
|
19
21
|
|
|
20
22
|
useHead({ title: `Overview - ${slidesTitle}` })
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const currentRoute = useRoute()
|
|
25
|
+
const router = useRouter()
|
|
26
|
+
const { openInEditor, slides, isEmbedded } = useNav()
|
|
27
|
+
const isPreviewMode = computed(() => currentRoute.query.mode === 'preview')
|
|
28
|
+
const isEmbeddedPreviewMode = computed(() => isPreviewMode.value && isEmbedded.value)
|
|
29
|
+
const overviewCardWidth = computed(() => {
|
|
30
|
+
if (!isPreviewMode.value)
|
|
31
|
+
return cardWidth
|
|
32
|
+
if (isEmbeddedPreviewMode.value)
|
|
33
|
+
return Math.max(0, windowSize.width.value - 16)
|
|
34
|
+
return Math.min(900, Math.max(320, windowSize.width.value - 160))
|
|
35
|
+
})
|
|
36
|
+
const overviewSlideHeight = computed(() => overviewCardWidth.value / slideAspect.value)
|
|
23
37
|
|
|
24
38
|
const blocks: Map<number, HTMLElement> = reactive(new Map())
|
|
39
|
+
const slidePreviews: Map<number, HTMLElement> = reactive(new Map())
|
|
25
40
|
const activeBlocks = ref<number[]>([])
|
|
41
|
+
const scroller = ref<HTMLElement>()
|
|
26
42
|
const edittingNote = ref<number | null>(null)
|
|
43
|
+
let ignoreOverviewScrollUntil = 0
|
|
44
|
+
let pendingOverviewScrollNo: number | undefined
|
|
45
|
+
let overviewScrollTimer: ReturnType<typeof setTimeout> | undefined
|
|
27
46
|
const wordCounts = computed(() => slides.value.map(route => wordCount(route.meta?.slide?.note || '')))
|
|
28
47
|
const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
|
|
29
48
|
const totalClicks = computed(() => slides.value.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
|
|
49
|
+
const slideNoDigits = computed(() => String(Math.max(1, slides.value.length)).length)
|
|
30
50
|
|
|
31
51
|
const activeSlide = shallowRef<SlideRoute>()
|
|
32
52
|
const clicksContextMap = new WeakMap<SlideRoute, ClicksContext>()
|
|
@@ -65,25 +85,23 @@ function wordCount(str: string) {
|
|
|
65
85
|
return count
|
|
66
86
|
}
|
|
67
87
|
|
|
68
|
-
function isElementInViewport(el: HTMLElement) {
|
|
69
|
-
const rect = el.getBoundingClientRect()
|
|
70
|
-
const delta = 20
|
|
71
|
-
return (
|
|
72
|
-
rect.top >= 0 - delta
|
|
73
|
-
&& rect.left >= 0 - delta
|
|
74
|
-
&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + delta
|
|
75
|
-
&& rect.right <= (window.innerWidth || document.documentElement.clientWidth) + delta
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
88
|
function checkActiveBlocks() {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
const viewportHeight = window.innerHeight || document.documentElement.clientHeight
|
|
90
|
+
let active: { idx: number, visibleHeight: number } | undefined
|
|
91
|
+
const fullyVisible: number[] = []
|
|
92
|
+
|
|
93
|
+
for (const [idx, el] of blocks.entries()) {
|
|
94
|
+
const rect = el.getBoundingClientRect()
|
|
95
|
+
const visibleHeight = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0))
|
|
96
|
+
if (visibleHeight === 0)
|
|
97
|
+
continue
|
|
98
|
+
if (visibleHeight >= rect.height)
|
|
99
|
+
fullyVisible.push(idx)
|
|
100
|
+
if (!active || visibleHeight > active.visibleHeight)
|
|
101
|
+
active = { idx, visibleHeight }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
activeBlocks.value = fullyVisible.length ? fullyVisible : active ? [active.idx] : []
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
function openSlideInNewTab(path: string) {
|
|
@@ -93,12 +111,142 @@ function openSlideInNewTab(path: string) {
|
|
|
93
111
|
a.click()
|
|
94
112
|
}
|
|
95
113
|
|
|
114
|
+
function openSlideInBrowser(path: string) {
|
|
115
|
+
const url = new URL(pathPrefix + path.slice(1), location.href).href
|
|
116
|
+
if (isEmbedded.value) {
|
|
117
|
+
window.parent.postMessage({
|
|
118
|
+
target: 'slidev',
|
|
119
|
+
sender: 'slidev',
|
|
120
|
+
type: 'open-external',
|
|
121
|
+
url,
|
|
122
|
+
}, '*')
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
openSlideInNewTab(path)
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
function scrollToSlide(idx: number) {
|
|
97
129
|
const el = blocks.get(idx)
|
|
98
130
|
if (el)
|
|
99
131
|
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
100
132
|
}
|
|
101
133
|
|
|
134
|
+
function getSlidePreviewTop(idx: number) {
|
|
135
|
+
const el = slidePreviews.get(idx) || blocks.get(idx)
|
|
136
|
+
if (!el || !scroller.value)
|
|
137
|
+
return null
|
|
138
|
+
const scrollerRect = scroller.value.getBoundingClientRect()
|
|
139
|
+
const elRect = el.getBoundingClientRect()
|
|
140
|
+
return elRect.top - scrollerRect.top + scroller.value.scrollTop
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function scrollSlideNoIntoCenter(no: number) {
|
|
144
|
+
if (!scroller.value || slides.value.length === 0)
|
|
145
|
+
return
|
|
146
|
+
const clamped = Math.min(Math.max(no, 1), slides.value.length)
|
|
147
|
+
const idx = Math.floor(clamped) - 1
|
|
148
|
+
const progress = clamped - (idx + 1)
|
|
149
|
+
const start = getSlidePreviewTop(idx)
|
|
150
|
+
const end = getSlidePreviewTop(idx + 1)
|
|
151
|
+
if (start == null)
|
|
152
|
+
return
|
|
153
|
+
const top = start
|
|
154
|
+
+ (end == null ? 0 : (end - start) * progress)
|
|
155
|
+
- scroller.value.clientHeight * 0.5
|
|
156
|
+
if (Math.abs(scroller.value.scrollTop - top) < 1)
|
|
157
|
+
return
|
|
158
|
+
scroller.value.scrollTo({ top })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getInitialSlideNo() {
|
|
162
|
+
const value = currentRoute.query.slideNo
|
|
163
|
+
const no = Number(Array.isArray(value) ? value[0] : value)
|
|
164
|
+
return Number.isFinite(no) && no > 0 ? no : undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function updateSlideNoQuery(no: number) {
|
|
168
|
+
if (!isEmbeddedPreviewMode.value)
|
|
169
|
+
return
|
|
170
|
+
const slideNo = Number(no.toFixed(3)).toString()
|
|
171
|
+
if (currentRoute.query.slideNo === slideNo)
|
|
172
|
+
return
|
|
173
|
+
router.replace({
|
|
174
|
+
query: {
|
|
175
|
+
...currentRoute.query,
|
|
176
|
+
slideNo,
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getCenteredSlideNo() {
|
|
182
|
+
if (!scroller.value || slides.value.length === 0)
|
|
183
|
+
return null
|
|
184
|
+
const center = scroller.value.scrollTop + scroller.value.clientHeight * 0.5
|
|
185
|
+
const tops = slides.value
|
|
186
|
+
.map((_, idx) => getSlidePreviewTop(idx))
|
|
187
|
+
.filter((top): top is number => top != null)
|
|
188
|
+
if (tops.length === 0)
|
|
189
|
+
return null
|
|
190
|
+
if (tops.length === 1 || center <= tops[0])
|
|
191
|
+
return 1
|
|
192
|
+
for (let i = 1; i < tops.length; i++) {
|
|
193
|
+
if (center <= tops[i]) {
|
|
194
|
+
const span = Math.max(1, tops[i] - tops[i - 1])
|
|
195
|
+
return i + (center - tops[i - 1]) / span
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return slides.value.length
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function postOverviewScroll(no: number) {
|
|
202
|
+
pendingOverviewScrollNo = no
|
|
203
|
+
if (overviewScrollTimer)
|
|
204
|
+
return
|
|
205
|
+
overviewScrollTimer = setTimeout(() => {
|
|
206
|
+
overviewScrollTimer = undefined
|
|
207
|
+
const no = pendingOverviewScrollNo
|
|
208
|
+
pendingOverviewScrollNo = undefined
|
|
209
|
+
if (no == null)
|
|
210
|
+
return
|
|
211
|
+
updateSlideNoQuery(no)
|
|
212
|
+
if (Date.now() < ignoreOverviewScrollUntil)
|
|
213
|
+
return
|
|
214
|
+
window.parent.postMessage({
|
|
215
|
+
target: 'slidev',
|
|
216
|
+
sender: 'slidev',
|
|
217
|
+
type: 'overview-scroll',
|
|
218
|
+
no,
|
|
219
|
+
}, '*')
|
|
220
|
+
}, 50)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function onOverviewScroll() {
|
|
224
|
+
checkActiveBlocks()
|
|
225
|
+
if (!isEmbeddedPreviewMode.value || Date.now() < ignoreOverviewScrollUntil)
|
|
226
|
+
return
|
|
227
|
+
const no = getCenteredSlideNo()
|
|
228
|
+
if (no != null)
|
|
229
|
+
postOverviewScroll(no)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function onOverviewMessage({ data }: MessageEvent) {
|
|
233
|
+
if (
|
|
234
|
+
!isEmbeddedPreviewMode.value
|
|
235
|
+
|| data?.target !== 'slidev'
|
|
236
|
+
|| data.sender !== 'vscode'
|
|
237
|
+
|| data.type !== 'overview-scroll'
|
|
238
|
+
) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
const no = Number(data.no)
|
|
242
|
+
if (no > 0) {
|
|
243
|
+
ignoreOverviewScrollUntil = Date.now() + 300
|
|
244
|
+
pendingOverviewScrollNo = undefined
|
|
245
|
+
updateSlideNoQuery(no)
|
|
246
|
+
scrollSlideNoIntoCenter(no)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
102
250
|
function onMarkerClick(e: MouseEvent, clicks: number, route: SlideRoute) {
|
|
103
251
|
const ctx = getClicksContext(route)
|
|
104
252
|
if (ctx.current === clicks)
|
|
@@ -108,18 +256,52 @@ function onMarkerClick(e: MouseEvent, clicks: number, route: SlideRoute) {
|
|
|
108
256
|
e.preventDefault()
|
|
109
257
|
}
|
|
110
258
|
|
|
259
|
+
function openOverviewSlideSource(e: MouseEvent, route: SlideRoute) {
|
|
260
|
+
if (e.ctrlKey || e.metaKey) {
|
|
261
|
+
e.preventDefault()
|
|
262
|
+
openSlideInBrowser(getSlidePath(route, false))
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
const slide = route.meta?.slide
|
|
266
|
+
if (!slide)
|
|
267
|
+
return
|
|
268
|
+
window.parent.postMessage({
|
|
269
|
+
target: 'slidev',
|
|
270
|
+
type: 'command',
|
|
271
|
+
command: 'goto',
|
|
272
|
+
args: [slide.filepath, slide.sourceIndex],
|
|
273
|
+
}, '*')
|
|
274
|
+
}
|
|
275
|
+
|
|
111
276
|
onMounted(() => {
|
|
277
|
+
window.addEventListener('message', onOverviewMessage)
|
|
278
|
+
const initialSlideNo = isEmbeddedPreviewMode.value ? getInitialSlideNo() : undefined
|
|
279
|
+
if (initialSlideNo != null) {
|
|
280
|
+
ignoreOverviewScrollUntil = Date.now() + 300
|
|
281
|
+
scrollSlideNoIntoCenter(initialSlideNo)
|
|
282
|
+
}
|
|
112
283
|
nextTick(() => {
|
|
284
|
+
if (initialSlideNo != null)
|
|
285
|
+
scrollSlideNoIntoCenter(initialSlideNo)
|
|
113
286
|
checkActiveBlocks()
|
|
114
287
|
})
|
|
115
288
|
})
|
|
289
|
+
|
|
290
|
+
onUnmounted(() => {
|
|
291
|
+
window.removeEventListener('message', onOverviewMessage)
|
|
292
|
+
if (overviewScrollTimer)
|
|
293
|
+
clearTimeout(overviewScrollTimer)
|
|
294
|
+
})
|
|
116
295
|
</script>
|
|
117
296
|
|
|
118
297
|
<template>
|
|
119
298
|
<div class="h-screen w-screen of-hidden flex">
|
|
120
|
-
<nav
|
|
299
|
+
<nav
|
|
300
|
+
v-if="!isEmbedded"
|
|
301
|
+
class="grid grid-rows-[auto_max-content] border-r border-main select-none max-h-full h-full"
|
|
302
|
+
>
|
|
121
303
|
<div class="relative">
|
|
122
|
-
<div class="absolute left-0 top-0 bottom-0 w-200 flex flex-col flex-auto items-end group
|
|
304
|
+
<div class="absolute left-0 top-0 bottom-0 w-200 flex flex-col flex-auto items-end group p-6px md:p-10px gap-1 max-h-full of-x-visible of-y-auto" style="direction:rtl">
|
|
123
305
|
<div
|
|
124
306
|
v-for="(route, idx) of slides"
|
|
125
307
|
:key="route.no"
|
|
@@ -163,46 +345,86 @@ onMounted(() => {
|
|
|
163
345
|
</div>
|
|
164
346
|
</nav>
|
|
165
347
|
<main
|
|
348
|
+
ref="scroller"
|
|
166
349
|
class="flex-1 h-full of-auto"
|
|
167
350
|
:style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
|
|
168
|
-
@scroll="
|
|
351
|
+
@scroll="onOverviewScroll"
|
|
169
352
|
>
|
|
170
353
|
<div
|
|
171
354
|
v-for="(route, idx) of slides"
|
|
172
355
|
:key="route.no"
|
|
173
356
|
:ref="el => blocks.set(idx, el as any)"
|
|
174
|
-
class="
|
|
175
|
-
:class="idx === 0 ? '
|
|
357
|
+
class="overview-slide-block relative of-hidden flex gap-4 min-h-50"
|
|
358
|
+
:class="[idx === 0 && !isEmbeddedPreviewMode ? 'pt2' : '', isEmbeddedPreviewMode ? 'justify-center' : 'border-t border-main']"
|
|
176
359
|
>
|
|
177
|
-
<div
|
|
178
|
-
|
|
360
|
+
<div
|
|
361
|
+
v-if="!isEmbeddedPreviewMode"
|
|
362
|
+
class="select-none text-right my5 flex flex-col justify-between items-end"
|
|
363
|
+
:class="isPreviewMode ? 'w-9' : 'w-13'"
|
|
364
|
+
:style="{ height: `${overviewSlideHeight}px` }"
|
|
365
|
+
>
|
|
366
|
+
<div class="self-center text-3xl op20 mb2 text-center mr--14px tabular-nums" :style="{ width: `${slideNoDigits}ch` }">
|
|
179
367
|
{{ idx + 1 }}
|
|
180
368
|
</div>
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
369
|
+
<div class="flex flex-col gap-1 mx-1 items-end">
|
|
370
|
+
<IconButton
|
|
371
|
+
class="overview-slide-action mr--4 op0"
|
|
372
|
+
:class="isPreviewMode ? 'text-lg' : ''"
|
|
373
|
+
title="Play in new tab"
|
|
374
|
+
@click="openSlideInNewTab(getSlidePath(route, false))"
|
|
375
|
+
>
|
|
376
|
+
<div class="i-carbon:presentation-file" />
|
|
377
|
+
</IconButton>
|
|
378
|
+
<IconButton
|
|
379
|
+
v-if="__DEV__ && route.meta?.slide"
|
|
380
|
+
class="overview-slide-action mr--4 op0"
|
|
381
|
+
:class="isPreviewMode ? 'text-lg' : ''"
|
|
382
|
+
title="Open in editor"
|
|
383
|
+
@click="openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
|
|
384
|
+
>
|
|
385
|
+
<div class="i-carbon:edit" />
|
|
386
|
+
</IconButton>
|
|
387
|
+
</div>
|
|
196
388
|
</div>
|
|
197
|
-
<div
|
|
389
|
+
<div
|
|
390
|
+
class="flex flex-col"
|
|
391
|
+
:class="isEmbeddedPreviewMode ? 'my1 gap-0' : 'my5 gap-2'"
|
|
392
|
+
:style="{ width: `${overviewCardWidth}px` }"
|
|
393
|
+
>
|
|
394
|
+
<div
|
|
395
|
+
v-if="isEmbeddedPreviewMode"
|
|
396
|
+
class="flex items-end gap-2"
|
|
397
|
+
>
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
class="select-none pl-1 text-lg leading-tight op60 tabular-nums hover:op90 hover:underline underline-offset-2"
|
|
401
|
+
@click="openOverviewSlideSource($event, route)"
|
|
402
|
+
>
|
|
403
|
+
{{ idx + 1 }}
|
|
404
|
+
</button>
|
|
405
|
+
<ClicksSlider
|
|
406
|
+
v-if="getSlideClicks(route)"
|
|
407
|
+
:active="activeSlide === route"
|
|
408
|
+
:clicks-context="getClicksContext(route)"
|
|
409
|
+
resettable
|
|
410
|
+
compact
|
|
411
|
+
attached
|
|
412
|
+
class="ml-auto w-88 min-w-[70%] max-w-[calc(100%-3rem)]"
|
|
413
|
+
@dblclick="toggleRoute(route)"
|
|
414
|
+
@activate="activeSlide = route"
|
|
415
|
+
@reset="activeSlide = undefined"
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
198
418
|
<div
|
|
199
|
-
|
|
200
|
-
|
|
419
|
+
:ref="el => slidePreviews.set(idx, el as any)"
|
|
420
|
+
class="border rounded border-main overflow-hidden bg-main h-max"
|
|
421
|
+
:class="[isEmbeddedPreviewMode && getSlideClicks(route) ? 'rounded-tr-0' : '', isEmbeddedPreviewMode ? '' : 'select-none']"
|
|
422
|
+
@dblclick="!isEmbeddedPreviewMode && openSlideInNewTab(getSlidePath(route, false))"
|
|
201
423
|
>
|
|
202
424
|
<SlideContainer
|
|
203
425
|
:key="route.no"
|
|
204
|
-
:width="
|
|
205
|
-
class="pointer-events-none important:[&_*]:select-none"
|
|
426
|
+
:width="overviewCardWidth"
|
|
427
|
+
:class="isEmbeddedPreviewMode ? '' : 'pointer-events-none important:[&_*]:select-none'"
|
|
206
428
|
>
|
|
207
429
|
<SlideWrapper
|
|
208
430
|
:clicks-context="getClicksContext(route)"
|
|
@@ -213,27 +435,21 @@ onMounted(() => {
|
|
|
213
435
|
</SlideContainer>
|
|
214
436
|
</div>
|
|
215
437
|
<ClicksSlider
|
|
216
|
-
v-if="getSlideClicks(route)"
|
|
438
|
+
v-if="getSlideClicks(route) && !isEmbeddedPreviewMode"
|
|
217
439
|
:active="activeSlide === route"
|
|
218
440
|
:clicks-context="getClicksContext(route)"
|
|
219
|
-
|
|
441
|
+
resettable
|
|
442
|
+
class="ml-1 w-[calc(100%-0.25rem)]"
|
|
443
|
+
:class="isPreviewMode ? '' : 'mt-2'"
|
|
220
444
|
@dblclick="toggleRoute(route)"
|
|
221
|
-
@
|
|
445
|
+
@activate="activeSlide = route"
|
|
446
|
+
@reset="activeSlide = undefined"
|
|
222
447
|
/>
|
|
223
448
|
</div>
|
|
224
|
-
<div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
|
|
225
|
-
<IconButton
|
|
226
|
-
title="Edit Note"
|
|
227
|
-
class="rounded-full w-9 h-9 text-sm"
|
|
228
|
-
:class="edittingNote === route.no ? 'important:op0' : ''"
|
|
229
|
-
@click="edittingNote = route.no"
|
|
230
|
-
>
|
|
231
|
-
<div class="i-carbon:pen" />
|
|
232
|
-
</IconButton>
|
|
233
|
-
</div>
|
|
234
449
|
<NoteEditable
|
|
450
|
+
v-if="!isPreviewMode"
|
|
235
451
|
:no="route.no"
|
|
236
|
-
class="max-w-250 w-250 text-lg rounded p3"
|
|
452
|
+
class="relative z-1 max-w-250 w-250 text-lg rounded p3"
|
|
237
453
|
:auto-height="true"
|
|
238
454
|
:highlight="activeSlide === route"
|
|
239
455
|
:editing="edittingNote === route.no"
|
|
@@ -243,14 +459,17 @@ onMounted(() => {
|
|
|
243
459
|
@marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
|
|
244
460
|
/>
|
|
245
461
|
<div
|
|
246
|
-
v-if="wordCounts[idx] > 0"
|
|
462
|
+
v-if="!isPreviewMode && wordCounts[idx] > 0"
|
|
247
463
|
class="select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
|
|
248
464
|
>
|
|
249
465
|
{{ wordCounts[idx] }} words
|
|
250
466
|
</div>
|
|
251
467
|
</div>
|
|
252
468
|
</main>
|
|
253
|
-
<div
|
|
469
|
+
<div
|
|
470
|
+
v-if="!isEmbedded"
|
|
471
|
+
class="absolute z-2 top-0 right-0 px3 py1.5 border-b border-l rounded-lb bg-main/80 backdrop-blur border-main select-none"
|
|
472
|
+
>
|
|
254
473
|
<div class="text-xs op50">
|
|
255
474
|
{{ slides.length }} slides ·
|
|
256
475
|
{{ totalClicks + slides.length - 1 }} clicks ·
|
|
@@ -259,3 +478,13 @@ onMounted(() => {
|
|
|
259
478
|
</div>
|
|
260
479
|
</div>
|
|
261
480
|
</template>
|
|
481
|
+
|
|
482
|
+
<style scoped>
|
|
483
|
+
.overview-slide-block:hover .overview-slide-action {
|
|
484
|
+
opacity: 0.6;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.overview-slide-action:hover {
|
|
488
|
+
opacity: 0.8;
|
|
489
|
+
}
|
|
490
|
+
</style>
|
package/pages/presenter.vue
CHANGED
|
@@ -436,6 +436,7 @@ onMounted(() => {
|
|
|
436
436
|
--slidev-presenter-notes-width: 360px;
|
|
437
437
|
--slidev-presenter-notes-row-size: 280px;
|
|
438
438
|
--uno: bg-gray/20 flex-1 of-hidden;
|
|
439
|
+
position: relative;
|
|
439
440
|
display: grid;
|
|
440
441
|
gap: 1px 1px;
|
|
441
442
|
}
|
|
@@ -572,8 +573,6 @@ onMounted(() => {
|
|
|
572
573
|
top: 0;
|
|
573
574
|
width: 1px;
|
|
574
575
|
height: 100%;
|
|
575
|
-
background-color: currentColor;
|
|
576
|
-
opacity: 0.2;
|
|
577
576
|
transform: translateX(-50%);
|
|
578
577
|
}
|
|
579
578
|
|
|
@@ -596,8 +595,6 @@ onMounted(() => {
|
|
|
596
595
|
top: 0;
|
|
597
596
|
width: 1px;
|
|
598
597
|
height: 100%;
|
|
599
|
-
background-color: currentColor;
|
|
600
|
-
opacity: 0.2;
|
|
601
598
|
transform: translateX(-50%);
|
|
602
599
|
}
|
|
603
600
|
</style>
|
package/setup/code-runners.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { CodeRunner, CodeRunnerOutput, CodeRunnerOutputs, CodeRunnerOutputText } from '@slidev/types'
|
|
2
2
|
import type { CodeToHastOptions } from 'shiki'
|
|
3
3
|
import type ts from 'typescript'
|
|
4
|
-
import deps from '#slidev/monaco-run-deps'
|
|
5
|
-
import setups from '#slidev/setups/code-runners'
|
|
6
4
|
import { createSingletonPromise } from '@antfu/utils'
|
|
7
5
|
import { ref } from 'vue'
|
|
6
|
+
import deps from '#slidev/monaco-run-deps'
|
|
7
|
+
import setups from '#slidev/setups/code-runners'
|
|
8
8
|
import { configs } from '../env'
|
|
9
9
|
|
|
10
10
|
export default createSingletonPromise(async () => {
|
package/setup/context-menu.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ContextMenuItem } from '@slidev/types'
|
|
4
4
|
import type { ComputedRef } from 'vue'
|
|
5
|
-
import setups from '#slidev/setups/context-menu'
|
|
6
5
|
import { computed } from 'vue'
|
|
6
|
+
import setups from '#slidev/setups/context-menu'
|
|
7
7
|
import { useDrawings } from '../composables/useDrawings'
|
|
8
8
|
import { useNav } from '../composables/useNav'
|
|
9
9
|
import { fullscreen, showEditor, toggleOverview } from '../state'
|
package/setup/main.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { AppContext } from '@slidev/types'
|
|
2
2
|
import type { App } from 'vue'
|
|
3
|
-
import setups from '#slidev/setups/main'
|
|
4
3
|
import TwoSlashFloatingVue from '@shikijs/vitepress-twoslash/client'
|
|
5
4
|
import { createHead } from '@unhead/vue/client'
|
|
6
|
-
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
5
|
+
import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
6
|
+
import setups from '#slidev/setups/main'
|
|
7
7
|
import { createVClickDirectives } from '../modules/v-click'
|
|
8
8
|
import { createVDragDirective } from '../modules/v-drag'
|
|
9
9
|
import { createVMarkDirective } from '../modules/v-mark'
|
|
10
10
|
import { createVMotionDirectives } from '../modules/v-motion'
|
|
11
|
+
import { setupPWA } from '../setup/pwa'
|
|
11
12
|
import setupRoutes from '../setup/routes'
|
|
12
13
|
import '../styles'
|
|
13
14
|
|
|
@@ -21,9 +22,11 @@ export default async function setupMain(app: App) {
|
|
|
21
22
|
window.addEventListener('resize', setMaxHeight)
|
|
22
23
|
|
|
23
24
|
const router = createRouter({
|
|
24
|
-
history:
|
|
25
|
-
?
|
|
26
|
-
:
|
|
25
|
+
history: __SLIDEV_MEMORY_ROUTE__
|
|
26
|
+
? createMemoryHistory(import.meta.env.BASE_URL)
|
|
27
|
+
: __SLIDEV_HASH_ROUTE__
|
|
28
|
+
? createWebHashHistory(import.meta.env.BASE_URL)
|
|
29
|
+
: createWebHistory(import.meta.env.BASE_URL),
|
|
27
30
|
routes: setupRoutes(),
|
|
28
31
|
})
|
|
29
32
|
|
|
@@ -42,4 +45,7 @@ export default async function setupMain(app: App) {
|
|
|
42
45
|
|
|
43
46
|
for (const setup of setups)
|
|
44
47
|
await setup(context)
|
|
48
|
+
|
|
49
|
+
if (__SLIDEV_FEATURE_PWA__)
|
|
50
|
+
setupPWA()
|
|
45
51
|
}
|
package/setup/mermaid.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MermaidConfig } from 'mermaid'
|
|
2
|
-
import setups from '#slidev/setups/mermaid'
|
|
3
2
|
import { createSingletonPromise } from '@antfu/utils'
|
|
3
|
+
import setups from '#slidev/setups/mermaid'
|
|
4
4
|
|
|
5
5
|
export default createSingletonPromise(async () => {
|
|
6
6
|
const setupReturn: MermaidConfig = {
|
package/setup/monaco.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { MonacoSetupReturn } from '@slidev/types'
|
|
2
|
-
import configs from '#slidev/configs'
|
|
3
|
-
import setups from '#slidev/setups/monaco'
|
|
4
2
|
import { createSingletonPromise } from '@antfu/utils'
|
|
5
3
|
import { shikiToMonaco } from '@shikijs/monaco'
|
|
6
4
|
import { setupTypeAcquisition } from '@typescript/ata'
|
|
7
5
|
import * as monaco from 'monaco-editor'
|
|
8
|
-
|
|
9
6
|
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
|
10
7
|
// @ts-expect-error missing types
|
|
11
8
|
import { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'
|
|
9
|
+
|
|
12
10
|
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
|
13
11
|
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
|
|
14
12
|
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
|
@@ -17,9 +15,11 @@ import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
|
|
17
15
|
import { ContextViewService } from 'monaco-editor/esm/vs/platform/contextview/browser/contextViewService'
|
|
18
16
|
// @ts-expect-error missing types
|
|
19
17
|
import { SyncDescriptor } from 'monaco-editor/esm/vs/platform/instantiation/common/descriptors'
|
|
20
|
-
|
|
21
18
|
import ts from 'typescript'
|
|
22
19
|
import { watchEffect } from 'vue'
|
|
20
|
+
|
|
21
|
+
import configs from '#slidev/configs'
|
|
22
|
+
import setups from '#slidev/setups/monaco'
|
|
23
23
|
import { isDark } from '../logic/dark'
|
|
24
24
|
import { lockShortcuts } from '../state'
|
|
25
25
|
|
package/setup/pwa.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export const pwaCaching = ref(false)
|
|
4
|
+
export const pwaReady = ref(false)
|
|
5
|
+
|
|
6
|
+
export async function setupPWA() {
|
|
7
|
+
if (!__SLIDEV_FEATURE_PWA__)
|
|
8
|
+
return
|
|
9
|
+
|
|
10
|
+
const { registerSW } = await import('virtual:pwa-register')
|
|
11
|
+
|
|
12
|
+
pwaCaching.value = true
|
|
13
|
+
|
|
14
|
+
registerSW({
|
|
15
|
+
immediate: true,
|
|
16
|
+
onOfflineReady() {
|
|
17
|
+
pwaCaching.value = false
|
|
18
|
+
pwaReady.value = true
|
|
19
|
+
// Auto-dismiss "ready" indicator after 4 seconds
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
pwaReady.value = false
|
|
22
|
+
}, 4000)
|
|
23
|
+
},
|
|
24
|
+
onRegisteredSW(_swUrl, _registration) {
|
|
25
|
+
// SW registered; caching is in progress until onOfflineReady fires
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
}
|
package/setup/root.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import setups from '#slidev/setups/root'
|
|
2
1
|
import { useHead } from '@unhead/vue'
|
|
3
2
|
import { computed, getCurrentInstance, reactive, ref, shallowRef, watch } from 'vue'
|
|
4
3
|
import { useRouter } from 'vue-router'
|
|
4
|
+
import setups from '#slidev/setups/root'
|
|
5
5
|
import { createFixedClicks } from '../composables/useClicks'
|
|
6
6
|
import { useEmbeddedControl } from '../composables/useEmbeddedCtrl'
|
|
7
7
|
import { useNav } from '../composables/useNav'
|
package/setup/shiki.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import setups from '#slidev/setups/shiki'
|
|
2
1
|
import { createSingletonPromise } from '@antfu/utils'
|
|
3
2
|
import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'
|
|
4
3
|
import { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'
|
|
4
|
+
import setups from '#slidev/setups/shiki'
|
|
5
5
|
import { resolveShikiOptions, shikiContext } from './shiki-options'
|
|
6
6
|
|
|
7
7
|
export default createSingletonPromise(async () => {
|
package/setup/shortcuts.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NavOperations, ShortcutOptions } from '@slidev/types'
|
|
2
|
-
import setups from '#slidev/setups/shortcuts'
|
|
3
2
|
import { and, not, or } from '@vueuse/math'
|
|
3
|
+
import setups from '#slidev/setups/shortcuts'
|
|
4
4
|
import { useDrawings } from '../composables/useDrawings'
|
|
5
5
|
import { useNav } from '../composables/useNav'
|
|
6
6
|
import { toggleDark } from '../logic/dark'
|
package/shim-vue.d.ts
CHANGED