@slidev/client 0.47.5 → 0.48.0-beta.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/builtin/CodeBlockWrapper.vue +38 -27
- package/builtin/KaTexBlockWrapper.vue +37 -26
- package/builtin/Mermaid.vue +18 -10
- package/builtin/SlidevVideo.vue +4 -7
- package/builtin/VClick.ts +1 -1
- package/builtin/VClickGap.vue +38 -0
- package/builtin/VClicks.ts +51 -54
- package/composables/useClicks.ts +83 -0
- package/composables/useContext.ts +2 -8
- package/composables/useNav.ts +5 -2
- package/constants.ts +2 -5
- package/internals/DrawingControls.vue +27 -39
- package/internals/Editor.vue +64 -34
- package/internals/IconButton.vue +14 -0
- package/internals/NavControls.vue +36 -67
- package/internals/NotesView.vue +7 -10
- package/internals/Play.vue +3 -3
- package/internals/Presenter.vue +31 -38
- package/internals/PrintSlide.vue +10 -8
- package/internals/PrintSlideClick.vue +7 -16
- package/internals/RecordingControls.vue +10 -14
- package/internals/SlideWrapper.ts +9 -42
- package/internals/SlidesOverview.vue +5 -5
- package/internals/SlidesShow.vue +3 -10
- package/logic/nav.ts +31 -33
- package/logic/utils.ts +18 -0
- package/modules/context.ts +5 -8
- package/modules/directives.ts +124 -167
- package/package.json +5 -5
- package/routes.ts +2 -1
- package/setup/codemirror.ts +1 -3
- package/setup/root.ts +5 -5
- package/state/index.ts +4 -2
- package/composables/useNavClicks.ts +0 -38
- package/internals/HiddenText.vue +0 -9
package/internals/Presenter.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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 { clicksContext, currentPage, currentRoute, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
|
|
6
6
|
import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
|
|
7
7
|
import { configs, themeVars } from '../env'
|
|
8
8
|
import { sharedState } from '../state/shared'
|
|
@@ -10,6 +10,7 @@ import { registerShortcuts } from '../logic/shortcuts'
|
|
|
10
10
|
import { getSlideClass } from '../utils'
|
|
11
11
|
import { useTimer } from '../logic/utils'
|
|
12
12
|
import { isDrawing } from '../logic/drawings'
|
|
13
|
+
import { useFixedClicks } from '../composables/useClicks'
|
|
13
14
|
import SlideContainer from './SlideContainer.vue'
|
|
14
15
|
import NavControls from './NavControls.vue'
|
|
15
16
|
import SlidesOverview from './SlidesOverview.vue'
|
|
@@ -19,7 +20,7 @@ import Goto from './Goto.vue'
|
|
|
19
20
|
import SlidesShow from './SlidesShow.vue'
|
|
20
21
|
import SlideWrapper from './SlideWrapper'
|
|
21
22
|
import DrawingControls from './DrawingControls.vue'
|
|
22
|
-
import
|
|
23
|
+
import IconButton from './IconButton.vue'
|
|
23
24
|
|
|
24
25
|
const main = ref<HTMLDivElement>()
|
|
25
26
|
|
|
@@ -35,26 +36,21 @@ const notesEditing = ref(false)
|
|
|
35
36
|
|
|
36
37
|
const { timer, resetTimer } = useTimer()
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
route: nextRoute.value,
|
|
50
|
-
clicks: 0,
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
return null
|
|
55
|
-
}
|
|
56
|
-
}
|
|
39
|
+
const clicksCtxMap = rawRoutes.map(route => useFixedClicks(route))
|
|
40
|
+
const nextFrame = computed(() => {
|
|
41
|
+
if (clicksContext.value.current < clicksContext.value.total)
|
|
42
|
+
return [currentRoute.value!, clicksContext.value.current + 1] as const
|
|
43
|
+
else if (hasNext.value)
|
|
44
|
+
return [nextRoute.value!, 0] as const
|
|
45
|
+
else
|
|
46
|
+
return null
|
|
47
|
+
})
|
|
48
|
+
const nextFrameClicksCtx = computed(() => {
|
|
49
|
+
return nextFrame.value && clicksCtxMap[+nextFrame.value[0].path - 1]
|
|
57
50
|
})
|
|
51
|
+
watch([currentRoute, queryClicks], () => {
|
|
52
|
+
nextFrameClicksCtx.value && (nextFrameClicksCtx.value[0].value = nextFrame.value![1])
|
|
53
|
+
}, { immediate: true })
|
|
58
54
|
|
|
59
55
|
const Editor = shallowRef<any>()
|
|
60
56
|
if (__DEV__ && __SLIDEV_FEATURE_EDITOR__)
|
|
@@ -120,17 +116,16 @@ onMounted(() => {
|
|
|
120
116
|
</div>
|
|
121
117
|
<div class="relative grid-section next flex flex-col p-2 lg:p-4" :style="themeVars">
|
|
122
118
|
<SlideContainer
|
|
123
|
-
v-if="
|
|
119
|
+
v-if="nextFrame && nextFrameClicksCtx"
|
|
124
120
|
key="next"
|
|
125
121
|
class="h-full w-full"
|
|
126
122
|
>
|
|
127
123
|
<SlideWrapper
|
|
128
|
-
:is="
|
|
129
|
-
|
|
130
|
-
:clicks="
|
|
131
|
-
:
|
|
132
|
-
:
|
|
133
|
-
:route="nextSlide.route"
|
|
124
|
+
:is="nextFrame[0].component as any"
|
|
125
|
+
:key="nextFrame[0].path"
|
|
126
|
+
:clicks-context="nextFrameClicksCtx[1]"
|
|
127
|
+
:class="getSlideClass(nextFrame[0])"
|
|
128
|
+
:route="nextFrame[0]"
|
|
134
129
|
render-context="previewNext"
|
|
135
130
|
/>
|
|
136
131
|
</SlideContainer>
|
|
@@ -155,21 +150,19 @@ onMounted(() => {
|
|
|
155
150
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
156
151
|
/>
|
|
157
152
|
<div class="border-t border-main py-1 px-2 text-sm">
|
|
158
|
-
<
|
|
159
|
-
<HiddenText text="Increase font size" />
|
|
153
|
+
<IconButton title="Increase font size" @click="increasePresenterFontSize">
|
|
160
154
|
<carbon:zoom-in />
|
|
161
|
-
</
|
|
162
|
-
<
|
|
163
|
-
<HiddenText text="Decrease font size" />
|
|
155
|
+
</IconButton>
|
|
156
|
+
<IconButton title="Decrease font size" @click="decreasePresenterFontSize">
|
|
164
157
|
<carbon:zoom-out />
|
|
165
|
-
</
|
|
166
|
-
<
|
|
158
|
+
</IconButton>
|
|
159
|
+
<IconButton
|
|
167
160
|
v-if="__DEV__"
|
|
168
|
-
|
|
161
|
+
title="Edit Notes"
|
|
162
|
+
@click="notesEditing = !notesEditing"
|
|
169
163
|
>
|
|
170
|
-
<HiddenText text="Edit Notes" />
|
|
171
164
|
<carbon:edit />
|
|
172
|
-
</
|
|
165
|
+
</IconButton>
|
|
173
166
|
</div>
|
|
174
167
|
</div>
|
|
175
168
|
<div class="grid-section bottom">
|
package/internals/PrintSlide.vue
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
-
import { computed
|
|
3
|
+
import { computed } from 'vue'
|
|
4
4
|
import { useNav } from '../composables/useNav'
|
|
5
|
-
import {
|
|
5
|
+
import { useFixedClicks } from '../composables/useClicks'
|
|
6
6
|
import PrintSlideClick from './PrintSlideClick.vue'
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{ route: RouteRecordRaw }>()
|
|
9
9
|
|
|
10
|
-
const clicksElements = ref(props.route.meta?.__clicksElements || [])
|
|
11
|
-
const clicks = computed(() => props.route.meta?.clicks ?? clicksElements.value.length)
|
|
12
|
-
|
|
13
10
|
const route = computed(() => props.route)
|
|
14
11
|
const nav = useNav(route)
|
|
12
|
+
const clicks0 = useFixedClicks(route.value, 0)[1]
|
|
15
13
|
</script>
|
|
16
14
|
|
|
17
15
|
<template>
|
|
18
|
-
<PrintSlideClick
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
<PrintSlideClick
|
|
17
|
+
:clicks-context="clicks0"
|
|
18
|
+
:nav="nav"
|
|
19
|
+
:route="route"
|
|
20
|
+
/>
|
|
21
|
+
<template v-if="!clicks0.disabled">
|
|
22
|
+
<PrintSlideClick v-for="i of clicks0.total" :key="i" :clicks-context="useFixedClicks(route, i)[1]" :nav="nav" :route="route" />
|
|
21
23
|
</template>
|
|
22
24
|
</template>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
3
|
import { computed, provide, reactive, shallowRef } from 'vue'
|
|
4
|
-
import {
|
|
5
|
-
import { useNavClicks } from '../composables/useNavClicks'
|
|
4
|
+
import type { ClicksContext } from '@slidev/types'
|
|
6
5
|
import { injectionSlidevContext } from '../constants'
|
|
7
|
-
import { isClicksDisabled } from '../logic/nav'
|
|
8
6
|
import { configs, slideHeight, slideWidth } from '../env'
|
|
9
7
|
import { getSlideClass } from '../utils'
|
|
10
8
|
import type { SlidevContextNav } from '../modules/context'
|
|
@@ -17,16 +15,11 @@ import GlobalTop from '/@slidev/global-components/top'
|
|
|
17
15
|
import GlobalBottom from '/@slidev/global-components/bottom'
|
|
18
16
|
|
|
19
17
|
const props = defineProps<{
|
|
20
|
-
|
|
21
|
-
clicksElements?: HTMLElement[]
|
|
18
|
+
clicksContext: ClicksContext
|
|
22
19
|
nav: SlidevContextNav
|
|
23
20
|
route: RouteRecordRaw
|
|
24
21
|
}>()
|
|
25
22
|
|
|
26
|
-
const emit = defineEmits(['update:clicksElements'])
|
|
27
|
-
|
|
28
|
-
const clicksElements = useVModel(props, 'clicksElements', emit)
|
|
29
|
-
|
|
30
23
|
const style = computed(() => ({
|
|
31
24
|
height: `${slideHeight}px`,
|
|
32
25
|
width: `${slideWidth}px`,
|
|
@@ -36,12 +29,12 @@ const DrawingPreview = shallowRef<any>()
|
|
|
36
29
|
if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
37
30
|
import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
|
|
38
31
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
const id = computed(() =>
|
|
33
|
+
`${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
|
|
34
|
+
)
|
|
42
35
|
|
|
43
36
|
provide(injectionSlidevContext, reactive({
|
|
44
|
-
nav:
|
|
37
|
+
nav: props.nav,
|
|
45
38
|
configs,
|
|
46
39
|
themeConfigs: computed(() => configs.themeConfig),
|
|
47
40
|
}))
|
|
@@ -53,9 +46,7 @@ provide(injectionSlidevContext, reactive({
|
|
|
53
46
|
|
|
54
47
|
<SlideWrapper
|
|
55
48
|
:is="route?.component!"
|
|
56
|
-
|
|
57
|
-
:clicks="isClicksDisabled ? undefined : clicks"
|
|
58
|
-
:clicks-disabled="isClicksDisabled"
|
|
49
|
+
:clicks-context="clicksContext"
|
|
59
50
|
:class="getSlideClass(route)"
|
|
60
51
|
:route="route"
|
|
61
52
|
/>
|
|
@@ -5,7 +5,7 @@ import { recorder } from '../logic/recording'
|
|
|
5
5
|
import { currentCamera, showRecordingDialog } from '../state'
|
|
6
6
|
import DevicesList from './DevicesList.vue'
|
|
7
7
|
import MenuButton from './MenuButton.vue'
|
|
8
|
-
import
|
|
8
|
+
import IconButton from './IconButton.vue'
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
recording,
|
|
@@ -34,33 +34,29 @@ onMounted(() => {
|
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
<template>
|
|
37
|
-
<
|
|
37
|
+
<IconButton
|
|
38
38
|
v-if="currentCamera !== 'none'"
|
|
39
|
-
class="
|
|
39
|
+
class="<md:hidden"
|
|
40
40
|
:class="{ 'text-green-500': Boolean(showAvatar && streamCamera) }"
|
|
41
|
-
title="
|
|
41
|
+
title="Toggle camera view"
|
|
42
42
|
@click="toggleAvatar"
|
|
43
43
|
>
|
|
44
|
-
<HiddenText text="Toggle camera view" />
|
|
45
44
|
<carbon:user-avatar />
|
|
46
|
-
</
|
|
45
|
+
</IconButton>
|
|
47
46
|
|
|
48
|
-
<
|
|
49
|
-
class="slidev-icon-btn"
|
|
47
|
+
<IconButton
|
|
50
48
|
:class="{ 'text-red-500': recording }"
|
|
51
|
-
title="
|
|
49
|
+
:title="recording ? 'Stop record video' : 'Record video'"
|
|
52
50
|
@click="toggleRecording"
|
|
53
51
|
>
|
|
54
|
-
<HiddenText :text="recording ? 'Stop record video' : 'Record video'" />
|
|
55
52
|
<carbon:stop-outline v-if="recording" />
|
|
56
53
|
<carbon:video v-else />
|
|
57
|
-
</
|
|
54
|
+
</IconButton>
|
|
58
55
|
<MenuButton :disabled="recording">
|
|
59
56
|
<template #button>
|
|
60
|
-
<
|
|
61
|
-
<HiddenText text="Select recording device" />
|
|
57
|
+
<IconButton title="Select recording device" class="h-full !text-sm !px-0">
|
|
62
58
|
<carbon:chevron-up class="opacity-50" />
|
|
63
|
-
</
|
|
59
|
+
</IconButton>
|
|
64
60
|
</template>
|
|
65
61
|
<template #menu>
|
|
66
62
|
<DevicesList />
|
|
@@ -1,26 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { RenderContext } from '@slidev/types'
|
|
4
|
-
import { injectionActive,
|
|
1
|
+
import { defineComponent, h, provide, ref, toRef } from 'vue'
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
4
|
+
import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
|
|
5
5
|
|
|
6
6
|
export default defineComponent({
|
|
7
7
|
name: 'SlideWrapper',
|
|
8
8
|
props: {
|
|
9
|
-
|
|
10
|
-
type:
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
clicksElements: {
|
|
14
|
-
type: Array,
|
|
15
|
-
default: () => [] as Element[],
|
|
16
|
-
},
|
|
17
|
-
clicksOrderMap: {
|
|
18
|
-
type: Map,
|
|
19
|
-
default: () => new Map<number, HTMLElement[]>(),
|
|
20
|
-
},
|
|
21
|
-
clicksDisabled: {
|
|
22
|
-
type: Boolean,
|
|
23
|
-
default: false,
|
|
9
|
+
clicksContext: {
|
|
10
|
+
type: Object as PropType<ClicksContext>,
|
|
11
|
+
required: true,
|
|
24
12
|
},
|
|
25
13
|
renderContext: {
|
|
26
14
|
type: String,
|
|
@@ -39,33 +27,12 @@ export default defineComponent({
|
|
|
39
27
|
default: undefined,
|
|
40
28
|
},
|
|
41
29
|
},
|
|
42
|
-
setup(props
|
|
43
|
-
const clicks = useVModel(props, 'clicks', emit)
|
|
44
|
-
const clicksElements = useVModel(props, 'clicksElements', emit)
|
|
45
|
-
const clicksDisabled = useVModel(props, 'clicksDisabled', emit)
|
|
46
|
-
const clicksOrderMap = useVModel(props, 'clicksOrderMap', emit)
|
|
47
|
-
|
|
48
|
-
clicksElements.value.length = 0
|
|
49
|
-
|
|
50
|
-
const clicksWithDisable = computed({
|
|
51
|
-
get() {
|
|
52
|
-
if (clicksDisabled.value)
|
|
53
|
-
return 9999999
|
|
54
|
-
return +clicks.value
|
|
55
|
-
},
|
|
56
|
-
set(value) {
|
|
57
|
-
clicks.value = value
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
|
|
30
|
+
setup(props) {
|
|
61
31
|
provide(injectionRoute, props.route as any)
|
|
62
32
|
provide(injectionCurrentPage, ref(+props.route?.path))
|
|
63
33
|
provide(injectionRenderContext, ref(props.renderContext as RenderContext))
|
|
64
34
|
provide(injectionActive, toRef(props, 'active'))
|
|
65
|
-
provide(
|
|
66
|
-
provide(injectionClicksDisabled, clicksDisabled)
|
|
67
|
-
provide(injectionClicksElements, clicksElements as any)
|
|
68
|
-
provide(injectionOrderMap, clicksOrderMap as any)
|
|
35
|
+
provide(injectionClicksContext, toRef(props, 'clicksContext'))
|
|
69
36
|
},
|
|
70
37
|
render() {
|
|
71
38
|
if (this.$props.is)
|
|
@@ -5,11 +5,12 @@ import { themeVars } from '../env'
|
|
|
5
5
|
import { breakpoints, showOverview, windowSize } from '../state'
|
|
6
6
|
import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
|
|
7
7
|
import { currentOverviewPage, overviewRowCount } from '../logic/overview'
|
|
8
|
+
import { useFixedClicks } from '../composables/useClicks'
|
|
8
9
|
import { getSlideClass } from '../utils'
|
|
9
10
|
import SlideContainer from './SlideContainer.vue'
|
|
10
11
|
import SlideWrapper from './SlideWrapper'
|
|
11
12
|
import DrawingPreview from './DrawingPreview.vue'
|
|
12
|
-
import
|
|
13
|
+
import IconButton from './IconButton.vue'
|
|
13
14
|
|
|
14
15
|
const props = defineProps<{ modelValue: boolean }>()
|
|
15
16
|
|
|
@@ -138,7 +139,7 @@ watchEffect(() => {
|
|
|
138
139
|
<SlideWrapper
|
|
139
140
|
:is="route.component"
|
|
140
141
|
v-if="route?.component"
|
|
141
|
-
:clicks-
|
|
142
|
+
:clicks-context="useFixedClicks(route, 99999)[1]"
|
|
142
143
|
:class="getSlideClass(route)"
|
|
143
144
|
:route="route"
|
|
144
145
|
render-context="overview"
|
|
@@ -162,8 +163,7 @@ watchEffect(() => {
|
|
|
162
163
|
</div>
|
|
163
164
|
</div>
|
|
164
165
|
</Transition>
|
|
165
|
-
<
|
|
166
|
-
<HiddenText text="Close" />
|
|
166
|
+
<IconButton v-if="value" title="Close" class="fixed text-2xl top-4 right-4 text-gray-400" @click="close">
|
|
167
167
|
<carbon:close />
|
|
168
|
-
</
|
|
168
|
+
</IconButton>
|
|
169
169
|
</template>
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { TransitionGroup, computed, shallowRef, watch } from 'vue'
|
|
3
|
-
import {
|
|
3
|
+
import { currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
|
|
4
4
|
import { getSlideClass } from '../utils'
|
|
5
5
|
import { useViewTransition } from '../composables/useViewTransition'
|
|
6
6
|
import { skipTransition } from '../composables/hmr'
|
|
7
|
+
import { usePrimaryClicks } from '../composables/useClicks'
|
|
7
8
|
import SlideWrapper from './SlideWrapper'
|
|
8
9
|
|
|
9
10
|
// @ts-expect-error virtual module
|
|
@@ -27,12 +28,6 @@ watch(currentRoute, () => {
|
|
|
27
28
|
|
|
28
29
|
const hasViewTransition = useViewTransition()
|
|
29
30
|
|
|
30
|
-
// preserve the clicks count for previous slide to avoid flash on transition
|
|
31
|
-
let previousClicks: [string | undefined, number] = [] as any
|
|
32
|
-
router.beforeEach(() => {
|
|
33
|
-
previousClicks = [currentRoute.value?.path, clicks.value]
|
|
34
|
-
})
|
|
35
|
-
|
|
36
31
|
const DrawingLayer = shallowRef<any>()
|
|
37
32
|
if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
38
33
|
import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
|
|
@@ -62,9 +57,7 @@ function onAfterLeave() {
|
|
|
62
57
|
<SlideWrapper
|
|
63
58
|
:is="route?.component as any"
|
|
64
59
|
v-show="route === currentRoute"
|
|
65
|
-
:clicks="route
|
|
66
|
-
:clicks-elements="route.meta?.__clicksElements || []"
|
|
67
|
-
:clicks-disabled="false"
|
|
60
|
+
:clicks-context="usePrimaryClicks(route)"
|
|
68
61
|
:class="getSlideClass(route)"
|
|
69
62
|
:route="route"
|
|
70
63
|
:render-context="renderContext"
|
package/logic/nav.ts
CHANGED
|
@@ -6,19 +6,21 @@ import { timestamp, usePointerSwipe } from '@vueuse/core'
|
|
|
6
6
|
import { rawRoutes, router } from '../routes'
|
|
7
7
|
import { configs } from '../env'
|
|
8
8
|
import { skipTransition } from '../composables/hmr'
|
|
9
|
+
import { usePrimaryClicks } from '../composables/useClicks'
|
|
9
10
|
import { useRouteQuery } from './route'
|
|
10
11
|
import { isDrawing } from './drawings'
|
|
11
12
|
|
|
12
13
|
export { rawRoutes, router }
|
|
13
14
|
|
|
14
15
|
// force update collected elements when the route is fully resolved
|
|
15
|
-
const routeForceRefresh = ref(0)
|
|
16
|
+
export const routeForceRefresh = ref(0)
|
|
16
17
|
nextTick(() => {
|
|
17
18
|
router.afterEach(async () => {
|
|
18
19
|
await nextTick()
|
|
19
20
|
routeForceRefresh.value += 1
|
|
20
21
|
})
|
|
21
22
|
})
|
|
23
|
+
|
|
22
24
|
export const navDirection = ref(0)
|
|
23
25
|
|
|
24
26
|
export const route = computed(() => router.currentRoute.value)
|
|
@@ -28,11 +30,25 @@ export const isPrintWithClicks = computed(() => route.value.query.print === 'cli
|
|
|
28
30
|
export const isEmbedded = computed(() => route.value.query.embedded !== undefined)
|
|
29
31
|
export const isPresenter = computed(() => route.value.path.startsWith('/presenter'))
|
|
30
32
|
export const isNotesViewer = computed(() => route.value.path.startsWith('/notes'))
|
|
31
|
-
export const isClicksDisabled = computed(() => isPrintMode.value && !isPrintWithClicks.value)
|
|
32
33
|
export const presenterPassword = computed(() => route.value.query.password)
|
|
33
34
|
export const showPresenter = computed(() => !isPresenter.value && (!configs.remote || presenterPassword.value === configs.remote))
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
const queryClicksRaw = useRouteQuery('clicks', '0')
|
|
37
|
+
export const queryClicks = computed({
|
|
38
|
+
get() {
|
|
39
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
40
|
+
if (clicksContext.value.disabled)
|
|
41
|
+
return 99999
|
|
42
|
+
let v = +(queryClicksRaw.value || 0)
|
|
43
|
+
if (Number.isNaN(v))
|
|
44
|
+
v = 0
|
|
45
|
+
return v
|
|
46
|
+
},
|
|
47
|
+
set(v) {
|
|
48
|
+
queryClicksRaw.value = v.toString()
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
36
52
|
export const total = computed(() => rawRoutes.length)
|
|
37
53
|
export const path = computed(() => route.value.path)
|
|
38
54
|
|
|
@@ -45,27 +61,9 @@ export const currentLayout = computed(() => currentRoute.value?.meta?.layout ||
|
|
|
45
61
|
export const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length, currentPage.value + 1)}`))
|
|
46
62
|
export const prevRoute = computed(() => rawRoutes.find(i => i.path === `${Math.max(1, currentPage.value - 1)}`))
|
|
47
63
|
|
|
48
|
-
export const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return currentRoute.value?.meta?.__clicksElements || []
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
export const clicks = computed<number>({
|
|
55
|
-
get() {
|
|
56
|
-
if (isClicksDisabled.value)
|
|
57
|
-
return 99999
|
|
58
|
-
let clicks = +(queryClicks.value || 0)
|
|
59
|
-
if (Number.isNaN(clicks))
|
|
60
|
-
clicks = 0
|
|
61
|
-
return clicks
|
|
62
|
-
},
|
|
63
|
-
set(v) {
|
|
64
|
-
queryClicks.value = v.toString()
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
export const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))
|
|
64
|
+
export const clicksContext = computed(() => usePrimaryClicks(currentRoute.value))
|
|
65
|
+
export const clicks = computed(() => clicksContext.value.current)
|
|
66
|
+
export const clicksTotal = computed(() => clicksContext.value.total)
|
|
69
67
|
|
|
70
68
|
export const hasNext = computed(() => currentPage.value < rawRoutes.length || clicks.value < clicksTotal.value)
|
|
71
69
|
export const hasPrev = computed(() => currentPage.value > 1 || clicks.value > 0)
|
|
@@ -85,27 +83,27 @@ watch(currentRoute, (next, prev) => {
|
|
|
85
83
|
navDirection.value = Number(next?.path) - Number(prev?.path)
|
|
86
84
|
})
|
|
87
85
|
|
|
88
|
-
export function next() {
|
|
89
|
-
if (clicksTotal.value <=
|
|
90
|
-
nextSlide()
|
|
86
|
+
export async function next() {
|
|
87
|
+
if (clicksTotal.value <= queryClicks.value)
|
|
88
|
+
await nextSlide()
|
|
91
89
|
else
|
|
92
|
-
|
|
90
|
+
queryClicks.value += 1
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
export async function prev() {
|
|
96
|
-
if (
|
|
94
|
+
if (queryClicks.value <= 0)
|
|
97
95
|
await prevSlide()
|
|
98
96
|
else
|
|
99
|
-
|
|
97
|
+
queryClicks.value -= 1
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
export function getPath(no: number | string) {
|
|
103
101
|
return isPresenter.value ? `/presenter/${no}` : `/${no}`
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
export function nextSlide() {
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
export async function nextSlide() {
|
|
105
|
+
if (currentPage.value < rawRoutes.length)
|
|
106
|
+
await go(currentPage.value + 1)
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
export async function prevSlide(lastClicks = true) {
|
package/logic/utils.ts
CHANGED
|
@@ -30,3 +30,21 @@ export function makeId(length = 5) {
|
|
|
30
30
|
result.push(characters.charAt(Math.floor(Math.random() * charactersLength)))
|
|
31
31
|
return result.join('')
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* '+3' => '+3'
|
|
36
|
+
* '-3' => '-3'
|
|
37
|
+
* '3' => 3
|
|
38
|
+
* 3 => 3
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeAtProp(at: string | number = '+1'): [isRelative: boolean, value: number] {
|
|
41
|
+
let n = +at
|
|
42
|
+
if (Number.isNaN(n)) {
|
|
43
|
+
console.warn('[slidev] Invalid click position:', at)
|
|
44
|
+
n = 0
|
|
45
|
+
}
|
|
46
|
+
return [
|
|
47
|
+
typeof at === 'string' && '+-'.includes(at[0]),
|
|
48
|
+
n,
|
|
49
|
+
]
|
|
50
|
+
}
|
package/modules/context.ts
CHANGED
|
@@ -4,21 +4,19 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
|
|
4
4
|
import type { ComputedRef } from '@vue/reactivity'
|
|
5
5
|
import type { configs } from '../env'
|
|
6
6
|
import * as nav from '../logic/nav'
|
|
7
|
-
import {
|
|
7
|
+
import { route } from '../logic/nav'
|
|
8
8
|
import { isDark } from '../logic/dark'
|
|
9
|
-
import {
|
|
9
|
+
import { injectionCurrentPage, injectionSlidevContext } from '../constants'
|
|
10
10
|
import { useContext } from '../composables/useContext'
|
|
11
11
|
|
|
12
|
-
export type SlidevContextNavKey = 'path' | 'total' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide' | 'rawRoutes' | 'go'
|
|
13
|
-
export type SlidevContextNavClicksKey = 'clicks' | 'clicksElements' | 'clicksTotal' | 'hasNext' | 'hasPrev'
|
|
12
|
+
export type SlidevContextNavKey = 'path' | 'total' | 'clicksContext' | 'clicks' | 'clicksTotal' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide' | 'rawRoutes' | 'go'
|
|
14
13
|
|
|
15
14
|
export interface SlidevContextNav extends Pick<typeof nav, SlidevContextNavKey> {
|
|
16
15
|
route: ComputedRef<RouteRecordRaw | RouteLocationNormalizedLoaded>
|
|
17
16
|
}
|
|
18
|
-
export type SlidevContextNavClicks = Pick<typeof nav, SlidevContextNavClicksKey>
|
|
19
17
|
|
|
20
18
|
export interface SlidevContext {
|
|
21
|
-
nav: SlidevContextNav
|
|
19
|
+
nav: SlidevContextNav
|
|
22
20
|
configs: typeof configs
|
|
23
21
|
themeConfigs: ComputedRef<typeof configs['themeConfig']>
|
|
24
22
|
}
|
|
@@ -26,10 +24,9 @@ export interface SlidevContext {
|
|
|
26
24
|
export default function createSlidevContext() {
|
|
27
25
|
return {
|
|
28
26
|
install(app: App) {
|
|
29
|
-
const context = reactive(useContext(route
|
|
27
|
+
const context = reactive(useContext(route))
|
|
30
28
|
app.provide(injectionSlidevContext, context)
|
|
31
29
|
app.provide(injectionCurrentPage, computed(() => context.nav.currentPage))
|
|
32
|
-
app.provide(injectionClicks, computed(() => context.nav.clicks))
|
|
33
30
|
|
|
34
31
|
// allows controls from postMessages
|
|
35
32
|
if (__DEV__) {
|