@slidev/client 0.48.0-beta.10 → 0.48.0-beta.12
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/TocList.vue +3 -2
- package/builtin/VClick.ts +2 -1
- package/composables/useClicks.ts +8 -4
- package/constants.ts +2 -0
- package/env.ts +3 -12
- package/internals/DrawingControls.vue +3 -5
- package/internals/Goto.vue +5 -4
- package/internals/NavControls.vue +1 -2
- package/internals/NoteDisplay.vue +53 -2
- package/internals/NoteEditor.vue +30 -2
- package/internals/NoteStatic.vue +2 -0
- package/internals/OverviewClicksSlider.vue +92 -0
- package/internals/PrintSlideClick.vue +2 -5
- package/internals/RecordingDialog.vue +3 -3
- package/internals/SlideContainer.vue +5 -3
- package/internals/SlidesOverview.vue +2 -1
- package/internals/SlidesShow.vue +7 -8
- package/layouts/two-cols-header.vue +9 -3
- package/logic/nav.ts +7 -1
- package/main.ts +3 -1
- package/package.json +3 -3
- package/pages/overview.vue +70 -22
- package/pages/presenter.vue +50 -46
- package/routes.ts +6 -36
- package/shim-vue.d.ts +35 -0
- package/shim.d.ts +0 -12
- package/styles/code.css +7 -3
- package/styles/index.css +46 -6
- package/styles/katex.css +1 -1
- package/styles/layouts-base.css +11 -8
- package/styles/vars.css +1 -1
- package/uno.config.ts +8 -0
package/builtin/TocList.vue
CHANGED
|
@@ -10,7 +10,7 @@ Usage:
|
|
|
10
10
|
import { computed } from 'vue'
|
|
11
11
|
import { toArray } from '@antfu/utils'
|
|
12
12
|
import type { TocItem } from '@slidev/types'
|
|
13
|
-
import Titles from '
|
|
13
|
+
import Titles from '#slidev/titles.md'
|
|
14
14
|
|
|
15
15
|
const props = withDefaults(defineProps<{
|
|
16
16
|
level: number
|
|
@@ -65,7 +65,8 @@ const styles = computed(() => {
|
|
|
65
65
|
.slidev-layout .slidev-toc-item p {
|
|
66
66
|
margin: 0;
|
|
67
67
|
}
|
|
68
|
-
.slidev-layout .slidev-toc-item div,
|
|
68
|
+
.slidev-layout .slidev-toc-item div,
|
|
69
|
+
.slidev-layout .slidev-toc-item div p {
|
|
69
70
|
display: initial;
|
|
70
71
|
}
|
|
71
72
|
</style>
|
package/builtin/VClick.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { PropType, VNode } from 'vue'
|
|
8
8
|
import { Text, defineComponent, h } from 'vue'
|
|
9
|
+
import { CLICKS_MAX } from '../constants'
|
|
9
10
|
import VClicks from './VClicks'
|
|
10
11
|
|
|
11
12
|
export default defineComponent({
|
|
@@ -31,7 +32,7 @@ export default defineComponent({
|
|
|
31
32
|
return h(
|
|
32
33
|
VClicks,
|
|
33
34
|
{
|
|
34
|
-
every:
|
|
35
|
+
every: CLICKS_MAX,
|
|
35
36
|
at: this.at,
|
|
36
37
|
hide: this.hide,
|
|
37
38
|
fade: this.fade,
|
package/composables/useClicks.ts
CHANGED
|
@@ -5,8 +5,12 @@ import { ref, shallowReactive } from 'vue'
|
|
|
5
5
|
import type { RouteRecordRaw } from 'vue-router'
|
|
6
6
|
import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
|
|
7
7
|
import { normalizeAtProp } from '../logic/utils'
|
|
8
|
+
import { CLICKS_MAX } from '../constants'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function useClicksContextBase(getCurrent: () => number, clicksOverrides?: number): ClicksContext {
|
|
10
14
|
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
|
|
11
15
|
const map: ClicksContext['map'] = shallowReactive(new Map())
|
|
12
16
|
|
|
@@ -62,14 +66,14 @@ function useClicksContextBase(getCurrent: () => number, clicksOverrides?: number
|
|
|
62
66
|
export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksContext {
|
|
63
67
|
if (route?.meta?.__clicksContext)
|
|
64
68
|
return route.meta.__clicksContext
|
|
65
|
-
const thisPath = +(route?.path ??
|
|
69
|
+
const thisPath = +(route?.path ?? CLICKS_MAX)
|
|
66
70
|
const context = useClicksContextBase(
|
|
67
71
|
() => {
|
|
68
|
-
const currentPath = +(currentRoute.value?.path ??
|
|
72
|
+
const currentPath = +(currentRoute.value?.path ?? CLICKS_MAX)
|
|
69
73
|
if (currentPath === thisPath)
|
|
70
74
|
return queryClicks.value
|
|
71
75
|
else if (currentPath > thisPath)
|
|
72
|
-
return
|
|
76
|
+
return CLICKS_MAX
|
|
73
77
|
else
|
|
74
78
|
return 0
|
|
75
79
|
},
|
package/constants.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const CLASS_VCLICK_HIDDEN_EXP = 'slidev-vclick-hidden-explicitly'
|
|
|
22
22
|
export const CLASS_VCLICK_CURRENT = 'slidev-vclick-current'
|
|
23
23
|
export const CLASS_VCLICK_PRIOR = 'slidev-vclick-prior'
|
|
24
24
|
|
|
25
|
+
export const CLICKS_MAX = 999999
|
|
26
|
+
|
|
25
27
|
export const TRUST_ORIGINS = [
|
|
26
28
|
'localhost',
|
|
27
29
|
'127.0.0.1',
|
package/env.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import type { SlidevConfig } from '@slidev/types'
|
|
2
|
-
import type { UnwrapNestedRefs } from 'vue'
|
|
3
1
|
import { computed } from 'vue'
|
|
4
2
|
import { objectMap } from '@antfu/utils'
|
|
3
|
+
import configs from '#slidev/configs'
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
import _configs from '/@slidev/configs'
|
|
8
|
-
import type { SlidevContext } from './modules/context'
|
|
5
|
+
export { configs }
|
|
9
6
|
|
|
10
|
-
export const configs = _configs as SlidevConfig
|
|
11
7
|
export const slideAspect = configs.aspectRatio ?? (16 / 9)
|
|
12
8
|
export const slideWidth = configs.canvasWidth ?? 980
|
|
9
|
+
|
|
13
10
|
// To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
|
|
14
11
|
// Doing this, we will prevent on print, to create an additional empty white page after each page.
|
|
15
12
|
export const slideHeight = Math.ceil(slideWidth / slideAspect)
|
|
@@ -17,9 +14,3 @@ export const slideHeight = Math.ceil(slideWidth / slideAspect)
|
|
|
17
14
|
export const themeVars = computed(() => {
|
|
18
15
|
return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
|
|
19
16
|
})
|
|
20
|
-
|
|
21
|
-
declare module 'vue' {
|
|
22
|
-
interface ComponentCustomProperties {
|
|
23
|
-
$slidev: UnwrapNestedRefs<SlidevContext>
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -131,10 +131,8 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
131
131
|
</Draggable>
|
|
132
132
|
</template>
|
|
133
133
|
|
|
134
|
-
<style
|
|
135
|
-
.v-popper--theme-menu {
|
|
136
|
-
|
|
137
|
-
@apply border-main;
|
|
138
|
-
}
|
|
134
|
+
<style>
|
|
135
|
+
.v-popper--theme-menu .v-popper__arrow-inner {
|
|
136
|
+
--uno: border-main;
|
|
139
137
|
}
|
|
140
138
|
</style>
|
package/internals/Goto.vue
CHANGED
|
@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
|
|
|
3
3
|
import Fuse from 'fuse.js'
|
|
4
4
|
import { go, rawRoutes } from '../logic/nav'
|
|
5
5
|
import { activeElement, showGotoDialog } from '../state'
|
|
6
|
-
import Titles from '
|
|
6
|
+
import Titles from '#slidev/titles.md'
|
|
7
7
|
|
|
8
8
|
const container = ref<HTMLDivElement>()
|
|
9
9
|
const input = ref<HTMLInputElement>()
|
|
@@ -165,10 +165,11 @@ watch(activeElement, () => {
|
|
|
165
165
|
</div>
|
|
166
166
|
</template>
|
|
167
167
|
|
|
168
|
-
<style scoped
|
|
168
|
+
<style scoped>
|
|
169
169
|
.autocomplete-list {
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
--uno: bg-main mt-1;
|
|
171
|
+
overflow: auto;
|
|
172
|
+
max-height: calc(100vh - 100px);
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
.autocomplete {
|
|
@@ -10,8 +10,7 @@ import MenuButton from './MenuButton.vue'
|
|
|
10
10
|
import VerticalDivider from './VerticalDivider.vue'
|
|
11
11
|
import IconButton from './IconButton.vue'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
import CustomNavControls from '/@slidev/custom-nav-controls'
|
|
13
|
+
import CustomNavControls from '#slidev/custom-nav-controls'
|
|
15
14
|
|
|
16
15
|
const props = defineProps({
|
|
17
16
|
persist: {
|
|
@@ -1,19 +1,70 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, defineEmits, defineProps, nextTick, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import { CLICKS_MAX } from '../constants'
|
|
4
|
+
|
|
2
5
|
const props = defineProps<{
|
|
3
6
|
class?: string
|
|
4
7
|
noteHtml?: string
|
|
5
8
|
note?: string
|
|
6
9
|
placeholder?: string
|
|
10
|
+
clicks?: number | string
|
|
7
11
|
}>()
|
|
8
12
|
|
|
9
13
|
defineEmits(['click'])
|
|
14
|
+
|
|
15
|
+
const withClicks = computed(() => props.clicks != null && props.noteHtml?.includes('slidev-note-click-mark'))
|
|
16
|
+
const noteDisplay = ref<HTMLElement | null>(null)
|
|
17
|
+
|
|
18
|
+
function highlightNote() {
|
|
19
|
+
if (!noteDisplay.value || !withClicks.value || props.clicks == null)
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
const children = Array.from(noteDisplay.value.querySelectorAll('*'))
|
|
23
|
+
|
|
24
|
+
const disabled = +props.clicks < 0 || +props.clicks >= CLICKS_MAX
|
|
25
|
+
if (disabled) {
|
|
26
|
+
children.forEach(el => el.classList.remove('slidev-note-fade'))
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let count = 0
|
|
31
|
+
|
|
32
|
+
const groups = new Map<number, Element[]>()
|
|
33
|
+
|
|
34
|
+
for (const child of children) {
|
|
35
|
+
if (!groups.has(count))
|
|
36
|
+
groups.set(count, [])
|
|
37
|
+
|
|
38
|
+
groups.get(count)!.push(child)
|
|
39
|
+
if (child.classList.contains('slidev-note-click-mark'))
|
|
40
|
+
count = Number((child as HTMLElement).dataset.clicks) || (count + 1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const [count, els] of groups)
|
|
44
|
+
els.forEach(el => el.classList.toggle('slidev-note-fade', +count !== +props.clicks!))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
watch(
|
|
48
|
+
() => [props.noteHtml, props.clicks],
|
|
49
|
+
() => {
|
|
50
|
+
nextTick(() => {
|
|
51
|
+
highlightNote()
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
{ immediate: true },
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
highlightNote()
|
|
59
|
+
})
|
|
10
60
|
</script>
|
|
11
61
|
|
|
12
62
|
<template>
|
|
13
63
|
<div
|
|
14
64
|
v-if="noteHtml"
|
|
15
|
-
|
|
16
|
-
|
|
65
|
+
ref="noteDisplay"
|
|
66
|
+
class="prose overflow-auto outline-none slidev-note"
|
|
67
|
+
:class="[props.class, withClicks ? 'slidev-note-with-clicks' : '']"
|
|
17
68
|
@click="$emit('click')"
|
|
18
69
|
v-html="noteHtml"
|
|
19
70
|
/>
|
package/internals/NoteEditor.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { nextTick, ref, watch, watchEffect } from 'vue'
|
|
2
3
|
import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
|
|
3
|
-
import { ref, watch, watchEffect } from 'vue'
|
|
4
4
|
import { useDynamicSlideInfo } from '../logic/note'
|
|
5
5
|
import NoteDisplay from './NoteDisplay.vue'
|
|
6
6
|
|
|
@@ -20,6 +20,12 @@ const props = defineProps({
|
|
|
20
20
|
placeholder: {
|
|
21
21
|
default: 'No notes for this slide',
|
|
22
22
|
},
|
|
23
|
+
clicks: {
|
|
24
|
+
type: [Number, String],
|
|
25
|
+
},
|
|
26
|
+
autoHeight: {
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
23
29
|
})
|
|
24
30
|
|
|
25
31
|
const emit = defineEmits([
|
|
@@ -66,6 +72,27 @@ watchEffect(() => {
|
|
|
66
72
|
onClickOutside(input, () => {
|
|
67
73
|
editing.value = false
|
|
68
74
|
})
|
|
75
|
+
|
|
76
|
+
function calculateHeight() {
|
|
77
|
+
if (!props.autoHeight || !input.value || !editing.value)
|
|
78
|
+
return
|
|
79
|
+
if (input.value.scrollHeight > input.value.clientHeight)
|
|
80
|
+
input.value.style.height = `${input.value.scrollHeight}px`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const inputHeight = ref<number | null>()
|
|
84
|
+
watchEffect(() => {
|
|
85
|
+
calculateHeight()
|
|
86
|
+
})
|
|
87
|
+
watch(
|
|
88
|
+
note,
|
|
89
|
+
() => {
|
|
90
|
+
nextTick(() => {
|
|
91
|
+
calculateHeight()
|
|
92
|
+
})
|
|
93
|
+
},
|
|
94
|
+
{ flush: 'post' },
|
|
95
|
+
)
|
|
69
96
|
</script>
|
|
70
97
|
|
|
71
98
|
<template>
|
|
@@ -76,6 +103,7 @@ onClickOutside(input, () => {
|
|
|
76
103
|
:style="props.style"
|
|
77
104
|
:note="note || placeholder"
|
|
78
105
|
:note-html="info?.noteHTML"
|
|
106
|
+
:clicks="props.clicks"
|
|
79
107
|
/>
|
|
80
108
|
<textarea
|
|
81
109
|
v-else
|
|
@@ -83,7 +111,7 @@ onClickOutside(input, () => {
|
|
|
83
111
|
v-model="note"
|
|
84
112
|
class="prose resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
|
|
85
113
|
style="line-height: 1.75;"
|
|
86
|
-
:style="props.style"
|
|
114
|
+
:style="[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]"
|
|
87
115
|
:class="props.class"
|
|
88
116
|
:placeholder="placeholder"
|
|
89
117
|
@keydown.esc=" editing = false"
|
package/internals/NoteStatic.vue
CHANGED
|
@@ -5,6 +5,7 @@ import NoteDisplay from './NoteDisplay.vue'
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
no?: number
|
|
7
7
|
class?: string
|
|
8
|
+
clicks?: number | string
|
|
8
9
|
}>()
|
|
9
10
|
|
|
10
11
|
const { info } = useSlideInfo(props.no)
|
|
@@ -15,5 +16,6 @@ const { info } = useSlideInfo(props.no)
|
|
|
15
16
|
:class="props.class"
|
|
16
17
|
:note="info?.note"
|
|
17
18
|
:note-html="info?.noteHTML"
|
|
19
|
+
:clicks="props.clicks"
|
|
18
20
|
/>
|
|
19
21
|
</template>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ClicksContext } from '@slidev/types'
|
|
3
|
+
import type { Ref } from 'vue'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { CLICKS_MAX } from '../constants'
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
clickContext: [Ref<number>, ClicksContext]
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const total = computed(() => props.clickContext[1].total)
|
|
12
|
+
const current = computed({
|
|
13
|
+
get() {
|
|
14
|
+
return props.clickContext[0].value > total.value ? -1 : props.clickContext[0].value
|
|
15
|
+
},
|
|
16
|
+
set(value: number) {
|
|
17
|
+
// eslint-disable-next-line vue/no-mutating-props
|
|
18
|
+
props.clickContext[0].value = value
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const range = computed(() => Array.from({ length: total.value + 1 }, (_, i) => i))
|
|
23
|
+
|
|
24
|
+
function onMousedown() {
|
|
25
|
+
if (current.value < 0 || current.value > total.value)
|
|
26
|
+
current.value = 0
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<div
|
|
32
|
+
class="flex gap-0.5 items-center select-none"
|
|
33
|
+
:title="`Clicks in this slide: ${total}`"
|
|
34
|
+
>
|
|
35
|
+
<div class="flex gap-1 items-center min-w-16">
|
|
36
|
+
<carbon:cursor-1 text-sm op50 />
|
|
37
|
+
<span v-if="current <= total && current >= 0" text-primary>{{ current }}/</span>
|
|
38
|
+
<span op50>{{ total }}</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div
|
|
41
|
+
relative flex-auto h5 flex="~"
|
|
42
|
+
@dblclick="current = CLICKS_MAX"
|
|
43
|
+
>
|
|
44
|
+
<div
|
|
45
|
+
v-for="i of range" :key="i"
|
|
46
|
+
border="y main" of-hidden relative
|
|
47
|
+
:class="[
|
|
48
|
+
i === 0 ? 'rounded-l border-l' : '',
|
|
49
|
+
i === total ? 'rounded-r border-r' : '',
|
|
50
|
+
]"
|
|
51
|
+
:style="{ width: `${1 / total * 100}%` }"
|
|
52
|
+
>
|
|
53
|
+
<div absolute inset-0 z--1 :class=" i <= current ? 'bg-primary op20' : ''" />
|
|
54
|
+
<div
|
|
55
|
+
:class="[
|
|
56
|
+
+i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
|
|
57
|
+
i === 0 ? 'rounded-l' : '',
|
|
58
|
+
i === total ? 'rounded-r' : 'border-r-2',
|
|
59
|
+
]"
|
|
60
|
+
w-full h-full text-xs flex items-center justify-center
|
|
61
|
+
>
|
|
62
|
+
{{ i }}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<input
|
|
66
|
+
v-model="current"
|
|
67
|
+
class="range" absolute inset-0
|
|
68
|
+
type="range" :min="0" :max="total" :step="1" z-10 op0
|
|
69
|
+
:style="{ '--thumb-width': `${1 / (total + 1) * 100}%` }"
|
|
70
|
+
@mousedown="onMousedown"
|
|
71
|
+
>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<style scoped>
|
|
77
|
+
.range {
|
|
78
|
+
-webkit-appearance: none;
|
|
79
|
+
appearance: none;
|
|
80
|
+
background: transparent;
|
|
81
|
+
}
|
|
82
|
+
.range::-webkit-slider-thumb {
|
|
83
|
+
-webkit-appearance: none;
|
|
84
|
+
height: 100%;
|
|
85
|
+
width: var(--thumb-width, 0.5rem);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.range::-moz-range-thumb {
|
|
89
|
+
height: 100%;
|
|
90
|
+
width: var(--thumb-width, 0.5rem);
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -9,11 +9,8 @@ import { getSlideClass } from '../utils'
|
|
|
9
9
|
import type { SlidevContextNav } from '../modules/context'
|
|
10
10
|
import SlideWrapper from './SlideWrapper'
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
// @ts-expect-error virtual module
|
|
16
|
-
import GlobalBottom from '/@slidev/global-components/bottom'
|
|
12
|
+
import GlobalTop from '#slidev/global-components/top'
|
|
13
|
+
import GlobalBottom from '#slidev/global-components/bottom'
|
|
17
14
|
|
|
18
15
|
const props = defineProps<{
|
|
19
16
|
clicksContext: ClicksContext
|
|
@@ -108,18 +108,18 @@ async function start() {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
input[type=
|
|
111
|
+
input[type='text'] {
|
|
112
112
|
@apply border border-main rounded px-2 py-1;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
button {
|
|
116
116
|
@apply bg-orange-400 text-white px-4 py-1 rounded border-b-2 border-orange-600;
|
|
117
|
-
@apply hover:(bg-orange-500 border-orange-700)
|
|
117
|
+
@apply hover:(bg-orange-500 border-orange-700);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
button.cancel {
|
|
121
121
|
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
|
|
122
|
-
@apply hover:(bg-opacity-75 border-opacity-75)
|
|
122
|
+
@apply hover:(bg-opacity-75 border-opacity-75);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
</style>
|
|
@@ -3,7 +3,7 @@ import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
|
|
|
3
3
|
import { computed, ref, watchEffect } from 'vue'
|
|
4
4
|
import { configs, slideAspect, slideHeight, slideWidth } from '../env'
|
|
5
5
|
import { injectionSlideScale } from '../constants'
|
|
6
|
-
import { isPrintMode } from '../logic/nav'
|
|
6
|
+
import { clicksDirection, isPrintMode } from '../logic/nav'
|
|
7
7
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
width: {
|
|
@@ -55,6 +55,8 @@ const style = computed(() => ({
|
|
|
55
55
|
|
|
56
56
|
const className = computed(() => ({
|
|
57
57
|
'select-none': !configs.selectable,
|
|
58
|
+
'slidev-nav-go-forward': clicksDirection.value > 0,
|
|
59
|
+
'slidev-nav-go-backward': clicksDirection.value < 0,
|
|
58
60
|
}))
|
|
59
61
|
|
|
60
62
|
if (props.isMain) {
|
|
@@ -69,8 +71,8 @@ provideLocal(injectionSlideScale, scale as any)
|
|
|
69
71
|
</script>
|
|
70
72
|
|
|
71
73
|
<template>
|
|
72
|
-
<div id="slide-container" ref="root" :class="className">
|
|
73
|
-
<div id="slide-content" :style="style">
|
|
74
|
+
<div id="slide-container" ref="root" class="slidev-slides-container" :class="className">
|
|
75
|
+
<div id="slide-content" class="slidev-slide-content" :style="style">
|
|
74
76
|
<slot />
|
|
75
77
|
</div>
|
|
76
78
|
<slot name="controls" />
|
|
@@ -7,6 +7,7 @@ import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
|
|
|
7
7
|
import { currentOverviewPage, overviewRowCount } from '../logic/overview'
|
|
8
8
|
import { useFixedClicks } from '../composables/useClicks'
|
|
9
9
|
import { getSlideClass } from '../utils'
|
|
10
|
+
import { CLICKS_MAX } from '../constants'
|
|
10
11
|
import SlideContainer from './SlideContainer.vue'
|
|
11
12
|
import SlideWrapper from './SlideWrapper'
|
|
12
13
|
import DrawingPreview from './DrawingPreview.vue'
|
|
@@ -139,7 +140,7 @@ watchEffect(() => {
|
|
|
139
140
|
<SlideWrapper
|
|
140
141
|
:is="route.component"
|
|
141
142
|
v-if="route?.component"
|
|
142
|
-
:clicks-context="useFixedClicks(route,
|
|
143
|
+
:clicks-context="useFixedClicks(route, CLICKS_MAX)[1]"
|
|
143
144
|
:class="getSlideClass(route)"
|
|
144
145
|
:route="route"
|
|
145
146
|
render-context="overview"
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -6,14 +6,11 @@ import { useViewTransition } from '../composables/useViewTransition'
|
|
|
6
6
|
import { skipTransition } from '../composables/hmr'
|
|
7
7
|
import { usePrimaryClicks } from '../composables/useClicks'
|
|
8
8
|
import SlideWrapper from './SlideWrapper'
|
|
9
|
-
|
|
10
|
-
// @ts-expect-error virtual module
|
|
11
|
-
import GlobalTop from '/@slidev/global-components/top'
|
|
12
|
-
|
|
13
|
-
// @ts-expect-error virtual module
|
|
14
|
-
import GlobalBottom from '/@slidev/global-components/bottom'
|
|
15
9
|
import PresenterMouse from './PresenterMouse.vue'
|
|
16
10
|
|
|
11
|
+
import GlobalTop from '#slidev/global-components/top'
|
|
12
|
+
import GlobalBottom from '#slidev/global-components/bottom'
|
|
13
|
+
|
|
17
14
|
defineProps<{
|
|
18
15
|
renderContext: 'slide' | 'presenter'
|
|
19
16
|
}>()
|
|
@@ -77,10 +74,12 @@ function onAfterLeave() {
|
|
|
77
74
|
|
|
78
75
|
<style scoped>
|
|
79
76
|
#slideshow {
|
|
80
|
-
|
|
77
|
+
height: 100%;
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
#slideshow > div {
|
|
84
|
-
|
|
81
|
+
position: absolute;
|
|
82
|
+
height: 100%;
|
|
83
|
+
width: 100%;
|
|
85
84
|
}
|
|
86
85
|
</style>
|
|
@@ -51,9 +51,15 @@ const props = defineProps({
|
|
|
51
51
|
grid-template-rows: repeat(2, 1fr);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.col-header {
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
.col-header {
|
|
55
|
+
grid-area: 1 / 1 / 2 / 3;
|
|
56
|
+
}
|
|
57
|
+
.col-left {
|
|
58
|
+
grid-area: 2 / 1 / 3 / 2;
|
|
59
|
+
}
|
|
60
|
+
.col-right {
|
|
61
|
+
grid-area: 2 / 2 / 3 / 3;
|
|
62
|
+
}
|
|
57
63
|
.col-bottom {
|
|
58
64
|
align-self: end;
|
|
59
65
|
grid-area: 3 / 1 / 3 / 3;
|
package/logic/nav.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { rawRoutes, router } from '../routes'
|
|
|
7
7
|
import { configs } from '../env'
|
|
8
8
|
import { skipTransition } from '../composables/hmr'
|
|
9
9
|
import { usePrimaryClicks } from '../composables/useClicks'
|
|
10
|
+
import { CLICKS_MAX } from '../constants'
|
|
10
11
|
import { useRouteQuery } from './route'
|
|
11
12
|
import { isDrawing } from './drawings'
|
|
12
13
|
|
|
@@ -22,6 +23,7 @@ nextTick(() => {
|
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
export const navDirection = ref(0)
|
|
26
|
+
export const clicksDirection = ref(0)
|
|
25
27
|
|
|
26
28
|
export const route = computed(() => router.currentRoute.value)
|
|
27
29
|
|
|
@@ -38,7 +40,7 @@ export const queryClicks = computed({
|
|
|
38
40
|
get() {
|
|
39
41
|
// eslint-disable-next-line ts/no-use-before-define
|
|
40
42
|
if (clicksContext.value.disabled)
|
|
41
|
-
return
|
|
43
|
+
return CLICKS_MAX
|
|
42
44
|
let v = +(queryClicksRaw.value || 0)
|
|
43
45
|
if (Number.isNaN(v))
|
|
44
46
|
v = 0
|
|
@@ -84,6 +86,7 @@ watch(currentRoute, (next, prev) => {
|
|
|
84
86
|
})
|
|
85
87
|
|
|
86
88
|
export async function next() {
|
|
89
|
+
clicksDirection.value = 1
|
|
87
90
|
if (clicksTotal.value <= queryClicks.value)
|
|
88
91
|
await nextSlide()
|
|
89
92
|
else
|
|
@@ -91,6 +94,7 @@ export async function next() {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
export async function prev() {
|
|
97
|
+
clicksDirection.value = -1
|
|
94
98
|
if (queryClicks.value <= 0)
|
|
95
99
|
await prevSlide()
|
|
96
100
|
else
|
|
@@ -102,11 +106,13 @@ export function getPath(no: number | string) {
|
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
export async function nextSlide() {
|
|
109
|
+
clicksDirection.value = 1
|
|
105
110
|
if (currentPage.value < rawRoutes.length)
|
|
106
111
|
await go(currentPage.value + 1)
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
export async function prevSlide(lastClicks = true) {
|
|
115
|
+
clicksDirection.value = -1
|
|
110
116
|
const next = Math.max(1, currentPage.value - 1)
|
|
111
117
|
await go(next)
|
|
112
118
|
if (lastClicks && clicksTotal.value)
|
package/main.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="@slidev/types/client" />
|
|
2
|
+
|
|
1
3
|
import { createApp } from 'vue'
|
|
2
4
|
import { createHead } from '@unhead/vue'
|
|
3
5
|
import App from './App.vue'
|
|
@@ -7,7 +9,7 @@ import { createVClickDirectives } from './modules/v-click'
|
|
|
7
9
|
import { createVMarkDirective } from './modules/v-mark'
|
|
8
10
|
import { createSlidevContext } from './modules/context'
|
|
9
11
|
|
|
10
|
-
import '
|
|
12
|
+
import '#slidev/styles'
|
|
11
13
|
|
|
12
14
|
const app = createApp(App)
|
|
13
15
|
app.use(router)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.48.0-beta.
|
|
4
|
+
"version": "0.48.0-beta.12",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"unocss": "^0.58.5",
|
|
55
55
|
"vue": "^3.4.19",
|
|
56
56
|
"vue-router": "^4.3.0",
|
|
57
|
-
"@slidev/
|
|
58
|
-
"@slidev/
|
|
57
|
+
"@slidev/parser": "0.48.0-beta.12",
|
|
58
|
+
"@slidev/types": "0.48.0-beta.12"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"vite": "^5.1.4"
|
package/pages/overview.vue
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
|
3
4
|
import { useHead } from '@unhead/vue'
|
|
5
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
6
|
+
import type { ClicksContext } from 'packages/types'
|
|
4
7
|
import { themeVars } from '../env'
|
|
5
8
|
import { rawRoutes } from '../logic/nav'
|
|
6
9
|
import { useFixedClicks } from '../composables/useClicks'
|
|
@@ -11,6 +14,8 @@ import SlideWrapper from '../internals/SlideWrapper'
|
|
|
11
14
|
import DrawingPreview from '../internals/DrawingPreview.vue'
|
|
12
15
|
import IconButton from '../internals/IconButton.vue'
|
|
13
16
|
import NoteEditor from '../internals/NoteEditor.vue'
|
|
17
|
+
import OverviewClicksSlider from '../internals/OverviewClicksSlider.vue'
|
|
18
|
+
import { CLICKS_MAX } from '../constants'
|
|
14
19
|
|
|
15
20
|
const cardWidth = 450
|
|
16
21
|
|
|
@@ -21,6 +26,25 @@ useHead({
|
|
|
21
26
|
const blocks: Map<number, HTMLElement> = reactive(new Map())
|
|
22
27
|
const activeBlocks = ref<number[]>([])
|
|
23
28
|
const edittingNote = ref<number | null>(null)
|
|
29
|
+
const wordCounts = computed(() => rawRoutes.map(route => wordCount(route.meta?.slide?.note || '')))
|
|
30
|
+
const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
|
|
31
|
+
const totalClicks = computed(() => rawRoutes.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
|
|
32
|
+
|
|
33
|
+
const clicksContextMap = new WeakMap<RouteRecordRaw, [Ref<number>, ClicksContext]>()
|
|
34
|
+
function getClickContext(route: RouteRecordRaw) {
|
|
35
|
+
// We create a local clicks context to calculate the total clicks of the slide
|
|
36
|
+
if (!clicksContextMap.has(route))
|
|
37
|
+
clicksContextMap.set(route, useFixedClicks(route, CLICKS_MAX))
|
|
38
|
+
return clicksContextMap.get(route)!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSlideClicks(route: RouteRecordRaw) {
|
|
42
|
+
return route.meta?.clicks || getClickContext(route)?.[1]?.total
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function wordCount(str: string) {
|
|
46
|
+
return str.match(/[\w\d\’\'-]+/gi)?.length || 0
|
|
47
|
+
}
|
|
24
48
|
|
|
25
49
|
function isElementInViewport(el: HTMLElement) {
|
|
26
50
|
const rect = el.getBoundingClientRect()
|
|
@@ -109,31 +133,39 @@ onMounted(() => {
|
|
|
109
133
|
class="relative border-t border-main of-hidden flex gap-4 min-h-50 group"
|
|
110
134
|
>
|
|
111
135
|
<div class="select-none w-13 text-right my4">
|
|
112
|
-
<div class="text-3xl op20">
|
|
136
|
+
<div class="text-3xl op20 mb2">
|
|
113
137
|
{{ idx + 1 }}
|
|
114
138
|
</div>
|
|
115
139
|
</div>
|
|
116
|
-
<div
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<SlideContainer
|
|
122
|
-
:key="route.path"
|
|
123
|
-
:width="cardWidth"
|
|
124
|
-
:clicks-disabled="true"
|
|
125
|
-
class="pointer-events-none important:[&_*]:select-none"
|
|
140
|
+
<div class="flex flex-col gap-2 my5">
|
|
141
|
+
<div
|
|
142
|
+
class="border rounded border-main overflow-hidden bg-main select-none h-max"
|
|
143
|
+
:style="themeVars"
|
|
144
|
+
@dblclick="openSlideInNewTab(route.path)"
|
|
126
145
|
>
|
|
127
|
-
<
|
|
128
|
-
:
|
|
129
|
-
|
|
130
|
-
:clicks-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
<SlideContainer
|
|
147
|
+
:key="route.path"
|
|
148
|
+
:width="cardWidth"
|
|
149
|
+
:clicks-disabled="true"
|
|
150
|
+
class="pointer-events-none important:[&_*]:select-none"
|
|
151
|
+
>
|
|
152
|
+
<SlideWrapper
|
|
153
|
+
:is="route.component"
|
|
154
|
+
v-if="route?.component"
|
|
155
|
+
:clicks-context="getClickContext(route)[1]"
|
|
156
|
+
:class="getSlideClass(route)"
|
|
157
|
+
:route="route"
|
|
158
|
+
render-context="overview"
|
|
159
|
+
/>
|
|
160
|
+
<DrawingPreview :page="+route.path" />
|
|
161
|
+
</SlideContainer>
|
|
162
|
+
</div>
|
|
163
|
+
<OverviewClicksSlider
|
|
164
|
+
v-if="getSlideClicks(route)"
|
|
165
|
+
mt-2
|
|
166
|
+
:click-context="getClickContext(route)"
|
|
167
|
+
class="w-full"
|
|
168
|
+
/>
|
|
137
169
|
</div>
|
|
138
170
|
<div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
|
|
139
171
|
<IconButton
|
|
@@ -148,10 +180,26 @@ onMounted(() => {
|
|
|
148
180
|
<NoteEditor
|
|
149
181
|
:no="idx"
|
|
150
182
|
class="max-w-250 w-250 text-lg rounded p3"
|
|
183
|
+
:auto-height="true"
|
|
151
184
|
:editing="edittingNote === idx"
|
|
185
|
+
:clicks="getClickContext(route)[0].value"
|
|
186
|
+
@dblclick="edittingNote !== idx ? edittingNote = idx : null"
|
|
152
187
|
@update:editing="edittingNote = null"
|
|
153
188
|
/>
|
|
189
|
+
<div
|
|
190
|
+
v-if="wordCounts[idx] > 0"
|
|
191
|
+
class="select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
|
|
192
|
+
>
|
|
193
|
+
{{ wordCounts[idx] }} words
|
|
194
|
+
</div>
|
|
154
195
|
</div>
|
|
155
196
|
</main>
|
|
197
|
+
<div class="absolute top-0 right-0 px3 py1.5 border-b border-l rounded-lb bg-main border-main select-none">
|
|
198
|
+
<div class="text-xs op50">
|
|
199
|
+
{{ rawRoutes.length }} slides ·
|
|
200
|
+
{{ totalClicks + rawRoutes.length - 1 }} clicks ·
|
|
201
|
+
{{ totalWords }} words
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
156
204
|
</div>
|
|
157
205
|
</template>
|
package/pages/presenter.vue
CHANGED
|
@@ -144,6 +144,7 @@ onMounted(() => {
|
|
|
144
144
|
:no="currentSlideId"
|
|
145
145
|
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
146
146
|
:editing="notesEditing"
|
|
147
|
+
:clicks="clicksContext.current"
|
|
147
148
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
148
149
|
/>
|
|
149
150
|
<NoteStatic
|
|
@@ -152,6 +153,7 @@ onMounted(() => {
|
|
|
152
153
|
:no="currentSlideId"
|
|
153
154
|
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
|
|
154
155
|
:style="{ fontSize: `${presenterNotesFontSize}em` }"
|
|
156
|
+
:clicks="clicksContext.current"
|
|
155
157
|
/>
|
|
156
158
|
<div class="border-t border-main py-1 px-2 text-sm">
|
|
157
159
|
<IconButton title="Increase font size" @click="increasePresenterFontSize">
|
|
@@ -185,26 +187,26 @@ onMounted(() => {
|
|
|
185
187
|
<SlidesOverview v-model="showOverview" />
|
|
186
188
|
</template>
|
|
187
189
|
|
|
188
|
-
<style
|
|
190
|
+
<style scoped>
|
|
189
191
|
.slidev-presenter {
|
|
190
192
|
--slidev-controls-foreground: current;
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
.timer-btn:hover {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
@apply opacity-100;
|
|
199
|
-
}
|
|
195
|
+
.timer-btn:hover > :first-child {
|
|
196
|
+
opacity: 0;
|
|
197
|
+
}
|
|
198
|
+
.timer-btn:hover > :last-child {
|
|
199
|
+
opacity: 1;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
.section-title {
|
|
203
|
-
|
|
203
|
+
--uno: px-4 py-2 text-xl;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
.grid-container {
|
|
207
|
-
|
|
207
|
+
--uno: bg-active;
|
|
208
|
+
height: 100%;
|
|
209
|
+
width: 100%;
|
|
208
210
|
display: grid;
|
|
209
211
|
gap: 1px 1px;
|
|
210
212
|
}
|
|
@@ -213,20 +215,20 @@ onMounted(() => {
|
|
|
213
215
|
grid-template-columns: 1fr 1fr;
|
|
214
216
|
grid-template-rows: min-content 2fr 1fr min-content;
|
|
215
217
|
grid-template-areas:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
'top top'
|
|
219
|
+
'main main'
|
|
220
|
+
'note next'
|
|
221
|
+
'bottom bottom';
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
.grid-container.layout2 {
|
|
223
225
|
grid-template-columns: 3fr 2fr;
|
|
224
226
|
grid-template-rows: min-content 2fr 1fr min-content;
|
|
225
227
|
grid-template-areas:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
'top top'
|
|
229
|
+
'note main'
|
|
230
|
+
'note next'
|
|
231
|
+
'bottom bottom';
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
@media (max-aspect-ratio: 3/5) {
|
|
@@ -234,11 +236,11 @@ onMounted(() => {
|
|
|
234
236
|
grid-template-columns: 1fr;
|
|
235
237
|
grid-template-rows: min-content 1fr 1fr 1fr min-content;
|
|
236
238
|
grid-template-areas:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
'top'
|
|
240
|
+
'main'
|
|
241
|
+
'note'
|
|
242
|
+
'next'
|
|
243
|
+
'bottom';
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
|
|
@@ -247,38 +249,40 @@ onMounted(() => {
|
|
|
247
249
|
grid-template-columns: 1fr 1.1fr 0.9fr;
|
|
248
250
|
grid-template-rows: min-content 1fr 2fr min-content;
|
|
249
251
|
grid-template-areas:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
'top top top'
|
|
253
|
+
'main main next'
|
|
254
|
+
'main main note'
|
|
255
|
+
'bottom bottom bottom';
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
.progress-bar {
|
|
258
|
-
|
|
260
|
+
--uno: fixed left-0 right-0 bottom-0;
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
.grid-section {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
&.top {
|
|
265
|
-
grid-area: top;
|
|
266
|
-
}
|
|
267
|
-
&.main {
|
|
268
|
-
grid-area: main;
|
|
269
|
-
}
|
|
270
|
-
&.next {
|
|
271
|
-
grid-area: next;
|
|
272
|
-
}
|
|
273
|
-
&.note {
|
|
274
|
-
grid-area: note;
|
|
275
|
-
}
|
|
276
|
-
&.bottom {
|
|
277
|
-
grid-area: bottom;
|
|
278
|
-
}
|
|
264
|
+
--uno: bg-main;
|
|
279
265
|
}
|
|
280
266
|
|
|
267
|
+
.grid-section.top {
|
|
268
|
+
grid-area: top;
|
|
269
|
+
}
|
|
270
|
+
.grid-section.main {
|
|
271
|
+
grid-area: main;
|
|
272
|
+
}
|
|
273
|
+
.grid-section.next {
|
|
274
|
+
grid-area: next;
|
|
275
|
+
}
|
|
276
|
+
.grid-section.note {
|
|
277
|
+
grid-area: note;
|
|
278
|
+
}
|
|
279
|
+
.grid-section.bottom {
|
|
280
|
+
grid-area: bottom;
|
|
281
|
+
}
|
|
281
282
|
.context {
|
|
282
|
-
|
|
283
|
+
position: absolute;
|
|
284
|
+
top: 0;
|
|
285
|
+
left: 0;
|
|
286
|
+
--uno: px-1 text-xs bg-gray-400 bg-opacity-50 opacity-75 rounded-br-md;
|
|
283
287
|
}
|
|
284
288
|
</style>
|
package/routes.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
|
|
2
2
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
3
|
-
import type { TransitionGroupProps } from 'vue'
|
|
4
|
-
import type { ClicksContext, SlideInfo } from '@slidev/types'
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
import
|
|
4
|
+
import { rawRoutes, redirects } from '#slidev/routes'
|
|
5
|
+
import configs from '#slidev/configs'
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
import _configs from '/@slidev/configs'
|
|
11
|
-
|
|
12
|
-
export const rawRoutes = _rawRoutes as RouteRecordRaw[]
|
|
7
|
+
export { rawRoutes }
|
|
13
8
|
|
|
14
9
|
export const routes: RouteRecordRaw[] = [
|
|
15
10
|
{
|
|
@@ -34,12 +29,12 @@ export const routes: RouteRecordRaw[] = [
|
|
|
34
29
|
|
|
35
30
|
if (__SLIDEV_FEATURE_PRESENTER__) {
|
|
36
31
|
function passwordGuard(to: RouteLocationNormalized) {
|
|
37
|
-
if (!
|
|
32
|
+
if (!configs.remote || configs.remote === to.query.password)
|
|
38
33
|
return true
|
|
39
|
-
if (
|
|
34
|
+
if (configs.remote && to.query.password === undefined) {
|
|
40
35
|
// eslint-disable-next-line no-alert
|
|
41
36
|
const password = prompt('Enter password')
|
|
42
|
-
if (
|
|
37
|
+
if (configs.remote === password)
|
|
43
38
|
return true
|
|
44
39
|
}
|
|
45
40
|
if (to.params.no)
|
|
@@ -87,28 +82,3 @@ export const router = createRouter({
|
|
|
87
82
|
: createWebHistory(import.meta.env.BASE_URL),
|
|
88
83
|
routes,
|
|
89
84
|
})
|
|
90
|
-
|
|
91
|
-
declare module 'vue-router' {
|
|
92
|
-
interface RouteMeta {
|
|
93
|
-
// inherited from frontmatter
|
|
94
|
-
layout: string
|
|
95
|
-
name?: string
|
|
96
|
-
class?: string
|
|
97
|
-
clicks?: number
|
|
98
|
-
transition?: string | TransitionGroupProps | undefined
|
|
99
|
-
preload?: boolean
|
|
100
|
-
|
|
101
|
-
// slide info
|
|
102
|
-
slide?: Omit<SlideInfo, 'source'> & {
|
|
103
|
-
noteHTML: string
|
|
104
|
-
filepath: string
|
|
105
|
-
start: number
|
|
106
|
-
id: number
|
|
107
|
-
no: number
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// private fields
|
|
111
|
-
__clicksContext: null | ClicksContext
|
|
112
|
-
__preloaded?: boolean
|
|
113
|
-
}
|
|
114
|
-
}
|
package/shim-vue.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
declare module 'vue' {
|
|
2
|
+
import type { UnwrapNestedRefs } from 'vue'
|
|
3
|
+
import type { SlidevContext } from './modules/context'
|
|
4
|
+
|
|
5
|
+
interface ComponentCustomProperties {
|
|
6
|
+
$slidev: UnwrapNestedRefs<SlidevContext>
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module 'vue-router' {
|
|
11
|
+
interface RouteMeta {
|
|
12
|
+
// inherited from frontmatter
|
|
13
|
+
layout: string
|
|
14
|
+
name?: string
|
|
15
|
+
class?: string
|
|
16
|
+
clicks?: number
|
|
17
|
+
transition?: string | TransitionGroupProps | undefined
|
|
18
|
+
preload?: boolean
|
|
19
|
+
|
|
20
|
+
// slide info
|
|
21
|
+
slide?: Omit<SlideInfo, 'source'> & {
|
|
22
|
+
noteHTML: string
|
|
23
|
+
filepath: string
|
|
24
|
+
start: number
|
|
25
|
+
id: number
|
|
26
|
+
no: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// private fields
|
|
30
|
+
__clicksContext: null | ClicksContext
|
|
31
|
+
__preloaded?: boolean
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {}
|
package/shim.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
declare interface Window {
|
|
2
|
-
// extend the window
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
declare module '*.vue';
|
|
6
|
-
|
|
7
1
|
// with unplugin-vue-markdown, markdowns can be treat as Vue components
|
|
8
2
|
declare module '*.md' {
|
|
9
3
|
import type { ComponentOptions } from 'vue'
|
|
@@ -12,12 +6,6 @@ declare module '*.md' {
|
|
|
12
6
|
export default component
|
|
13
7
|
}
|
|
14
8
|
|
|
15
|
-
declare module '/@slidev/configs' {
|
|
16
|
-
import { SlidevConfig } from '@slidev/types'
|
|
17
|
-
|
|
18
|
-
export default SlidevConfig
|
|
19
|
-
}
|
|
20
|
-
|
|
21
9
|
declare module 'mermaid/dist/mermaid.esm.mjs' {
|
|
22
10
|
import Mermaid from 'mermaid/dist/mermaid.d.ts'
|
|
23
11
|
|
package/styles/code.css
CHANGED
|
@@ -59,7 +59,9 @@ html:not(.dark) .shiki span {
|
|
|
59
59
|
.slidev-code-line-numbers .slidev-code code .line::before {
|
|
60
60
|
content: counter(step);
|
|
61
61
|
counter-increment: step;
|
|
62
|
-
|
|
62
|
+
display: inline-block;
|
|
63
|
+
text-align: right;
|
|
64
|
+
--uno: w-4 mr-6 text-gray-400 dark-text-gray-600;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/* Inline Code */
|
|
@@ -67,7 +69,7 @@ html:not(.dark) .shiki span {
|
|
|
67
69
|
font-size: 0.9em;
|
|
68
70
|
background: var(--slidev-code-background);
|
|
69
71
|
border-radius: var(--slidev-code-radius);
|
|
70
|
-
|
|
72
|
+
--uno: font-light py-0.5 px-1.5;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
.slidev-layout :not(pre) > code:before {
|
|
@@ -82,4 +84,6 @@ html:not(.dark) .shiki span {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/* CodeMirror */
|
|
85
|
-
.CodeMirror pre.CodeMirror-placeholder {
|
|
87
|
+
.CodeMirror pre.CodeMirror-placeholder {
|
|
88
|
+
opacity: 0.4;
|
|
89
|
+
}
|
package/styles/index.css
CHANGED
|
@@ -23,15 +23,16 @@ html {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.slidev-icon-btn.shallow {
|
|
26
|
-
|
|
26
|
+
opacity: 0.3;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.slidev-icon-btn.active {
|
|
30
|
-
|
|
30
|
+
opacity: 1;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.slidev-icon-btn.disabled {
|
|
34
|
-
|
|
34
|
+
opacity: 0.25;
|
|
35
|
+
pointer-events: none;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
.slidev-vclick-target {
|
|
@@ -39,11 +40,13 @@ html {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
.slidev-vclick-hidden {
|
|
42
|
-
|
|
43
|
+
opacity: 0 !important;
|
|
44
|
+
pointer-events: none !important;
|
|
45
|
+
user-select: none !important;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
.slidev-vclick-fade {
|
|
46
|
-
|
|
49
|
+
opacity: 0.5;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
.slidev-icon {
|
|
@@ -53,7 +56,44 @@ html {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
.slidev-page {
|
|
56
|
-
|
|
59
|
+
position: relative;
|
|
60
|
+
top: 0;
|
|
61
|
+
left: 0;
|
|
62
|
+
right: 0;
|
|
63
|
+
width: 100%;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Note Clicks */
|
|
67
|
+
|
|
68
|
+
.slidev-note-with-clicks .slidev-note-fade {
|
|
69
|
+
color: #888888ab;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.slidev-note-click-mark {
|
|
73
|
+
font-size: 0.8em;
|
|
74
|
+
--uno: text-violet bg-violet/10 mx1 px1 font-mono rounded flex flex-inline
|
|
75
|
+
items-center align-middle;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.slidev-note-click-mark::before {
|
|
79
|
+
content: '';
|
|
80
|
+
display: inline-block;
|
|
81
|
+
--un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M23 28a1 1 0 0 1-.71-.29l-6.13-6.14l-3.33 5a1 1 0 0 1-1 .44a1 1 0 0 1-.81-.7l-6-20A1 1 0 0 1 6.29 5l20 6a1 1 0 0 1 .7.81a1 1 0 0 1-.44 1l-5 3.33l6.14 6.13a1 1 0 0 1 0 1.42l-4 4A1 1 0 0 1 23 28m0-2.41L25.59 23l-7.16-7.15l5.25-3.5L7.49 7.49l4.86 16.19l3.5-5.25Z'/%3E%3C/svg%3E");
|
|
82
|
+
-webkit-mask: var(--un-icon) no-repeat;
|
|
83
|
+
mask: var(--un-icon) no-repeat;
|
|
84
|
+
-webkit-mask-size: 100% 100%;
|
|
85
|
+
mask-size: 100% 100%;
|
|
86
|
+
background-color: currentColor;
|
|
87
|
+
color: inherit;
|
|
88
|
+
width: 1.2em;
|
|
89
|
+
height: 1.2em;
|
|
90
|
+
opacity: 0.8;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.slidev-note-click-mark::after {
|
|
94
|
+
content: attr(data-clicks);
|
|
95
|
+
display: inline-block;
|
|
96
|
+
transform: translateY(0.1em);
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
/* Transform the position back for Rough Notation (v-mark) */
|
package/styles/katex.css
CHANGED
package/styles/layouts-base.css
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
.slidev-layout {
|
|
2
2
|
@apply px-14 py-10 text-[1.1rem] h-full;
|
|
3
3
|
|
|
4
|
-
pre,
|
|
4
|
+
pre,
|
|
5
|
+
code {
|
|
5
6
|
@apply select-text;
|
|
6
7
|
}
|
|
7
8
|
|
|
@@ -79,11 +80,13 @@
|
|
|
79
80
|
@apply border-current border-b border-dashed hover:text-primary hover:border-solid;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
td,
|
|
83
|
+
td,
|
|
84
|
+
th {
|
|
83
85
|
@apply p-2 py-3;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
b,
|
|
88
|
+
b,
|
|
89
|
+
strong {
|
|
87
90
|
@apply font-600;
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -94,8 +97,8 @@
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
.slidev-layout,
|
|
97
|
-
[dir=ltr],
|
|
98
|
-
.slidev-layout [dir=ltr] {
|
|
100
|
+
[dir='ltr'],
|
|
101
|
+
.slidev-layout [dir='ltr'] {
|
|
99
102
|
h1 {
|
|
100
103
|
@apply -ml-[0.05em] mr-0;
|
|
101
104
|
}
|
|
@@ -109,10 +112,10 @@
|
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
[dir=rtl],
|
|
113
|
-
.slidev-layout [dir=rtl] {
|
|
115
|
+
[dir='rtl'],
|
|
116
|
+
.slidev-layout [dir='rtl'] {
|
|
114
117
|
h1 {
|
|
115
|
-
@apply -mr-[0.05em] ml-0;
|
|
118
|
+
@apply -mr-[0.05em] ml-0;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
h6 {
|
package/styles/vars.css
CHANGED
package/uno.config.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
transformerDirectives,
|
|
9
9
|
transformerVariantGroup,
|
|
10
10
|
} from 'unocss'
|
|
11
|
+
import { variantMatcher } from '@unocss/preset-mini/utils'
|
|
11
12
|
|
|
12
13
|
export default defineConfig({
|
|
13
14
|
safelist: [
|
|
@@ -28,6 +29,13 @@ export default defineConfig({
|
|
|
28
29
|
'abs-bl': 'absolute bottom-0 left-0',
|
|
29
30
|
'abs-br': 'absolute bottom-0 right-0',
|
|
30
31
|
},
|
|
32
|
+
// Slidev Specific Variants, probably extrat to a preset later
|
|
33
|
+
variants: [
|
|
34
|
+
// `forward:` and `backward:` variant to selectively apply styles based on the direction of the slide
|
|
35
|
+
// For example, `forward:text-red` will only apply to the slides that are navigated forward
|
|
36
|
+
variantMatcher('forward', input => ({ prefix: `.slidev-nav-go-forward ${input.prefix}` })),
|
|
37
|
+
variantMatcher('backward', input => ({ prefix: `.slidev-nav-go-backward ${input.prefix}` })),
|
|
38
|
+
],
|
|
31
39
|
presets: [
|
|
32
40
|
presetUno(),
|
|
33
41
|
presetAttributify(),
|