@slidev/client 0.48.0-beta.8 → 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 +15 -17
- package/internals/DrawingLayer.vue +6 -5
- package/internals/DrawingPreview.vue +4 -2
- package/internals/Goto.vue +9 -6
- package/internals/IconButton.vue +7 -3
- package/internals/NavControls.vue +31 -12
- package/internals/NoteDisplay.vue +131 -8
- package/internals/NoteEditable.vue +129 -0
- package/internals/NoteStatic.vue +8 -6
- package/internals/PrintContainer.vue +11 -8
- package/internals/PrintSlide.vue +11 -12
- package/internals/PrintSlideClick.vue +14 -19
- package/internals/{SlidesOverview.vue → QuickOverview.vue} +35 -22
- package/internals/RecordingControls.vue +1 -1
- package/internals/RecordingDialog.vue +5 -6
- package/internals/{Editor.vue → SideEditor.vue} +26 -17
- 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/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
- package/{internals/NotesView.vue → pages/notes.vue} +9 -6
- package/pages/overview.vue +231 -0
- package/{internals/Play.vue → pages/play.vue} +22 -15
- package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +15 -8
- package/{internals/Presenter.vue → pages/presenter.vue} +129 -107
- package/{internals/Print.vue → pages/print.vue} +6 -5
- package/routes.ts +26 -57
- 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 +17 -12
- package/styles/shiki-twoslash.css +1 -1
- package/styles/vars.css +1 -0
- package/uno.config.ts +14 -2
- 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 -88
- 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
|
})
|
|
@@ -112,7 +113,7 @@ watchEffect(() => {
|
|
|
112
113
|
>
|
|
113
114
|
<div
|
|
114
115
|
v-show="value"
|
|
115
|
-
class="bg-main !bg-opacity-75 p-16 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
|
|
116
|
+
class="bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
|
|
116
117
|
@click="close()"
|
|
117
118
|
>
|
|
118
119
|
<div
|
|
@@ -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
|
-
class="inline-block border rounded
|
|
129
|
-
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border
|
|
130
|
-
|
|
131
|
-
@click="go(+route.path)"
|
|
129
|
+
class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
|
|
130
|
+
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
|
|
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,7 +163,20 @@ watchEffect(() => {
|
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
165
|
</Transition>
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
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
|
|
171
|
+
v-if="__DEV__"
|
|
172
|
+
as="a"
|
|
173
|
+
title="Slides Overview"
|
|
174
|
+
target="_blank"
|
|
175
|
+
href="/overview"
|
|
176
|
+
tab-index="-1"
|
|
177
|
+
class="text-2xl"
|
|
178
|
+
>
|
|
179
|
+
<carbon:list-boxes />
|
|
180
|
+
</IconButton>
|
|
181
|
+
</div>
|
|
169
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,19 +108,18 @@ async function start() {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
input[type=
|
|
112
|
-
@apply border border-
|
|
111
|
+
input[type='text'] {
|
|
112
|
+
@apply border border-main rounded px-2 py-1;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
button {
|
|
116
116
|
@apply bg-orange-400 text-white px-4 py-1 rounded border-b-2 border-orange-600;
|
|
117
|
-
@apply hover:(bg-orange-500 border-orange-700)
|
|
117
|
+
@apply hover:(bg-orange-500 border-orange-700);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
button.cancel {
|
|
121
|
-
@apply bg-gray-400 text-white px-4 py-1 rounded border-b-2 border-
|
|
122
|
-
@apply bg-opacity-
|
|
123
|
-
@apply hover:(bg-opacity-75 border-opacity-75)
|
|
121
|
+
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
|
|
122
|
+
@apply hover:(bg-opacity-75 border-opacity-75);
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
</style>
|
|
@@ -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' })
|
|
@@ -172,13 +181,13 @@ throttledWatch(
|
|
|
172
181
|
<div class="flex pb-2 text-xl -mt-1">
|
|
173
182
|
<div class="mr-4 rounded flex">
|
|
174
183
|
<IconButton
|
|
175
|
-
title="Switch to content tab" :class="tab === 'content' ? 'text
|
|
184
|
+
title="Switch to content tab" :class="tab === 'content' ? 'text-primary' : ''"
|
|
176
185
|
@click="switchTab('content')"
|
|
177
186
|
>
|
|
178
187
|
<carbon:account />
|
|
179
188
|
</IconButton>
|
|
180
189
|
<IconButton
|
|
181
|
-
title="Switch to notes tab" :class="tab === 'note' ? 'text
|
|
190
|
+
title="Switch to notes tab" :class="tab === 'note' ? 'text-primary' : ''"
|
|
182
191
|
@click="switchTab('note')"
|
|
183
192
|
>
|
|
184
193
|
<carbon:align-box-bottom-right />
|
|
@@ -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)
|