@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
|
@@ -12,18 +12,23 @@ Learn more: https://sli.dev/guide/syntax.html#line-highlighting
|
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
|
-
import { range, remove } from '@antfu/utils'
|
|
16
15
|
import { parseRangeString } from '@slidev/parser/core'
|
|
17
16
|
import { useClipboard } from '@vueuse/core'
|
|
18
|
-
import { computed,
|
|
17
|
+
import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
|
18
|
+
import type { PropType } from 'vue'
|
|
19
19
|
import { configs } from '../env'
|
|
20
|
-
import { CLASS_VCLICK_TARGET, injectionClicks, injectionClicksDisabled, injectionClicksElements } from '../constants'
|
|
21
20
|
import { makeId } from '../logic/utils'
|
|
21
|
+
import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
|
|
22
22
|
|
|
23
23
|
const props = defineProps({
|
|
24
24
|
ranges: {
|
|
25
|
+
type: Array as PropType<string[]>,
|
|
25
26
|
default: () => [],
|
|
26
27
|
},
|
|
28
|
+
finally: {
|
|
29
|
+
type: [String, Number],
|
|
30
|
+
default: 'last',
|
|
31
|
+
},
|
|
27
32
|
startLine: {
|
|
28
33
|
type: Number,
|
|
29
34
|
default: 1,
|
|
@@ -33,8 +38,8 @@ const props = defineProps({
|
|
|
33
38
|
default: configs.lineNumbers,
|
|
34
39
|
},
|
|
35
40
|
at: {
|
|
36
|
-
type: Number,
|
|
37
|
-
default:
|
|
41
|
+
type: [String, Number],
|
|
42
|
+
default: '+1',
|
|
38
43
|
},
|
|
39
44
|
maxHeight: {
|
|
40
45
|
type: String,
|
|
@@ -42,39 +47,47 @@ const props = defineProps({
|
|
|
42
47
|
},
|
|
43
48
|
})
|
|
44
49
|
|
|
45
|
-
const clicks = inject(
|
|
46
|
-
const elements = inject(injectionClicksElements)
|
|
47
|
-
const disabled = inject(injectionClicksDisabled)
|
|
48
|
-
|
|
50
|
+
const clicks = inject(injectionClicksContext)?.value
|
|
49
51
|
const el = ref<HTMLDivElement>()
|
|
50
|
-
const
|
|
52
|
+
const id = makeId()
|
|
53
|
+
|
|
54
|
+
onUnmounted(() => {
|
|
55
|
+
clicks!.unregister(id)
|
|
56
|
+
})
|
|
51
57
|
|
|
52
58
|
onMounted(() => {
|
|
53
|
-
|
|
59
|
+
if (!clicks || clicks.disabled)
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
|
|
63
|
+
clicks.register(id, { max: end, delta })
|
|
64
|
+
|
|
54
65
|
const index = computed(() => {
|
|
55
|
-
if (disabled
|
|
66
|
+
if (clicks.disabled)
|
|
56
67
|
return props.ranges.length - 1
|
|
57
|
-
return Math.
|
|
68
|
+
return Math.max(0, clicks.current - start + 1)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const finallyRange = computed(() => {
|
|
72
|
+
return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
|
|
58
73
|
})
|
|
59
|
-
const rangeStr = computed(() => props.ranges[index.value] || '')
|
|
60
|
-
if (props.ranges.length >= 2 && !disabled?.value) {
|
|
61
|
-
const id = makeId()
|
|
62
|
-
const ids = range(props.ranges.length - 1).map(i => id + i)
|
|
63
|
-
if (elements?.value) {
|
|
64
|
-
elements.value.push(...ids)
|
|
65
|
-
onUnmounted(() => ids.forEach(i => remove(elements.value, i)), vm)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
74
|
|
|
69
75
|
watchEffect(() => {
|
|
70
76
|
if (!el.value)
|
|
71
77
|
return
|
|
78
|
+
|
|
79
|
+
let rangeStr = props.ranges[index.value] ?? finallyRange.value
|
|
80
|
+
const hide = rangeStr === 'hide'
|
|
81
|
+
el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
|
|
82
|
+
if (hide)
|
|
83
|
+
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
84
|
+
|
|
72
85
|
const isDuoTone = el.value.querySelector('.shiki-dark')
|
|
73
86
|
const targets = isDuoTone ? Array.from(el.value.querySelectorAll('.shiki')) : [el.value]
|
|
74
87
|
const startLine = props.startLine
|
|
75
88
|
for (const target of targets) {
|
|
76
89
|
const lines = Array.from(target.querySelectorAll('code > .line'))
|
|
77
|
-
const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr
|
|
90
|
+
const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr)
|
|
78
91
|
lines.forEach((line, idx) => {
|
|
79
92
|
const highlighted = highlights.includes(idx + startLine)
|
|
80
93
|
line.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
@@ -107,11 +120,9 @@ function copyCode() {
|
|
|
107
120
|
|
|
108
121
|
<template>
|
|
109
122
|
<div
|
|
110
|
-
ref="el" class="slidev-code-wrapper relative group"
|
|
111
|
-
:class="{
|
|
123
|
+
ref="el" class="slidev-code-wrapper relative group" :class="{
|
|
112
124
|
'slidev-code-line-numbers': props.lines,
|
|
113
|
-
}"
|
|
114
|
-
:style="{
|
|
125
|
+
}" :style="{
|
|
115
126
|
'max-height': props.maxHeight,
|
|
116
127
|
'overflow-y': props.maxHeight ? 'scroll' : undefined,
|
|
117
128
|
'--start': props.startLine,
|
|
@@ -20,53 +20,66 @@ Learn more: https://sli.dev/guide/syntax.html#latex-line-highlighting
|
|
|
20
20
|
-->
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
|
|
24
|
+
import type { PropType } from 'vue'
|
|
25
25
|
import { parseRangeString } from '@slidev/parser'
|
|
26
|
-
import {
|
|
26
|
+
import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
|
|
27
27
|
import { makeId } from '../logic/utils'
|
|
28
28
|
|
|
29
29
|
const props = defineProps({
|
|
30
30
|
ranges: {
|
|
31
|
+
type: Array as PropType<string[]>,
|
|
31
32
|
default: () => [],
|
|
32
33
|
},
|
|
34
|
+
finally: {
|
|
35
|
+
type: [String, Number],
|
|
36
|
+
default: 'last',
|
|
37
|
+
},
|
|
33
38
|
startLine: {
|
|
34
39
|
type: Number,
|
|
35
40
|
default: 1,
|
|
36
41
|
},
|
|
37
42
|
at: {
|
|
38
|
-
type: Number,
|
|
39
|
-
default:
|
|
43
|
+
type: [String, Number],
|
|
44
|
+
default: '+1',
|
|
40
45
|
},
|
|
41
46
|
})
|
|
42
47
|
|
|
43
|
-
const clicks = inject(
|
|
44
|
-
const elements = inject(injectionClicksElements)
|
|
45
|
-
const disabled = inject(injectionClicksDisabled)
|
|
46
|
-
|
|
48
|
+
const clicks = inject(injectionClicksContext)?.value
|
|
47
49
|
const el = ref<HTMLDivElement>()
|
|
48
|
-
const
|
|
50
|
+
const id = makeId()
|
|
51
|
+
|
|
52
|
+
onUnmounted(() => {
|
|
53
|
+
clicks!.unregister(id)
|
|
54
|
+
})
|
|
49
55
|
|
|
50
56
|
onMounted(() => {
|
|
51
|
-
|
|
57
|
+
if (!clicks || clicks.disabled)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
|
|
61
|
+
clicks.register(id, { max: end, delta })
|
|
62
|
+
|
|
52
63
|
const index = computed(() => {
|
|
53
|
-
if (disabled
|
|
64
|
+
if (clicks.disabled)
|
|
54
65
|
return props.ranges.length - 1
|
|
55
|
-
return Math.
|
|
66
|
+
return Math.max(0, clicks.current - start + 1)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const finallyRange = computed(() => {
|
|
70
|
+
return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
|
|
56
71
|
})
|
|
57
|
-
const rangeStr = computed(() => props.ranges[index.value] || '')
|
|
58
|
-
if (props.ranges.length >= 2 && !disabled?.value) {
|
|
59
|
-
const id = makeId()
|
|
60
|
-
const ids = range(props.ranges.length - 1).map(i => id + i)
|
|
61
|
-
if (elements?.value) {
|
|
62
|
-
elements.value.push(...ids)
|
|
63
|
-
onUnmounted(() => ids.forEach(i => remove(elements.value, i)), vm)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
72
|
|
|
67
73
|
watchEffect(() => {
|
|
68
74
|
if (!el.value)
|
|
69
75
|
return
|
|
76
|
+
|
|
77
|
+
let rangeStr = props.ranges[index.value] ?? finallyRange.value
|
|
78
|
+
const hide = rangeStr === 'hide'
|
|
79
|
+
el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
|
|
80
|
+
if (hide)
|
|
81
|
+
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
82
|
+
|
|
70
83
|
// KaTeX equations have col-align-XXX as parent
|
|
71
84
|
const equationParents = el.value.querySelectorAll('.mtable > [class*=col-align]')
|
|
72
85
|
if (!equationParents)
|
|
@@ -89,7 +102,7 @@ onMounted(() => {
|
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
const startLine = props.startLine
|
|
92
|
-
const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr
|
|
105
|
+
const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr)
|
|
93
106
|
lines.forEach((line, idx) => {
|
|
94
107
|
const highlighted = highlights.includes(idx + startLine)
|
|
95
108
|
line.forEach((node) => {
|
|
@@ -103,9 +116,7 @@ onMounted(() => {
|
|
|
103
116
|
</script>
|
|
104
117
|
|
|
105
118
|
<template>
|
|
106
|
-
<div
|
|
107
|
-
ref="el" class="slidev-katex-wrapper"
|
|
108
|
-
>
|
|
119
|
+
<div ref="el" class="slidev-katex-wrapper">
|
|
109
120
|
<slot />
|
|
110
121
|
</div>
|
|
111
122
|
</template>
|
package/builtin/Mermaid.vue
CHANGED
|
@@ -26,6 +26,7 @@ const props = defineProps<{
|
|
|
26
26
|
|
|
27
27
|
const vm = getCurrentInstance()
|
|
28
28
|
const el = ref<ShadowRoot>()
|
|
29
|
+
const error = ref<string | null>(null)
|
|
29
30
|
const html = ref('')
|
|
30
31
|
|
|
31
32
|
watchEffect(async (onCleanup) => {
|
|
@@ -33,15 +34,21 @@ watchEffect(async (onCleanup) => {
|
|
|
33
34
|
onCleanup(() => {
|
|
34
35
|
disposed = true
|
|
35
36
|
})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
error.value = null
|
|
38
|
+
try {
|
|
39
|
+
const svg = await renderMermaid(
|
|
40
|
+
props.code || '',
|
|
41
|
+
{
|
|
42
|
+
theme: props.theme || (isDark.value ? 'dark' : undefined),
|
|
43
|
+
...vm!.attrs,
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
if (!disposed)
|
|
47
|
+
html.value = svg
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
error.value = `${e}`
|
|
51
|
+
}
|
|
45
52
|
})
|
|
46
53
|
|
|
47
54
|
const actualHeight = ref<number>()
|
|
@@ -69,5 +76,6 @@ watchEffect(() => {
|
|
|
69
76
|
</script>
|
|
70
77
|
|
|
71
78
|
<template>
|
|
72
|
-
<
|
|
79
|
+
<pre v-if="error" border="1 red rounded" class="pa-3">{{ error }}</pre>
|
|
80
|
+
<ShadowRoot v-else class="mermaid" :inner-html="html" @shadow="el = $event" />
|
|
73
81
|
</template>
|
package/builtin/SlidevVideo.vue
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
3
|
-
|
|
4
|
-
import { injectionClicks, injectionClicksDisabled, injectionClicksElements, injectionRenderContext, injectionRoute, injectionSlidevContext } from '../constants'
|
|
3
|
+
import { injectionClicksContext, injectionRenderContext, injectionRoute, injectionSlidevContext } from '../constants'
|
|
5
4
|
|
|
6
5
|
const props = defineProps<{
|
|
7
6
|
autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
|
|
@@ -12,9 +11,7 @@ const props = defineProps<{
|
|
|
12
11
|
const $slidev = inject(injectionSlidevContext)
|
|
13
12
|
const route = inject(injectionRoute)
|
|
14
13
|
const currentContext = inject(injectionRenderContext)
|
|
15
|
-
const clicks = inject(
|
|
16
|
-
const clicksDisabled = inject(injectionClicksDisabled)
|
|
17
|
-
const clicksElements = inject(injectionClicksElements)
|
|
14
|
+
const clicks = inject(injectionClicksContext)?.value
|
|
18
15
|
|
|
19
16
|
const video = ref<HTMLMediaElement>()
|
|
20
17
|
const played = ref(false)
|
|
@@ -27,9 +24,9 @@ const matchRoute = computed(() => {
|
|
|
27
24
|
})
|
|
28
25
|
|
|
29
26
|
const matchClick = computed(() => {
|
|
30
|
-
if (!video.value || currentContext?.value !== 'slide' || clicks?.
|
|
27
|
+
if (!video.value || currentContext?.value !== 'slide' || clicks?.disabled || clicks?.current === undefined)
|
|
31
28
|
return false
|
|
32
|
-
return
|
|
29
|
+
return clicks.map.get(video.value)?.isShown?.value ?? true
|
|
33
30
|
})
|
|
34
31
|
|
|
35
32
|
const matchRouteAndClick = computed(() => matchRoute.value && matchClick.value)
|
package/builtin/VClick.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Fragment, inject, onMounted, watchEffect } from 'vue'
|
|
3
|
+
import { injectionClicksContext } from '../constants'
|
|
4
|
+
import { makeId } from '../logic/utils'
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
size: {
|
|
8
|
+
type: [String, Number],
|
|
9
|
+
default: 1,
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const clicksRef = inject(injectionClicksContext)
|
|
14
|
+
|
|
15
|
+
onMounted(() => {
|
|
16
|
+
watchEffect((onCleanup) => {
|
|
17
|
+
const clicks = clicksRef?.value
|
|
18
|
+
|
|
19
|
+
if (!clicks || clicks.disabled)
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
let delta = +props.size
|
|
23
|
+
if (Number.isNaN(delta)) {
|
|
24
|
+
console.warn(`[slidev] Invalid size for VClickGap: ${props.size}`)
|
|
25
|
+
delta = 1
|
|
26
|
+
}
|
|
27
|
+
const max = clicks.currentOffset + delta - 1
|
|
28
|
+
|
|
29
|
+
const id = makeId()
|
|
30
|
+
clicks.register(id, { max, delta })
|
|
31
|
+
onCleanup(() => clicks.unregister(id))
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<Fragment />
|
|
38
|
+
</template>
|
package/builtin/VClicks.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { toArray } from '@antfu/utils'
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
import {
|
|
8
|
+
import type { VNode, VNodeArrayChildren } from 'vue'
|
|
9
|
+
import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
|
|
10
|
+
import { normalizeAtProp } from '../logic/utils'
|
|
11
|
+
import VClickGap from './VClickGap.vue'
|
|
11
12
|
|
|
12
13
|
const listTags = ['ul', 'ol']
|
|
13
14
|
|
|
@@ -18,12 +19,12 @@ export default defineComponent({
|
|
|
18
19
|
default: 1,
|
|
19
20
|
},
|
|
20
21
|
every: {
|
|
21
|
-
type: Number,
|
|
22
|
+
type: [Number, String],
|
|
22
23
|
default: 1,
|
|
23
24
|
},
|
|
24
25
|
at: {
|
|
25
26
|
type: [Number, String],
|
|
26
|
-
default:
|
|
27
|
+
default: '+1',
|
|
27
28
|
},
|
|
28
29
|
hide: {
|
|
29
30
|
type: Boolean,
|
|
@@ -35,13 +36,15 @@ export default defineComponent({
|
|
|
35
36
|
},
|
|
36
37
|
},
|
|
37
38
|
render() {
|
|
39
|
+
const every = +this.every
|
|
40
|
+
const [isRelative, at] = normalizeAtProp(this.at)
|
|
41
|
+
|
|
38
42
|
const click = resolveDirective('click')!
|
|
39
|
-
const after = resolveDirective('after')!
|
|
40
43
|
|
|
41
|
-
const applyDirective = (node: VNode,
|
|
44
|
+
const applyDirective = (node: VNode, value: number | string) => {
|
|
42
45
|
return withDirectives(node, [[
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
click,
|
|
47
|
+
value,
|
|
45
48
|
'',
|
|
46
49
|
{
|
|
47
50
|
hide: this.hide,
|
|
@@ -59,83 +62,77 @@ export default defineComponent({
|
|
|
59
62
|
}) as T
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
let
|
|
65
|
+
let elements = this.$slots.default?.()
|
|
63
66
|
|
|
64
|
-
if (!
|
|
67
|
+
if (!elements)
|
|
65
68
|
return
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
elements = openAllTopLevelSlots(toArray(elements))
|
|
68
71
|
|
|
69
|
-
const mapSubList = (children: VNodeArrayChildren, depth = 1):
|
|
70
|
-
let idx = 0
|
|
72
|
+
const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
|
|
71
73
|
const vNodes = openAllTopLevelSlots(children).map((i) => {
|
|
72
74
|
if (!isVNode(i))
|
|
73
75
|
return i
|
|
74
76
|
if (listTags.includes(i.type as string) && Array.isArray(i.children)) {
|
|
75
77
|
// eslint-disable-next-line ts/no-use-before-define
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
return h(i, {}, [vNodes])
|
|
78
|
+
const vNodes = mapChildren(i.children, depth + 1)
|
|
79
|
+
return h(i, {}, vNodes)
|
|
79
80
|
}
|
|
80
81
|
return h(i)
|
|
81
82
|
})
|
|
82
|
-
return
|
|
83
|
+
return vNodes
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
let globalIdx =
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
let globalIdx = 1
|
|
87
|
+
let execIdx = 0
|
|
88
|
+
const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
|
|
88
89
|
const vNodes = openAllTopLevelSlots(children).map((i) => {
|
|
89
90
|
if (!isVNode(i) || i.type === Comment)
|
|
90
91
|
return i
|
|
91
|
-
|
|
92
|
+
|
|
93
|
+
const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1
|
|
94
|
+
|
|
92
95
|
let vNode
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
vNode =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
vNode = h(i)
|
|
102
|
-
idx++
|
|
103
|
-
}
|
|
104
|
-
const delta = this.at != null
|
|
105
|
-
? Number(this.at) + Math.floor(globalIdx / this.every) + depth
|
|
106
|
-
: (depth - 1 - childCount).toString()
|
|
107
|
-
globalIdx++
|
|
96
|
+
if (depth < +this.depth && Array.isArray(i.children))
|
|
97
|
+
vNode = h(i, {}, mapSubList(i.children, depth))
|
|
98
|
+
else
|
|
99
|
+
vNode = i
|
|
100
|
+
|
|
101
|
+
const delta = thisShowIdx - execIdx
|
|
102
|
+
execIdx = thisShowIdx
|
|
103
|
+
|
|
108
104
|
return applyDirective(
|
|
109
105
|
vNode,
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
isRelative
|
|
107
|
+
? delta >= 0 ? `+${delta}` : `${delta}`
|
|
108
|
+
: thisShowIdx,
|
|
112
109
|
)
|
|
113
110
|
})
|
|
114
|
-
return
|
|
111
|
+
return vNodes
|
|
115
112
|
}
|
|
116
113
|
|
|
114
|
+
const lastGap = () => createVNode(VClickGap, {
|
|
115
|
+
size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,
|
|
116
|
+
})
|
|
117
|
+
|
|
117
118
|
// handle ul, ol list
|
|
118
|
-
if (
|
|
119
|
-
return h(
|
|
119
|
+
if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
|
|
120
|
+
return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])
|
|
120
121
|
|
|
121
|
-
if (
|
|
122
|
-
const tableNode =
|
|
122
|
+
if (elements.length === 1 && elements[0].type as string === 'table') {
|
|
123
|
+
const tableNode = elements[0]
|
|
123
124
|
if (Array.isArray(tableNode.children)) {
|
|
124
125
|
return h(tableNode, {}, tableNode.children.map((i) => {
|
|
125
|
-
if (!isVNode(i))
|
|
126
|
+
if (!isVNode(i))
|
|
126
127
|
return i
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
else
|
|
133
|
-
return h(i)
|
|
134
|
-
}
|
|
128
|
+
else if (i.type === 'tbody' && Array.isArray(i.children))
|
|
129
|
+
return h(i, {}, [...mapChildren(i.children), lastGap()])
|
|
130
|
+
else
|
|
131
|
+
return h(i)
|
|
135
132
|
}))
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
return mapChildren(
|
|
136
|
+
return [...mapChildren(elements), lastGap()]
|
|
140
137
|
},
|
|
141
138
|
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { sum } from '@antfu/utils'
|
|
2
|
+
import type { ClicksContext } from '@slidev/types'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { ref, shallowReactive } from 'vue'
|
|
5
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
6
|
+
import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
|
|
7
|
+
import { normalizeAtProp } from '../logic/utils'
|
|
8
|
+
|
|
9
|
+
function useClicksContextBase(route: RouteRecordRaw | undefined, getCurrent: () => number): ClicksContext {
|
|
10
|
+
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
11
|
+
const map: ClicksContext['map'] = shallowReactive(new Map())
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
get disabled() {
|
|
15
|
+
return isPrintMode.value && !isPrintWithClicks.value
|
|
16
|
+
},
|
|
17
|
+
get current() {
|
|
18
|
+
return getCurrent()
|
|
19
|
+
},
|
|
20
|
+
relativeOffsets,
|
|
21
|
+
map,
|
|
22
|
+
resolve(at, size = 1) {
|
|
23
|
+
const [isRelative, value] = normalizeAtProp(at)
|
|
24
|
+
if (isRelative) {
|
|
25
|
+
const offset = this.currentOffset
|
|
26
|
+
return {
|
|
27
|
+
start: offset + value,
|
|
28
|
+
end: offset + value + size - 1,
|
|
29
|
+
delta: value + size - 1,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
return {
|
|
34
|
+
start: value,
|
|
35
|
+
end: value + size - 1,
|
|
36
|
+
delta: 0,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
register(el, resolved) {
|
|
41
|
+
relativeOffsets.set(el, resolved.delta)
|
|
42
|
+
map.set(el, resolved)
|
|
43
|
+
},
|
|
44
|
+
unregister(el) {
|
|
45
|
+
relativeOffsets.delete(el)
|
|
46
|
+
map.delete(el)
|
|
47
|
+
},
|
|
48
|
+
get currentOffset() {
|
|
49
|
+
// eslint-disable-next-line no-unused-expressions
|
|
50
|
+
routeForceRefresh.value
|
|
51
|
+
return sum(...relativeOffsets.values())
|
|
52
|
+
},
|
|
53
|
+
get total() {
|
|
54
|
+
// eslint-disable-next-line no-unused-expressions
|
|
55
|
+
routeForceRefresh.value
|
|
56
|
+
return route?.meta?.clicks
|
|
57
|
+
?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksContext {
|
|
63
|
+
if (route?.meta?.__clicksContext)
|
|
64
|
+
return route.meta.__clicksContext
|
|
65
|
+
const thisPath = +(route?.path ?? 99999)
|
|
66
|
+
const context = useClicksContextBase(route, () => {
|
|
67
|
+
const currentPath = +(currentRoute.value?.path ?? 99999)
|
|
68
|
+
if (currentPath === thisPath)
|
|
69
|
+
return queryClicks.value
|
|
70
|
+
else if (currentPath > thisPath)
|
|
71
|
+
return 99999
|
|
72
|
+
else
|
|
73
|
+
return 0
|
|
74
|
+
})
|
|
75
|
+
if (route?.meta)
|
|
76
|
+
route.meta.__clicksContext = context
|
|
77
|
+
return context
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useFixedClicks(route: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
|
|
81
|
+
const current = ref(currentInit)
|
|
82
|
+
return [current, useClicksContextBase(route, () => current.value)]
|
|
83
|
+
}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
import type { ComputedRef
|
|
1
|
+
import type { ComputedRef } from 'vue'
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
4
4
|
import type { SlidevContext } from '../modules/context'
|
|
5
5
|
import { configs } from '../env'
|
|
6
6
|
import { useNav } from './useNav'
|
|
7
|
-
import { useNavClicks } from './useNavClicks'
|
|
8
7
|
|
|
9
8
|
export function useContext(
|
|
10
9
|
route: ComputedRef<RouteLocationNormalizedLoaded>,
|
|
11
|
-
clicks: WritableComputedRef<number>,
|
|
12
10
|
): SlidevContext {
|
|
13
11
|
const nav = useNav(route)
|
|
14
|
-
const navClicks = useNavClicks(clicks, nav.currentRoute, nav.currentPage)
|
|
15
12
|
return {
|
|
16
|
-
nav
|
|
17
|
-
...nav,
|
|
18
|
-
...navClicks,
|
|
19
|
-
},
|
|
13
|
+
nav,
|
|
20
14
|
configs,
|
|
21
15
|
themeConfigs: computed(() => configs.themeConfig),
|
|
22
16
|
}
|
package/composables/useNav.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
|
4
4
|
import type { TocItem } from '@slidev/types'
|
|
5
5
|
import type { SlidevContextNav } from '../modules/context'
|
|
6
|
-
import { addToTree, downloadPDF, filterTree, getPath, getTreeWithActiveStatuses, go, next, nextSlide, openInEditor, prev, prevSlide } from '../logic/nav'
|
|
6
|
+
import { addToTree, clicks, clicksContext, clicksTotal, downloadPDF, filterTree, getPath, getTreeWithActiveStatuses, go, next, nextSlide, openInEditor, prev, prevSlide } from '../logic/nav'
|
|
7
7
|
import { rawRoutes } from '../routes'
|
|
8
8
|
|
|
9
9
|
export function useNav(route: ComputedRef<RouteRecordRaw | RouteLocationNormalizedLoaded>): SlidevContextNav {
|
|
@@ -12,7 +12,7 @@ export function useNav(route: ComputedRef<RouteRecordRaw | RouteLocationNormaliz
|
|
|
12
12
|
|
|
13
13
|
const currentPage = computed(() => Number.parseInt(path.value.split(/\//g).slice(-1)[0]) || 1)
|
|
14
14
|
const currentPath = computed(() => getPath(currentPage.value))
|
|
15
|
-
const currentRoute = computed(() => rawRoutes.find(i => i.path === `${currentPage.value}`))
|
|
15
|
+
const currentRoute = computed(() => rawRoutes.find(i => i.path === `${currentPage.value}`) ?? rawRoutes.at(-1) ?? rawRoutes[0])
|
|
16
16
|
const currentSlideId = computed(() => currentRoute.value?.meta?.slide?.id)
|
|
17
17
|
const currentLayout = computed(() => currentRoute.value?.meta?.layout || (currentPage.value === 1 ? 'cover' : 'default'))
|
|
18
18
|
|
|
@@ -32,6 +32,9 @@ export function useNav(route: ComputedRef<RouteRecordRaw | RouteLocationNormaliz
|
|
|
32
32
|
route,
|
|
33
33
|
path,
|
|
34
34
|
total,
|
|
35
|
+
clicksContext,
|
|
36
|
+
clicks,
|
|
37
|
+
clicksTotal,
|
|
35
38
|
currentPage,
|
|
36
39
|
currentPath,
|
|
37
40
|
currentRoute,
|
package/constants.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey, Ref, UnwrapNestedRefs } from 'vue'
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
-
import type { RenderContext } from '@slidev/types'
|
|
3
|
+
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
4
4
|
import type { SlidevContext } from './modules/context'
|
|
5
5
|
|
|
6
|
-
export const
|
|
7
|
-
export const injectionClicksElements: InjectionKey<Ref<(Element | string)[]>> = Symbol('v-click-clicks-elements')
|
|
8
|
-
export const injectionClicksDisabled: InjectionKey<Ref<boolean>> = Symbol('v-click-clicks-disabled')
|
|
9
|
-
export const injectionOrderMap: InjectionKey<Ref<Map<number, HTMLElement[]>>> = Symbol('v-click-clicks-order-map')
|
|
6
|
+
export const injectionClicksContext: InjectionKey<Ref<ClicksContext>> = Symbol('slidev-clicks-context')
|
|
10
7
|
export const injectionCurrentPage: InjectionKey<Ref<number>> = Symbol('slidev-page')
|
|
11
8
|
export const injectionSlideScale: InjectionKey<ComputedRef<number>> = Symbol('slidev-slide-scale')
|
|
12
9
|
export const injectionSlidevContext: InjectionKey<UnwrapNestedRefs<SlidevContext>> = Symbol('slidev-slidev-context')
|