@slidev/client 0.40.5 → 0.40.6
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 +9 -3
- package/builtin/SlidevVideo.vue +87 -0
- package/builtin/TocList.vue +1 -1
- package/iframes/monaco/index.ts +2 -2
- package/internals/Goto.vue +137 -38
- package/internals/MenuButton.vue +1 -1
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +1 -1
- package/modules/directives.ts +3 -0
- package/package.json +6 -5
- package/routes.ts +3 -0
- package/styles/code.css +1 -0
- package/uno.config.ts +2 -0
- package/windi.config.ts +2 -0
|
@@ -81,9 +81,15 @@ onMounted(() => {
|
|
|
81
81
|
line.classList.toggle('dishonored', !highlighted)
|
|
82
82
|
})
|
|
83
83
|
if (props.maxHeight) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const highlightedEls = Array.from(target.querySelectorAll('.line.highlighted'))
|
|
85
|
+
const height = highlightedEls.reduce((acc, el) => el.offsetHeight + acc, 0)
|
|
86
|
+
if (height > el.value.offsetHeight) {
|
|
87
|
+
highlightedEls[0].scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
88
|
+
}
|
|
89
|
+
else if (highlightedEls.length > 0) {
|
|
90
|
+
const middleEl = highlightedEls[Math.round((highlightedEls.length - 1) / 2)]
|
|
91
|
+
middleEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
92
|
+
}
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { injectionClicks, injectionClicksDisabled, injectionClicksElements, injectionRoute, injectionSlideContext, injectionSlidevContext } from '../constants'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
|
|
8
|
+
autoPause?: 'slide' | 'click'
|
|
9
|
+
autoReset?: 'slide' | 'click'
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const $slidev = inject(injectionSlidevContext)
|
|
13
|
+
const route = inject(injectionRoute)
|
|
14
|
+
const currentContext = inject(injectionSlideContext)
|
|
15
|
+
const clicks = inject(injectionClicks)
|
|
16
|
+
const clicksDisabled = inject(injectionClicksDisabled)
|
|
17
|
+
const clicksElements = inject(injectionClicksElements)
|
|
18
|
+
|
|
19
|
+
const video = ref<HTMLMediaElement>()
|
|
20
|
+
const played = ref(false)
|
|
21
|
+
const ended = ref(false)
|
|
22
|
+
|
|
23
|
+
const matchRoute = computed(() => {
|
|
24
|
+
if (!video.value || currentContext !== 'slide')
|
|
25
|
+
return false
|
|
26
|
+
return route === $slidev?.nav.currentRoute
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const matchClick = computed(() => {
|
|
30
|
+
if (!video.value || currentContext !== 'slide' || clicks?.value === undefined || clicksDisabled?.value)
|
|
31
|
+
return false
|
|
32
|
+
return !clicksElements?.value.includes(video.value) || clicksElements?.value[clicks?.value - 1] === video.value
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const matchRouteAndClick = computed(() => matchRoute.value && matchClick.value)
|
|
36
|
+
|
|
37
|
+
watch(matchRouteAndClick, () => {
|
|
38
|
+
if (!video.value || currentContext !== 'slide')
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if (matchRouteAndClick.value) {
|
|
42
|
+
if (props.autoReset === 'click')
|
|
43
|
+
video.value.currentTime = 0
|
|
44
|
+
if (props.autoPlay && (!played.value || props.autoPlay === 'resume' || (props.autoPlay === 'resumeOnce' && !ended.value)))
|
|
45
|
+
video.value.play()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ((props.autoPause === 'click' && !matchRouteAndClick.value) || (props.autoPause === 'slide' && !matchRoute.value))
|
|
49
|
+
video.value.pause()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
watch(matchRoute, () => {
|
|
53
|
+
if (!video.value || currentContext !== 'slide')
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if (matchRoute.value && props.autoReset === 'slide')
|
|
57
|
+
video.value.currentTime = 0
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
function onPlay() {
|
|
61
|
+
played.value = true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onEnded() {
|
|
65
|
+
ended.value = true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
if (!video.value || currentContext !== 'slide')
|
|
70
|
+
return
|
|
71
|
+
video.value?.addEventListener('play', onPlay)
|
|
72
|
+
video.value?.addEventListener('ended', onEnded)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
onUnmounted(() => {
|
|
76
|
+
if (!video.value || currentContext !== 'slide')
|
|
77
|
+
return
|
|
78
|
+
video.value?.removeEventListener('play', onPlay)
|
|
79
|
+
video.value?.removeEventListener('ended', onEnded)
|
|
80
|
+
})
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<video ref="video">
|
|
85
|
+
<slot />
|
|
86
|
+
</video>
|
|
87
|
+
</template>
|
package/builtin/TocList.vue
CHANGED
|
@@ -31,7 +31,7 @@ const classes = computed(() => {
|
|
|
31
31
|
<template>
|
|
32
32
|
<ol v-if="list && list.length > 0" :class="classes">
|
|
33
33
|
<li
|
|
34
|
-
v-for="item
|
|
34
|
+
v-for="item of list"
|
|
35
35
|
:key="item.path" class="slidev-toc-item"
|
|
36
36
|
:class="[{ 'slidev-toc-item-active': item.active }, { 'slidev-toc-item-parent-active': item.activeParent }]"
|
|
37
37
|
>
|
package/iframes/monaco/index.ts
CHANGED
|
@@ -26,7 +26,7 @@ let update: () => void = () => { }
|
|
|
26
26
|
|
|
27
27
|
document.body.appendChild(styleObject)
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
function lang() {
|
|
30
30
|
switch (props.lang) {
|
|
31
31
|
case 'ts':
|
|
32
32
|
return 'typescript'
|
|
@@ -37,7 +37,7 @@ const lang = () => {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function ext() {
|
|
41
41
|
switch (lang()) {
|
|
42
42
|
case 'typescript':
|
|
43
43
|
return 'ts'
|
package/internals/Goto.vue
CHANGED
|
@@ -1,74 +1,173 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import Fuse from 'fuse.js'
|
|
4
|
+
import { go, rawRoutes } from '../logic/nav'
|
|
5
|
+
import { activeElement, showGotoDialog } from '../state'
|
|
5
6
|
|
|
7
|
+
const container = ref<HTMLDivElement>()
|
|
6
8
|
const input = ref<HTMLInputElement>()
|
|
9
|
+
const list = ref<HTMLUListElement>()
|
|
10
|
+
const items = ref<HTMLLIElement[]>()
|
|
7
11
|
const text = ref('')
|
|
12
|
+
const selectedIndex = ref(0)
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
function notNull<T>(value: T | null | undefined): value is T {
|
|
15
|
+
return value !== null && value !== undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fuse = computed(() => new Fuse(rawRoutes.map(i => i.meta?.slide).filter(notNull), {
|
|
19
|
+
keys: ['no', 'title'],
|
|
20
|
+
threshold: 0.3,
|
|
21
|
+
shouldSort: true,
|
|
22
|
+
minMatchCharLength: 1,
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
const path = computed(() => text.value.startsWith('/') ? text.value.substring(1) : text.value)
|
|
26
|
+
const result = computed(() => fuse.value.search(path.value).map(result => result.item))
|
|
27
|
+
const valid = computed(() => !!result.value.length)
|
|
18
28
|
|
|
19
29
|
function goTo() {
|
|
20
30
|
if (valid.value) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
else
|
|
25
|
-
go(+text.value)
|
|
31
|
+
const item = result.value.at(selectedIndex.value || 0)
|
|
32
|
+
if (item)
|
|
33
|
+
go(item.no)
|
|
26
34
|
}
|
|
27
35
|
close()
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
function close() {
|
|
39
|
+
text.value = ''
|
|
31
40
|
showGotoDialog.value = false
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
function focusDown(event: Event) {
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
selectedIndex.value++
|
|
46
|
+
if (selectedIndex.value >= result.value.length)
|
|
47
|
+
selectedIndex.value = 0
|
|
48
|
+
scroll()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function focusUp(event: Event) {
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
selectedIndex.value--
|
|
54
|
+
if (selectedIndex.value <= -2)
|
|
55
|
+
selectedIndex.value = result.value.length - 1
|
|
56
|
+
scroll()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function scroll() {
|
|
60
|
+
const item = items.value?.[selectedIndex.value]
|
|
61
|
+
if (item && list.value) {
|
|
62
|
+
if (item.offsetTop + item.offsetHeight > list.value.offsetHeight + list.value.scrollTop) {
|
|
63
|
+
list.value.scrollTo({
|
|
64
|
+
behavior: 'smooth',
|
|
65
|
+
top: item.offsetTop + item.offsetHeight - list.value.offsetHeight + 1,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
else if (item.offsetTop < list.value.scrollTop) {
|
|
69
|
+
list.value.scrollTo({
|
|
70
|
+
behavior: 'smooth',
|
|
71
|
+
top: item.offsetTop,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function updateText(event: Event) {
|
|
78
|
+
selectedIndex.value = 0
|
|
79
|
+
text.value = (event.target as HTMLInputElement).value
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function select(no: number) {
|
|
83
|
+
go(no)
|
|
84
|
+
close()
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
watch(showGotoDialog, async (show) => {
|
|
35
88
|
if (show) {
|
|
36
|
-
await nextTick()
|
|
37
89
|
text.value = ''
|
|
38
|
-
|
|
90
|
+
selectedIndex.value = 0
|
|
91
|
+
// delay the focus to avoid the g character coming from the key that triggered showGotoDialog
|
|
92
|
+
setTimeout(() => input.value?.focus(), 0)
|
|
39
93
|
}
|
|
40
94
|
else {
|
|
41
95
|
input.value?.blur()
|
|
42
96
|
}
|
|
43
97
|
})
|
|
44
98
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
text.value = text.value.substring(1)
|
|
99
|
+
watch(activeElement, () => {
|
|
100
|
+
if (!container.value?.contains(activeElement.value as Node))
|
|
101
|
+
close()
|
|
49
102
|
})
|
|
50
103
|
</script>
|
|
51
104
|
|
|
52
105
|
<template>
|
|
53
106
|
<div
|
|
54
107
|
id="slidev-goto-dialog"
|
|
55
|
-
|
|
108
|
+
ref="container"
|
|
109
|
+
class="fixed right-5 transition-all"
|
|
56
110
|
:class="showGotoDialog ? 'top-5' : '-top-20'"
|
|
57
|
-
shadow="~"
|
|
58
|
-
p="x-4 y-2"
|
|
59
|
-
border="~ transparent rounded dark:gray-400 dark:opacity-25"
|
|
60
111
|
>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
<div
|
|
113
|
+
class="bg-main transform"
|
|
114
|
+
shadow="~"
|
|
115
|
+
p="x-4 y-2"
|
|
116
|
+
border="~ transparent rounded dark:main"
|
|
117
|
+
>
|
|
118
|
+
<input
|
|
119
|
+
ref="input"
|
|
120
|
+
:value="text"
|
|
121
|
+
type="text"
|
|
122
|
+
:disabled="!showGotoDialog"
|
|
123
|
+
class="outline-none bg-transparent"
|
|
124
|
+
placeholder="Goto..."
|
|
125
|
+
:class="{ 'text-red-400': !valid && text }"
|
|
126
|
+
@keydown.enter="goTo"
|
|
127
|
+
@keydown.escape="close"
|
|
128
|
+
@keydown.down="focusDown"
|
|
129
|
+
@keydown.up="focusUp"
|
|
130
|
+
@input="updateText"
|
|
131
|
+
>
|
|
132
|
+
</div>
|
|
133
|
+
<ul
|
|
134
|
+
v-if="result.length > 0"
|
|
135
|
+
ref="list"
|
|
136
|
+
class="autocomplete-list"
|
|
137
|
+
shadow="~"
|
|
138
|
+
border="~ transparent rounded dark:main"
|
|
72
139
|
>
|
|
140
|
+
<li
|
|
141
|
+
v-for="(item, index) of result"
|
|
142
|
+
ref="items"
|
|
143
|
+
:key="item.id"
|
|
144
|
+
role="button"
|
|
145
|
+
tabindex="0"
|
|
146
|
+
p="x-4 y-2"
|
|
147
|
+
cursor-pointer
|
|
148
|
+
hover="op100"
|
|
149
|
+
flex="~ gap-2"
|
|
150
|
+
items-center
|
|
151
|
+
:border="index === 0 ? '' : 't main'"
|
|
152
|
+
:class="selectedIndex === index ? 'bg-active op100' : 'op80'"
|
|
153
|
+
@click.stop="select(item.no)"
|
|
154
|
+
>
|
|
155
|
+
<div w-4 text-right op50 text-sm>
|
|
156
|
+
{{ item.no }}
|
|
157
|
+
</div>
|
|
158
|
+
{{ item.title }}
|
|
159
|
+
</li>
|
|
160
|
+
</ul>
|
|
73
161
|
</div>
|
|
74
162
|
</template>
|
|
163
|
+
|
|
164
|
+
<style scoped lang="postcss">
|
|
165
|
+
.autocomplete-list {
|
|
166
|
+
@apply bg-main transform mt-1 overflow-auto;
|
|
167
|
+
max-height: calc( 100vh - 100px );
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.autocomplete {
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
}
|
|
173
|
+
</style>
|
package/internals/MenuButton.vue
CHANGED
package/internals/Modal.vue
CHANGED
|
@@ -25,7 +25,7 @@ const presenterLink = computed(() => `/presenter/${currentPage.value}${query.val
|
|
|
25
25
|
const nonPresenterLink = computed(() => `/${currentPage.value}${query.value}`)
|
|
26
26
|
|
|
27
27
|
const root = ref<HTMLDivElement>()
|
|
28
|
-
|
|
28
|
+
function onMouseLeave() {
|
|
29
29
|
if (root.value && activeElement.value && root.value.contains(activeElement.value))
|
|
30
30
|
activeElement.value.blur()
|
|
31
31
|
}
|
package/modules/directives.ts
CHANGED
|
@@ -128,6 +128,9 @@ export default function createDirectives() {
|
|
|
128
128
|
// Set default dir.value
|
|
129
129
|
if (dir.value == null)
|
|
130
130
|
dir.value = elements?.value.length
|
|
131
|
+
// Relative value starts with '+' o '-'
|
|
132
|
+
if (typeof dir.value === 'string' && (dir.value.startsWith('+') || dir.value.startsWith('-')))
|
|
133
|
+
dir.value = (elements?.value?.length || 0) + Number(dir.value)
|
|
131
134
|
|
|
132
135
|
// If a v-click order before v-after is lower than v-after, the order map will
|
|
133
136
|
// not contain the key for v-after, so we need to set it first, then move v-after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.6",
|
|
4
4
|
"description": "Presentation slides for developers",
|
|
5
5
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
"defu": "^6.1.2",
|
|
26
26
|
"drauu": "^0.3.2",
|
|
27
27
|
"file-saver": "^2.0.5",
|
|
28
|
+
"fuse.js": "^6.6.2",
|
|
28
29
|
"js-base64": "^3.7.5",
|
|
29
30
|
"js-yaml": "^4.1.0",
|
|
30
31
|
"katex": "^0.16.4",
|
|
31
32
|
"mermaid": "^9.4.3",
|
|
32
33
|
"monaco-editor": "^0.33.0",
|
|
33
|
-
"nanoid": "^4.0.
|
|
34
|
-
"prettier": "^2.8.
|
|
34
|
+
"nanoid": "^4.0.2",
|
|
35
|
+
"prettier": "^2.8.7",
|
|
35
36
|
"recordrtc": "^5.6.2",
|
|
36
37
|
"resolve": "^1.22.1",
|
|
37
38
|
"unocss": "^0.50.6",
|
|
@@ -40,8 +41,8 @@
|
|
|
40
41
|
"vue-router": "^4.1.6",
|
|
41
42
|
"vue-starport": "^0.3.0",
|
|
42
43
|
"windicss": "^3.5.6",
|
|
43
|
-
"@slidev/parser": "0.40.
|
|
44
|
-
"@slidev/types": "0.40.
|
|
44
|
+
"@slidev/parser": "0.40.6",
|
|
45
|
+
"@slidev/types": "0.40.6"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"vite": "^4.2.1"
|
package/routes.ts
CHANGED
package/styles/code.css
CHANGED
package/uno.config.ts
CHANGED
|
@@ -15,6 +15,8 @@ export default defineConfig({
|
|
|
15
15
|
],
|
|
16
16
|
shortcuts: {
|
|
17
17
|
'bg-main': 'bg-white text-[#181818] dark:(bg-[#121212] text-[#ddd])',
|
|
18
|
+
'bg-active': 'bg-gray-400/10',
|
|
19
|
+
'border-main': 'border-gray-400/20',
|
|
18
20
|
'abs-tl': 'absolute top-0 left-0',
|
|
19
21
|
'abs-tr': 'absolute top-0 right-0',
|
|
20
22
|
'abs-b': 'absolute bottom-0 left-0 right-0',
|
package/windi.config.ts
CHANGED
|
@@ -48,6 +48,8 @@ export default defineConfig({
|
|
|
48
48
|
],
|
|
49
49
|
shortcuts: {
|
|
50
50
|
'bg-main': 'bg-white text-[#181818] dark:(bg-[#121212] text-[#ddd])',
|
|
51
|
+
'bg-active': 'bg-gray-400/10',
|
|
52
|
+
'border-main': 'border-gray-400/20',
|
|
51
53
|
'abs-tl': 'absolute top-0 left-0',
|
|
52
54
|
'abs-tr': 'absolute top-0 right-0',
|
|
53
55
|
'abs-b': 'absolute bottom-0 left-0 right-0',
|