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