@slidev/client 0.48.0-beta.1 → 0.48.0-beta.10
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 +4 -3
- package/builtin/KaTexBlockWrapper.vue +4 -3
- package/builtin/RenderWhen.vue +3 -3
- package/builtin/SlideCurrentNo.vue +2 -3
- package/builtin/SlidesTotal.vue +3 -4
- package/builtin/SlidevVideo.vue +8 -6
- package/builtin/Toc.vue +3 -3
- package/builtin/Tweet.vue +4 -15
- package/builtin/VClickGap.vue +3 -5
- package/builtin/VClicks.ts +1 -1
- package/composables/useClicks.ts +16 -13
- package/composables/useTweetScript.ts +17 -0
- package/constants.ts +56 -8
- package/context.ts +70 -0
- package/internals/DrawingControls.vue +41 -9
- package/internals/DrawingLayer.vue +3 -2
- package/internals/Editor.vue +7 -3
- package/internals/IconButton.vue +4 -3
- package/internals/InfoDialog.vue +1 -1
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +1 -1
- package/internals/NoteDisplay.vue +1 -1
- package/internals/NoteEditor.vue +10 -6
- package/internals/NoteStatic.vue +5 -6
- package/internals/PrintContainer.vue +3 -2
- package/internals/PrintSlideClick.vue +3 -2
- package/internals/RecordingDialog.vue +2 -3
- package/internals/SlideContainer.vue +7 -6
- package/internals/SlideWrapper.ts +28 -12
- package/internals/SlidesOverview.vue +18 -7
- package/logic/drawings.ts +6 -3
- package/logic/nav.ts +1 -1
- package/logic/note.ts +7 -7
- package/main.ts +5 -3
- package/modules/context.ts +4 -3
- package/modules/{directives.ts → v-click.ts} +15 -15
- package/modules/v-mark.ts +159 -0
- package/package.json +21 -13
- package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
- package/{internals/NotesView.vue → pages/notes.vue} +3 -3
- package/pages/overview.vue +157 -0
- package/{internals/Play.vue → pages/play.vue} +7 -7
- package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +7 -5
- package/{internals/Presenter.vue → pages/presenter.vue} +16 -12
- package/{internals/Print.vue → pages/print.vue} +2 -2
- package/routes.ts +25 -19
- package/setup/codemirror.ts +7 -0
- package/state/index.ts +10 -10
- package/styles/index.css +5 -0
- package/styles/layouts-base.css +6 -4
- package/styles/vars.css +1 -0
- package/uno.config.ts +6 -2
package/internals/NoteEditor.vue
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
|
|
3
3
|
import { ref, watch, watchEffect } from 'vue'
|
|
4
|
-
import { currentSlideId } from '../logic/nav'
|
|
5
4
|
import { useDynamicSlideInfo } from '../logic/note'
|
|
6
5
|
import NoteDisplay from './NoteDisplay.vue'
|
|
7
6
|
|
|
8
7
|
const props = defineProps({
|
|
8
|
+
no: {
|
|
9
|
+
type: Number,
|
|
10
|
+
},
|
|
9
11
|
class: {
|
|
10
12
|
default: '',
|
|
11
13
|
},
|
|
@@ -25,7 +27,7 @@ const emit = defineEmits([
|
|
|
25
27
|
])
|
|
26
28
|
const editing = useVModel(props, 'editing', emit, { passive: true })
|
|
27
29
|
|
|
28
|
-
const { info, update } = useDynamicSlideInfo(
|
|
30
|
+
const { info, update } = useDynamicSlideInfo(props.no)
|
|
29
31
|
|
|
30
32
|
const note = ref('')
|
|
31
33
|
let timer: any
|
|
@@ -33,10 +35,10 @@ let timer: any
|
|
|
33
35
|
const { ignoreUpdates } = ignorableWatch(
|
|
34
36
|
note,
|
|
35
37
|
(v) => {
|
|
36
|
-
const id =
|
|
38
|
+
const id = props.no
|
|
37
39
|
clearTimeout(timer)
|
|
38
40
|
timer = setTimeout(() => {
|
|
39
|
-
update({
|
|
41
|
+
update({ note: v }, id)
|
|
40
42
|
}, 500)
|
|
41
43
|
},
|
|
42
44
|
)
|
|
@@ -44,6 +46,8 @@ const { ignoreUpdates } = ignorableWatch(
|
|
|
44
46
|
watch(
|
|
45
47
|
info,
|
|
46
48
|
(v) => {
|
|
49
|
+
if (editing.value)
|
|
50
|
+
return
|
|
47
51
|
clearTimeout(timer)
|
|
48
52
|
ignoreUpdates(() => {
|
|
49
53
|
note.value = v?.note || ''
|
|
@@ -68,7 +72,7 @@ onClickOutside(input, () => {
|
|
|
68
72
|
<NoteDisplay
|
|
69
73
|
v-if="!editing"
|
|
70
74
|
class="my--4 border-transparent border-2"
|
|
71
|
-
:class="[props.class, note ? '' : 'opacity-
|
|
75
|
+
:class="[props.class, note ? '' : 'opacity-25 italic select-none']"
|
|
72
76
|
:style="props.style"
|
|
73
77
|
:note="note || placeholder"
|
|
74
78
|
:note-html="info?.noteHTML"
|
|
@@ -77,7 +81,7 @@ onClickOutside(input, () => {
|
|
|
77
81
|
v-else
|
|
78
82
|
ref="input"
|
|
79
83
|
v-model="note"
|
|
80
|
-
class="prose resize-none overflow-auto outline-none bg-transparent block border-
|
|
84
|
+
class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
|
|
81
85
|
style="line-height: 1.75;"
|
|
82
86
|
:style="props.style"
|
|
83
87
|
:class="props.class"
|
package/internals/NoteStatic.vue
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { currentRoute } from '../logic/nav'
|
|
2
|
+
import { useSlideInfo } from '../logic/note'
|
|
4
3
|
import NoteDisplay from './NoteDisplay.vue'
|
|
5
4
|
|
|
6
5
|
const props = defineProps<{
|
|
6
|
+
no?: number
|
|
7
7
|
class?: string
|
|
8
8
|
}>()
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
|
|
10
|
+
const { info } = useSlideInfo(props.no)
|
|
12
11
|
</script>
|
|
13
12
|
|
|
14
13
|
<template>
|
|
15
14
|
<NoteDisplay
|
|
16
15
|
:class="props.class"
|
|
17
|
-
:note="note"
|
|
18
|
-
:note-html="
|
|
16
|
+
:note="info?.note"
|
|
17
|
+
:note-html="info?.noteHTML"
|
|
19
18
|
/>
|
|
20
19
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { parseRangeString } from '@slidev/parser/core'
|
|
3
|
-
import { computed
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { provideLocal } from '@vueuse/core'
|
|
4
5
|
import { configs, slideAspect, slideWidth } from '../env'
|
|
5
6
|
import { injectionSlideScale } from '../constants'
|
|
6
7
|
import { route as currentRoute, rawRoutes } from '../logic/nav'
|
|
@@ -31,7 +32,7 @@ const className = computed(() => ({
|
|
|
31
32
|
'select-none': !configs.selectable,
|
|
32
33
|
}))
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
provideLocal(injectionSlideScale, scale)
|
|
35
36
|
</script>
|
|
36
37
|
|
|
37
38
|
<template>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
-
import { computed,
|
|
3
|
+
import { computed, reactive, shallowRef } from 'vue'
|
|
4
4
|
import type { ClicksContext } from '@slidev/types'
|
|
5
|
+
import { provideLocal } from '@vueuse/core'
|
|
5
6
|
import { injectionSlidevContext } from '../constants'
|
|
6
7
|
import { configs, slideHeight, slideWidth } from '../env'
|
|
7
8
|
import { getSlideClass } from '../utils'
|
|
@@ -33,7 +34,7 @@ const id = computed(() =>
|
|
|
33
34
|
`${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
|
|
34
35
|
)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
provideLocal(injectionSlidevContext, reactive({
|
|
37
38
|
nav: props.nav,
|
|
38
39
|
configs,
|
|
39
40
|
themeConfigs: computed(() => configs.themeConfig),
|
|
@@ -109,7 +109,7 @@ async function start() {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
input[type="text"] {
|
|
112
|
-
@apply border border-
|
|
112
|
+
@apply border border-main rounded px-2 py-1;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
button {
|
|
@@ -118,8 +118,7 @@ async function start() {
|
|
|
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-50 border-opacity-50;
|
|
121
|
+
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
|
|
123
122
|
@apply hover:(bg-opacity-75 border-opacity-75)
|
|
124
123
|
}
|
|
125
124
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useElementSize, useStyleTag } from '@vueuse/core'
|
|
3
|
-
import { computed,
|
|
2
|
+
import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
|
|
3
|
+
import { computed, ref, watchEffect } from 'vue'
|
|
4
4
|
import { configs, slideAspect, slideHeight, slideWidth } from '../env'
|
|
5
5
|
import { injectionSlideScale } from '../constants'
|
|
6
6
|
import { isPrintMode } from '../logic/nav'
|
|
@@ -47,9 +47,10 @@ const scale = computed(() => {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
const style = computed(() => ({
|
|
50
|
-
height: `${slideHeight}px`,
|
|
51
|
-
width: `${slideWidth}px`,
|
|
52
|
-
transform: `translate(-50%, -50%) scale(${scale.value})`,
|
|
50
|
+
'height': `${slideHeight}px`,
|
|
51
|
+
'width': `${slideWidth}px`,
|
|
52
|
+
'transform': `translate(-50%, -50%) scale(${scale.value})`,
|
|
53
|
+
'--slidev-slide-scale': scale.value,
|
|
53
54
|
}))
|
|
54
55
|
|
|
55
56
|
const className = computed(() => ({
|
|
@@ -64,7 +65,7 @@ if (props.isMain) {
|
|
|
64
65
|
`))
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
provideLocal(injectionSlideScale, scale as any)
|
|
68
69
|
</script>
|
|
69
70
|
|
|
70
71
|
<template>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { defineComponent, h,
|
|
1
|
+
import { computed, defineComponent, h, ref, toRef } from 'vue'
|
|
2
2
|
import type { PropType } from 'vue'
|
|
3
|
+
import { provideLocal } from '@vueuse/core'
|
|
3
4
|
import type { ClicksContext, RenderContext } from '@slidev/types'
|
|
5
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
4
6
|
import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
|
|
5
7
|
|
|
6
8
|
export default defineComponent({
|
|
@@ -20,23 +22,37 @@ export default defineComponent({
|
|
|
20
22
|
},
|
|
21
23
|
is: {
|
|
22
24
|
type: Object,
|
|
23
|
-
|
|
25
|
+
required: true,
|
|
24
26
|
},
|
|
25
27
|
route: {
|
|
26
|
-
type: Object
|
|
27
|
-
|
|
28
|
+
type: Object as PropType<RouteRecordRaw>,
|
|
29
|
+
required: true,
|
|
28
30
|
},
|
|
29
31
|
},
|
|
30
32
|
setup(props) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
provideLocal(injectionRoute, props.route)
|
|
34
|
+
provideLocal(injectionCurrentPage, ref(+props.route.path))
|
|
35
|
+
provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
|
|
36
|
+
provideLocal(injectionActive, toRef(props, 'active'))
|
|
37
|
+
provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
|
|
38
|
+
|
|
39
|
+
const style = computed(() => {
|
|
40
|
+
const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
|
|
41
|
+
return zoom === 1
|
|
42
|
+
? undefined
|
|
43
|
+
: {
|
|
44
|
+
width: `${100 / zoom}%`,
|
|
45
|
+
height: `${100 / zoom}%`,
|
|
46
|
+
transformOrigin: 'top left',
|
|
47
|
+
transform: `scale(${zoom})`,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
style,
|
|
53
|
+
}
|
|
36
54
|
},
|
|
37
55
|
render() {
|
|
38
|
-
|
|
39
|
-
return h(this.$props.is)
|
|
40
|
-
return this.$slots?.default?.()
|
|
56
|
+
return h(this.$props.is, { style: this.style })
|
|
41
57
|
},
|
|
42
58
|
})
|
|
@@ -14,7 +14,7 @@ import IconButton from './IconButton.vue'
|
|
|
14
14
|
|
|
15
15
|
const props = defineProps<{ modelValue: boolean }>()
|
|
16
16
|
|
|
17
|
-
const emit = defineEmits([])
|
|
17
|
+
const emit = defineEmits(['update:modelValue'])
|
|
18
18
|
const value = useVModel(props, 'modelValue', emit)
|
|
19
19
|
|
|
20
20
|
function close() {
|
|
@@ -112,7 +112,7 @@ watchEffect(() => {
|
|
|
112
112
|
>
|
|
113
113
|
<div
|
|
114
114
|
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)]"
|
|
115
|
+
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
116
|
@click="close()"
|
|
117
117
|
>
|
|
118
118
|
<div
|
|
@@ -125,8 +125,8 @@ watchEffect(() => {
|
|
|
125
125
|
class="relative"
|
|
126
126
|
>
|
|
127
127
|
<div
|
|
128
|
-
class="inline-block border rounded
|
|
129
|
-
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border
|
|
128
|
+
class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
|
|
129
|
+
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
|
|
130
130
|
:style="themeVars"
|
|
131
131
|
@click="go(+route.path)"
|
|
132
132
|
>
|
|
@@ -163,7 +163,18 @@ 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 items-center gap-4">
|
|
167
|
+
<RouterLink
|
|
168
|
+
v-if="__DEV__"
|
|
169
|
+
target="_blank"
|
|
170
|
+
to="/overview"
|
|
171
|
+
tab-index="-1"
|
|
172
|
+
class="border-main border px3 py1 rounded hover:bg-gray/5 hover:text-primary"
|
|
173
|
+
>
|
|
174
|
+
List overview
|
|
175
|
+
</RouterLink>
|
|
176
|
+
<IconButton title="Close" class="text-2xl" @click="close">
|
|
177
|
+
<carbon:close />
|
|
178
|
+
</IconButton>
|
|
179
|
+
</div>
|
|
169
180
|
</template>
|
package/logic/drawings.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createDrauu } from 'drauu'
|
|
|
4
4
|
import { toReactive, useLocalStorage } from '@vueuse/core'
|
|
5
5
|
import { drawingState, onPatch, patch } from '../state/drawings'
|
|
6
6
|
import { configs } from '../env'
|
|
7
|
+
import { isInputting } from '../state'
|
|
7
8
|
import { currentPage, isPresenter } from './nav'
|
|
8
9
|
|
|
9
10
|
export const brushColors = [
|
|
@@ -40,11 +41,13 @@ export const drawingMode = computed({
|
|
|
40
41
|
set(v: DrawingMode | 'arrow') {
|
|
41
42
|
_mode.value = v
|
|
42
43
|
if (v === 'arrow') {
|
|
43
|
-
|
|
44
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
45
|
+
drauu.mode = 'line'
|
|
44
46
|
brush.arrowEnd = true
|
|
45
47
|
}
|
|
46
48
|
else {
|
|
47
|
-
|
|
49
|
+
// eslint-disable-next-line ts/no-use-before-define
|
|
50
|
+
drauu.mode = v
|
|
48
51
|
brush.arrowEnd = false
|
|
49
52
|
}
|
|
50
53
|
},
|
|
@@ -110,7 +113,7 @@ drauu.on('start', () => isDrawing.value = true)
|
|
|
110
113
|
drauu.on('end', () => isDrawing.value = false)
|
|
111
114
|
|
|
112
115
|
window.addEventListener('keydown', (e) => {
|
|
113
|
-
if (!drawingEnabled.value)
|
|
116
|
+
if (!drawingEnabled.value || isInputting.value)
|
|
114
117
|
return
|
|
115
118
|
|
|
116
119
|
const noModifier = !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
|
package/logic/nav.ts
CHANGED
|
@@ -177,7 +177,7 @@ export async function downloadPDF() {
|
|
|
177
177
|
export async function openInEditor(url?: string) {
|
|
178
178
|
if (url == null) {
|
|
179
179
|
const slide = currentRoute.value?.meta?.slide
|
|
180
|
-
if (!slide
|
|
180
|
+
if (!slide)
|
|
181
181
|
return false
|
|
182
182
|
url = `${slide.filepath}:${slide.start}`
|
|
183
183
|
}
|
package/logic/note.ts
CHANGED
|
@@ -2,17 +2,17 @@ import type { MaybeRef } from '@vueuse/core'
|
|
|
2
2
|
import { useFetch } from '@vueuse/core'
|
|
3
3
|
import type { Ref } from 'vue'
|
|
4
4
|
import { computed, ref, unref } from 'vue'
|
|
5
|
-
import type { SlideInfo,
|
|
5
|
+
import type { SlideInfo, SlidePatch } from '@slidev/types'
|
|
6
6
|
|
|
7
7
|
export interface UseSlideInfo {
|
|
8
|
-
info: Ref<
|
|
9
|
-
update: (data:
|
|
8
|
+
info: Ref<SlideInfo | undefined>
|
|
9
|
+
update: (data: SlidePatch) => Promise<SlideInfo | void>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
13
13
|
if (id == null) {
|
|
14
14
|
return {
|
|
15
|
-
info: ref() as Ref<
|
|
15
|
+
info: ref() as Ref<SlideInfo | undefined>,
|
|
16
16
|
update: async () => {},
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -21,7 +21,7 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
21
21
|
|
|
22
22
|
execute()
|
|
23
23
|
|
|
24
|
-
const update = async (data:
|
|
24
|
+
const update = async (data: SlidePatch) => {
|
|
25
25
|
return await fetch(
|
|
26
26
|
url,
|
|
27
27
|
{
|
|
@@ -41,7 +41,7 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
41
41
|
info.value = payload.data
|
|
42
42
|
})
|
|
43
43
|
import.meta.hot?.on('slidev-update-note', (payload) => {
|
|
44
|
-
if (payload.id === id && info.value.note
|
|
44
|
+
if (payload.id === id && info.value.note?.trim() !== payload.note?.trim())
|
|
45
45
|
info.value = { ...info.value, ...payload }
|
|
46
46
|
})
|
|
47
47
|
}
|
|
@@ -64,7 +64,7 @@ export function useDynamicSlideInfo(id: MaybeRef<number | undefined>) {
|
|
|
64
64
|
|
|
65
65
|
return {
|
|
66
66
|
info: computed(() => get(unref(id)).info.value),
|
|
67
|
-
update: async (data:
|
|
67
|
+
update: async (data: SlidePatch, newId?: number) => {
|
|
68
68
|
const info = get(newId ?? unref(id))
|
|
69
69
|
const newData = await info.update(data)
|
|
70
70
|
if (newData)
|
package/main.ts
CHANGED
|
@@ -3,15 +3,17 @@ import { createHead } from '@unhead/vue'
|
|
|
3
3
|
import App from './App.vue'
|
|
4
4
|
import setupMain from './setup/main'
|
|
5
5
|
import { router } from './routes'
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import { createVClickDirectives } from './modules/v-click'
|
|
7
|
+
import { createVMarkDirective } from './modules/v-mark'
|
|
8
|
+
import { createSlidevContext } from './modules/context'
|
|
8
9
|
|
|
9
10
|
import '/@slidev/styles'
|
|
10
11
|
|
|
11
12
|
const app = createApp(App)
|
|
12
13
|
app.use(router)
|
|
13
14
|
app.use(createHead())
|
|
14
|
-
app.use(
|
|
15
|
+
app.use(createVClickDirectives())
|
|
16
|
+
app.use(createVMarkDirective())
|
|
15
17
|
app.use(createSlidevContext())
|
|
16
18
|
|
|
17
19
|
setupMain({ app, router })
|
package/modules/context.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { App } from 'vue'
|
|
2
|
-
import { computed, reactive } from 'vue'
|
|
2
|
+
import { computed, reactive, ref } from 'vue'
|
|
3
3
|
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
7
|
import { route } from '../logic/nav'
|
|
8
8
|
import { isDark } from '../logic/dark'
|
|
9
|
-
import { injectionCurrentPage, injectionSlidevContext } from '../constants'
|
|
9
|
+
import { injectionCurrentPage, injectionRenderContext, injectionSlidevContext } from '../constants'
|
|
10
10
|
import { useContext } from '../composables/useContext'
|
|
11
11
|
|
|
12
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'
|
|
@@ -21,10 +21,11 @@ export interface SlidevContext {
|
|
|
21
21
|
themeConfigs: ComputedRef<typeof configs['themeConfig']>
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export
|
|
24
|
+
export function createSlidevContext() {
|
|
25
25
|
return {
|
|
26
26
|
install(app: App) {
|
|
27
27
|
const context = reactive(useContext(route))
|
|
28
|
+
app.provide(injectionRenderContext, ref('none'))
|
|
28
29
|
app.provide(injectionSlidevContext, context)
|
|
29
30
|
app.provide(injectionCurrentPage, computed(() => context.nav.currentPage))
|
|
30
31
|
|
|
@@ -11,19 +11,21 @@ import {
|
|
|
11
11
|
injectionClicksContext,
|
|
12
12
|
} from '../constants'
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
export type VClickValue = string | [string | number, string | number] | boolean
|
|
15
|
+
|
|
16
|
+
export function dirInject<T = unknown>(dir: DirectiveBinding<any>, key: InjectionKey<T> | string, defaultValue?: T): T | undefined {
|
|
15
17
|
return (dir.instance?.$ as any).provides[key as any] ?? defaultValue
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export
|
|
20
|
+
export function createVClickDirectives() {
|
|
19
21
|
return {
|
|
20
22
|
install(app: App) {
|
|
21
|
-
app.directive('click', {
|
|
23
|
+
app.directive<HTMLElement, VClickValue>('click', {
|
|
22
24
|
// @ts-expect-error extra prop
|
|
23
25
|
name: 'v-click',
|
|
24
26
|
|
|
25
|
-
mounted(el
|
|
26
|
-
const resolved = resolveClick(el, dir)
|
|
27
|
+
mounted(el, dir) {
|
|
28
|
+
const resolved = resolveClick(el, dir, dir.value)
|
|
27
29
|
if (resolved == null)
|
|
28
30
|
return
|
|
29
31
|
|
|
@@ -55,12 +57,12 @@ export default function createDirectives() {
|
|
|
55
57
|
unmounted,
|
|
56
58
|
})
|
|
57
59
|
|
|
58
|
-
app.directive('after', {
|
|
60
|
+
app.directive<HTMLElement, VClickValue>('after', {
|
|
59
61
|
// @ts-expect-error extra prop
|
|
60
62
|
name: 'v-after',
|
|
61
63
|
|
|
62
|
-
mounted(el
|
|
63
|
-
const resolved = resolveClick(el, dir, true)
|
|
64
|
+
mounted(el, dir) {
|
|
65
|
+
const resolved = resolveClick(el, dir, dir.value, true)
|
|
64
66
|
if (resolved == null)
|
|
65
67
|
return
|
|
66
68
|
|
|
@@ -86,12 +88,12 @@ export default function createDirectives() {
|
|
|
86
88
|
unmounted,
|
|
87
89
|
})
|
|
88
90
|
|
|
89
|
-
app.directive('click-hide', {
|
|
91
|
+
app.directive<HTMLElement, VClickValue>('click-hide', {
|
|
90
92
|
// @ts-expect-error extra prop
|
|
91
93
|
name: 'v-click-hide',
|
|
92
94
|
|
|
93
|
-
mounted(el
|
|
94
|
-
const resolved = resolveClick(el, dir, false, true)
|
|
95
|
+
mounted(el, dir) {
|
|
96
|
+
const resolved = resolveClick(el, dir, dir.value, false, true)
|
|
95
97
|
if (resolved == null)
|
|
96
98
|
return
|
|
97
99
|
|
|
@@ -127,14 +129,12 @@ function isCurrent(thisClick: number | [number, number], clicks: number) {
|
|
|
127
129
|
: thisClick === clicks
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
function resolveClick(el: Element, dir: DirectiveBinding<any>, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
132
|
+
export function resolveClick(el: Element, dir: DirectiveBinding<any>, value: VClickValue, clickAfter = false, flagHide = false): ResolvedClicksInfo | null {
|
|
131
133
|
const ctx = dirInject(dir, injectionClicksContext)?.value
|
|
132
134
|
|
|
133
135
|
if (!el || !ctx || ctx.disabled)
|
|
134
136
|
return null
|
|
135
137
|
|
|
136
|
-
let value = dir.value
|
|
137
|
-
|
|
138
138
|
if (value === false || value === 'false')
|
|
139
139
|
return null
|
|
140
140
|
|
|
@@ -153,7 +153,7 @@ function resolveClick(el: Element, dir: DirectiveBinding<any>, clickAfter = fals
|
|
|
153
153
|
// range (absolute)
|
|
154
154
|
delta = 0
|
|
155
155
|
thisClick = value as [number, number]
|
|
156
|
-
maxClick = value[1]
|
|
156
|
+
maxClick = +value[1]
|
|
157
157
|
}
|
|
158
158
|
else {
|
|
159
159
|
({ start: thisClick, end: maxClick, delta } = ctx.resolve(value))
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { RoughAnnotationConfig } from '@slidev/rough-notation'
|
|
2
|
+
import { annotate } from '@slidev/rough-notation'
|
|
3
|
+
import type { App } from 'vue'
|
|
4
|
+
import { computed, watchEffect } from 'vue'
|
|
5
|
+
import type { VClickValue } from './v-click'
|
|
6
|
+
import { resolveClick } from './v-click'
|
|
7
|
+
|
|
8
|
+
export interface RoughDirectiveOptions extends Partial<RoughAnnotationConfig> {
|
|
9
|
+
at: VClickValue
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type RoughDirectiveValue = VClickValue | RoughDirectiveOptions
|
|
13
|
+
|
|
14
|
+
function addClass(options: RoughDirectiveOptions, cls: string) {
|
|
15
|
+
options.class = [options.class, cls].filter(Boolean).join(' ')
|
|
16
|
+
return options
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Definitions of supported modifiers
|
|
20
|
+
const vMarkModifiers: Record<string, (options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions> = {
|
|
21
|
+
// Types
|
|
22
|
+
'box': options => Object.assign(options, { type: 'box' }),
|
|
23
|
+
'circle': options => Object.assign(options, { type: 'circle' }),
|
|
24
|
+
'underline': options => Object.assign(options, { type: 'underline' }),
|
|
25
|
+
'highlight': options => Object.assign(options, { type: 'highlight' }),
|
|
26
|
+
'strike-through': options => Object.assign(options, { type: 'strike-through' }),
|
|
27
|
+
'crossed-off': options => Object.assign(options, { type: 'crossed-off' }),
|
|
28
|
+
'bracket': options => Object.assign(options, { type: 'bracket' }),
|
|
29
|
+
|
|
30
|
+
// Type Aliases
|
|
31
|
+
'strike': options => Object.assign(options, { type: 'strike-through' }),
|
|
32
|
+
'cross': options => Object.assign(options, { type: 'crossed-off' }),
|
|
33
|
+
'crossed': options => Object.assign(options, { type: 'crossed-off' }),
|
|
34
|
+
'linethrough': options => Object.assign(options, { type: 'strike-through' }),
|
|
35
|
+
'line-through': options => Object.assign(options, { type: 'strike-through' }),
|
|
36
|
+
|
|
37
|
+
// Colors
|
|
38
|
+
// @unocss-include
|
|
39
|
+
'black': options => addClass(options, 'text-black'),
|
|
40
|
+
'blue': options => addClass(options, 'text-blue'),
|
|
41
|
+
'cyan': options => addClass(options, 'text-cyan'),
|
|
42
|
+
'gray': options => addClass(options, 'text-gray'),
|
|
43
|
+
'green': options => addClass(options, 'text-green'),
|
|
44
|
+
'indigo': options => addClass(options, 'text-indigo'),
|
|
45
|
+
'lime': options => addClass(options, 'text-lime'),
|
|
46
|
+
'orange': options => addClass(options, 'text-orange'),
|
|
47
|
+
'pink': options => addClass(options, 'text-pink'),
|
|
48
|
+
'purple': options => addClass(options, 'text-purple'),
|
|
49
|
+
'red': options => addClass(options, 'text-red'),
|
|
50
|
+
'teal': options => addClass(options, 'text-teal'),
|
|
51
|
+
'white': options => addClass(options, 'text-white'),
|
|
52
|
+
'yellow': options => addClass(options, 'text-yellow'),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const vMarkModifiersDynamic: [RegExp, (match: RegExpMatchArray, options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions][] = [
|
|
56
|
+
// Support setting delay like `v-mark.delay300="1"`
|
|
57
|
+
[/^delay-?(\d+)?$/, (match, options, value) => {
|
|
58
|
+
const ms = (match[1] ? Number.parseInt(match[1]) : value) || 300
|
|
59
|
+
options.delay = ms
|
|
60
|
+
return options
|
|
61
|
+
}],
|
|
62
|
+
// Support setting opacity like `v-mark.op50="1"`
|
|
63
|
+
[/^(?:op|opacity)-?(\d+)?$/, (match, options, value) => {
|
|
64
|
+
const opacity = (match[1] ? Number.parseInt(match[1]) : value) || 100
|
|
65
|
+
options.opacity = opacity / 100
|
|
66
|
+
return options
|
|
67
|
+
}],
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* This supports v-mark directive to add notations to elements, powered by `rough-notation`.
|
|
72
|
+
*/
|
|
73
|
+
export function createVMarkDirective() {
|
|
74
|
+
return {
|
|
75
|
+
install(app: App) {
|
|
76
|
+
app.directive<HTMLElement, RoughDirectiveValue>('mark', {
|
|
77
|
+
// @ts-expect-error extra prop
|
|
78
|
+
name: 'v-mark',
|
|
79
|
+
|
|
80
|
+
mounted: (el, binding) => {
|
|
81
|
+
const options = computed(() => {
|
|
82
|
+
const bindingOptions = (typeof binding.value === 'object' && !Array.isArray(binding.value))
|
|
83
|
+
? { ...binding.value }
|
|
84
|
+
: { at: binding.value }
|
|
85
|
+
|
|
86
|
+
let modifierOptions: RoughDirectiveOptions = { at: bindingOptions.at }
|
|
87
|
+
const unknownModifiers = Object.entries(binding.modifiers)
|
|
88
|
+
.filter(([k, v]) => {
|
|
89
|
+
if (vMarkModifiers[k]) {
|
|
90
|
+
modifierOptions = vMarkModifiers[k](modifierOptions, v)
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
for (const [re, fn] of vMarkModifiersDynamic) {
|
|
94
|
+
const match = k.match(re)
|
|
95
|
+
if (match) {
|
|
96
|
+
modifierOptions = fn(match, modifierOptions, v)
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (unknownModifiers.length)
|
|
104
|
+
console.warn('[Slidev] Invalid modifiers for v-mark:', unknownModifiers)
|
|
105
|
+
|
|
106
|
+
const options = {
|
|
107
|
+
...modifierOptions,
|
|
108
|
+
...bindingOptions,
|
|
109
|
+
}
|
|
110
|
+
options.type ||= 'underline'
|
|
111
|
+
|
|
112
|
+
return options
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const annotation = annotate(el, options.value as RoughAnnotationConfig)
|
|
116
|
+
|
|
117
|
+
const resolvedClick = resolveClick(el, binding, options.value.at)
|
|
118
|
+
if (!resolvedClick) {
|
|
119
|
+
console.error('[Slidev] Invalid value for v-mark:', options.value.at)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
watchEffect(() => {
|
|
124
|
+
let shouldShow: boolean | undefined
|
|
125
|
+
|
|
126
|
+
if (options.value.class)
|
|
127
|
+
annotation.class = options.value.class
|
|
128
|
+
if (options.value.color)
|
|
129
|
+
annotation.color = options.value.color
|
|
130
|
+
|
|
131
|
+
const at = options.value.at
|
|
132
|
+
|
|
133
|
+
if (at === true) {
|
|
134
|
+
shouldShow = true
|
|
135
|
+
}
|
|
136
|
+
else if (at === false) {
|
|
137
|
+
shouldShow = false
|
|
138
|
+
}
|
|
139
|
+
else if (resolvedClick) {
|
|
140
|
+
shouldShow = resolvedClick.isActive.value
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
console.error('[Slidev] Invalid value for v-mark:', at)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (shouldShow == null)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if (shouldShow)
|
|
151
|
+
annotation.show()
|
|
152
|
+
else
|
|
153
|
+
annotation.hide()
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|