@slidev/client 0.49.8 → 0.49.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/internals/ClicksSlider.vue +13 -6
- package/internals/NoteDisplay.vue +16 -10
- package/internals/NoteEditable.vue +4 -0
- package/logic/slides.ts +1 -2
- package/package.json +8 -8
- package/pages/404.vue +46 -0
- package/pages/notes.vue +12 -1
- package/pages/overview.vue +16 -5
- package/setup/root.ts +2 -0
- package/setup/routes.ts +5 -0
- package/state/shared.ts +5 -0
|
@@ -4,9 +4,13 @@ import { clamp, range } from '@antfu/utils'
|
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
import { CLICKS_MAX } from '../constants'
|
|
6
6
|
|
|
7
|
-
const props = defineProps<{
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
8
|
clicksContext: ClicksContext
|
|
9
|
-
|
|
9
|
+
readonly?: boolean
|
|
10
|
+
active?: boolean
|
|
11
|
+
}>(), {
|
|
12
|
+
active: true,
|
|
13
|
+
})
|
|
10
14
|
|
|
11
15
|
const total = computed(() => props.clicksContext.total)
|
|
12
16
|
const start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))
|
|
@@ -24,6 +28,8 @@ const current = computed({
|
|
|
24
28
|
const clicksRange = computed(() => range(start.value, total.value + 1))
|
|
25
29
|
|
|
26
30
|
function onMousedown() {
|
|
31
|
+
if (props.readonly)
|
|
32
|
+
return
|
|
27
33
|
if (current.value < 0 || current.value > total.value)
|
|
28
34
|
current.value = 0
|
|
29
35
|
}
|
|
@@ -38,7 +44,7 @@ function onMousedown() {
|
|
|
38
44
|
<div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
|
|
39
45
|
<carbon:cursor-1 text-sm op50 />
|
|
40
46
|
<div flex-auto />
|
|
41
|
-
<template v-if="current >= 0 && current !== CLICKS_MAX">
|
|
47
|
+
<template v-if="current >= 0 && current !== CLICKS_MAX && active">
|
|
42
48
|
<span text-primary>{{ current }}</span>
|
|
43
49
|
<span op25>/</span>
|
|
44
50
|
</template>
|
|
@@ -46,7 +52,6 @@ function onMousedown() {
|
|
|
46
52
|
</div>
|
|
47
53
|
<div
|
|
48
54
|
relative flex-auto h5 font-mono flex="~"
|
|
49
|
-
@dblclick="current = clicksContext.total"
|
|
50
55
|
>
|
|
51
56
|
<div
|
|
52
57
|
v-for="i of clicksRange" :key="i"
|
|
@@ -71,8 +76,10 @@ function onMousedown() {
|
|
|
71
76
|
</div>
|
|
72
77
|
<input
|
|
73
78
|
v-model="current"
|
|
74
|
-
class="range"
|
|
75
|
-
type="range" :min="start" :max="total" :step="1"
|
|
79
|
+
class="range"
|
|
80
|
+
type="range" :min="start" :max="total" :step="1"
|
|
81
|
+
absolute inset-0 z-10 op0
|
|
82
|
+
:class="readonly ? 'pointer-events-none' : ''"
|
|
76
83
|
:style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
|
|
77
84
|
@mousedown="onMousedown"
|
|
78
85
|
@focus="event => (event.currentTarget as HTMLElement)?.blur()"
|
|
@@ -3,14 +3,20 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
|
3
3
|
import type { ClicksContext } from '@slidev/types'
|
|
4
4
|
import { CLICKS_MAX } from '../constants'
|
|
5
5
|
|
|
6
|
-
const props =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
class?: string | string[]
|
|
9
|
+
noteHtml?: string
|
|
10
|
+
note?: string
|
|
11
|
+
highlight?: boolean
|
|
12
|
+
placeholder?: string
|
|
13
|
+
clicksContext?: ClicksContext
|
|
14
|
+
autoScroll?: boolean
|
|
15
|
+
}>(),
|
|
16
|
+
{
|
|
17
|
+
highlight: true,
|
|
18
|
+
},
|
|
19
|
+
)
|
|
14
20
|
|
|
15
21
|
const emit = defineEmits<{
|
|
16
22
|
(type: 'markerDblclick', e: MouseEvent, clicks: number): void
|
|
@@ -30,7 +36,7 @@ function highlightNote() {
|
|
|
30
36
|
const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
|
|
31
37
|
|
|
32
38
|
const current = +(props.clicksContext?.current ?? CLICKS_MAX)
|
|
33
|
-
const disabled = current < 0 || current >= CLICKS_MAX
|
|
39
|
+
const disabled = current < 0 || current >= CLICKS_MAX || !props.highlight
|
|
34
40
|
|
|
35
41
|
const nodeToIgnores = new Set<Element>()
|
|
36
42
|
function ignoreParent(node: Element) {
|
|
@@ -114,7 +120,7 @@ function highlightNote() {
|
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
watch(
|
|
117
|
-
() => [props.noteHtml, props.clicksContext?.current],
|
|
123
|
+
() => [props.noteHtml, props.clicksContext?.current, props.highlight],
|
|
118
124
|
() => {
|
|
119
125
|
nextTick(() => {
|
|
120
126
|
highlightNote()
|
|
@@ -26,6 +26,9 @@ const props = defineProps({
|
|
|
26
26
|
clicksContext: {
|
|
27
27
|
type: Object as PropType<ClicksContext>,
|
|
28
28
|
},
|
|
29
|
+
highlight: {
|
|
30
|
+
default: true,
|
|
31
|
+
},
|
|
29
32
|
autoHeight: {
|
|
30
33
|
default: false,
|
|
31
34
|
},
|
|
@@ -112,6 +115,7 @@ watch(
|
|
|
112
115
|
:note-html="info?.noteHTML"
|
|
113
116
|
:clicks-context="clicksContext"
|
|
114
117
|
:auto-scroll="!autoHeight"
|
|
118
|
+
:highlight="props.highlight"
|
|
115
119
|
@marker-click="(e, clicks) => emit('markerClick', e, clicks)"
|
|
116
120
|
@marker-dblclick="(e, clicks) => emit('markerDblclick', e, clicks)"
|
|
117
121
|
/>
|
package/logic/slides.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { SlideRoute } from '@slidev/types'
|
|
2
|
-
import { pathPrefix } from '../env'
|
|
3
2
|
import { slides } from '#slidev/slides'
|
|
4
3
|
|
|
5
4
|
export { slides }
|
|
@@ -17,5 +16,5 @@ export function getSlidePath(
|
|
|
17
16
|
if (typeof route === 'number' || typeof route === 'string')
|
|
18
17
|
route = getSlide(route)!
|
|
19
18
|
const no = route.meta.slide?.frontmatter.routeAlias ?? route.no
|
|
20
|
-
return presenter ?
|
|
19
|
+
return presenter ? `/presenter/${no}` : `/${no}`
|
|
21
20
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.49.
|
|
4
|
+
"version": "0.49.10",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"@shikijs/monaco": "^1.6.1",
|
|
36
36
|
"@shikijs/vitepress-twoslash": "^1.6.1",
|
|
37
37
|
"@slidev/rough-notation": "^0.1.0",
|
|
38
|
-
"@typescript/ata": "^0.9.
|
|
38
|
+
"@typescript/ata": "^0.9.5",
|
|
39
39
|
"@unhead/vue": "^1.9.11",
|
|
40
|
-
"@unocss/reset": "^0.60.
|
|
40
|
+
"@unocss/reset": "^0.60.4",
|
|
41
41
|
"@vueuse/core": "^10.10.0",
|
|
42
42
|
"@vueuse/math": "^10.10.0",
|
|
43
|
-
"@vueuse/motion": "^2.
|
|
43
|
+
"@vueuse/motion": "^2.2.3",
|
|
44
44
|
"codemirror": "^5.65.16",
|
|
45
45
|
"drauu": "^0.4.0",
|
|
46
46
|
"file-saver": "^2.0.5",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
"lz-string": "^1.5.0",
|
|
51
51
|
"mermaid": "^10.9.1",
|
|
52
52
|
"monaco-editor": "^0.49.0",
|
|
53
|
-
"prettier": "^3.
|
|
53
|
+
"prettier": "^3.3.0",
|
|
54
54
|
"recordrtc": "^5.6.2",
|
|
55
55
|
"shiki": "^1.6.1",
|
|
56
56
|
"shiki-magic-move": "^0.4.2",
|
|
57
57
|
"typescript": "^5.4.5",
|
|
58
|
-
"unocss": "^0.60.
|
|
58
|
+
"unocss": "^0.60.4",
|
|
59
59
|
"vue": "^3.4.27",
|
|
60
60
|
"vue-router": "^4.3.2",
|
|
61
61
|
"yaml": "^2.4.2",
|
|
62
|
-
"@slidev/parser": "0.49.
|
|
63
|
-
"@slidev/types": "0.49.
|
|
62
|
+
"@slidev/parser": "0.49.10",
|
|
63
|
+
"@slidev/types": "0.49.10"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"vite": "^5.2.12"
|
package/pages/404.vue
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
import { useNav } from '../composables/useNav'
|
|
5
|
+
|
|
6
|
+
const { currentRoute } = useRouter()
|
|
7
|
+
const { total } = useNav()
|
|
8
|
+
|
|
9
|
+
const guessedSlide = computed(() => {
|
|
10
|
+
const path = currentRoute.value.path
|
|
11
|
+
const match = path.match(/\d+/)
|
|
12
|
+
if (match) {
|
|
13
|
+
const slideNo = +match[0]
|
|
14
|
+
if (slideNo > 0 && slideNo <= total.value)
|
|
15
|
+
return slideNo
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
})
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div class="grid justify-center pt-15%">
|
|
23
|
+
<div>
|
|
24
|
+
<h1 class="text-9xl font-bold">
|
|
25
|
+
404
|
|
26
|
+
</h1>
|
|
27
|
+
<p class="text-2xl">
|
|
28
|
+
Page not found<code class="op-70">:{{ currentRoute.path }}</code>
|
|
29
|
+
</p>
|
|
30
|
+
<div class="mt-3 flex flex-col gap-2">
|
|
31
|
+
<RouterLink v-if="guessedSlide !== 1" to="/" class="page-link">
|
|
32
|
+
Go Home
|
|
33
|
+
</RouterLink>
|
|
34
|
+
<RouterLink v-if="guessedSlide" :to="`/${guessedSlide}`" class="page-link">
|
|
35
|
+
Go to Slide {{ guessedSlide }}
|
|
36
|
+
</RouterLink>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style scoped lang="postcss">
|
|
43
|
+
.page-link {
|
|
44
|
+
@apply py-2 px-4 bg-gray/10 hover:bg-gray/20 rounded;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
package/pages/notes.vue
CHANGED
|
@@ -8,7 +8,9 @@ import { fullscreen } from '../state'
|
|
|
8
8
|
|
|
9
9
|
import NoteDisplay from '../internals/NoteDisplay.vue'
|
|
10
10
|
import IconButton from '../internals/IconButton.vue'
|
|
11
|
+
import ClicksSlider from '../internals/ClicksSlider.vue'
|
|
11
12
|
import { useNav } from '../composables/useNav'
|
|
13
|
+
import { createClicksContextBase } from '../composables/useClicks'
|
|
12
14
|
|
|
13
15
|
useHead({ title: `Notes - ${slidesTitle}` })
|
|
14
16
|
|
|
@@ -32,6 +34,12 @@ function increaseFontSize() {
|
|
|
32
34
|
function decreaseFontSize() {
|
|
33
35
|
fontSize.value = fontSize.value - 1
|
|
34
36
|
}
|
|
37
|
+
|
|
38
|
+
const clicksContext = computed(() => {
|
|
39
|
+
const clicks = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicks : sharedState.clicks
|
|
40
|
+
const total = sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerClicksTotal : sharedState.clicksTotal
|
|
41
|
+
return createClicksContextBase(ref(clicks), undefined, total)
|
|
42
|
+
})
|
|
35
43
|
</script>
|
|
36
44
|
|
|
37
45
|
<template>
|
|
@@ -49,10 +57,13 @@ function decreaseFontSize() {
|
|
|
49
57
|
:note="currentRoute?.meta.slide.note"
|
|
50
58
|
:note-html="currentRoute?.meta.slide.noteHTML"
|
|
51
59
|
:placeholder="`No notes for Slide ${pageNo}.`"
|
|
52
|
-
:clicks-context="
|
|
60
|
+
:clicks-context="clicksContext"
|
|
53
61
|
:auto-scroll="true"
|
|
54
62
|
/>
|
|
55
63
|
</div>
|
|
64
|
+
<div class="flex-none border-t border-main" px3 py2>
|
|
65
|
+
<ClicksSlider :clicks-context="clicksContext" readonly />
|
|
66
|
+
</div>
|
|
56
67
|
<div class="flex-none border-t border-main">
|
|
57
68
|
<div class="flex gap-1 items-center px-6 py-3">
|
|
58
69
|
<IconButton :title="isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'" @click="toggleFullscreen">
|
package/pages/overview.vue
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
|
2
|
+
import { computed, nextTick, onMounted, reactive, ref, shallowRef } from 'vue'
|
|
3
3
|
import { useHead } from '@unhead/vue'
|
|
4
4
|
import type { ClicksContext, SlideRoute } from '@slidev/types'
|
|
5
|
-
import { slidesTitle } from '../env'
|
|
5
|
+
import { pathPrefix, slidesTitle } from '../env'
|
|
6
6
|
import { getSlidePath } from '../logic/slides'
|
|
7
7
|
import { createFixedClicks } from '../composables/useClicks'
|
|
8
8
|
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
|
|
@@ -28,6 +28,7 @@ const wordCounts = computed(() => slides.value.map(route => wordCount(route.meta
|
|
|
28
28
|
const totalWords = computed(() => wordCounts.value.reduce((a, b) => a + b, 0))
|
|
29
29
|
const totalClicks = computed(() => slides.value.map(route => getSlideClicks(route)).reduce((a, b) => a + b, 0))
|
|
30
30
|
|
|
31
|
+
const activeSlide = shallowRef<SlideRoute>()
|
|
31
32
|
const clicksContextMap = new WeakMap<SlideRoute, ClicksContext>()
|
|
32
33
|
function getClicksContext(route: SlideRoute) {
|
|
33
34
|
// We create a local clicks context to calculate the total clicks of the slide
|
|
@@ -40,8 +41,15 @@ function getSlideClicks(route: SlideRoute) {
|
|
|
40
41
|
return route.meta?.clicks || getClicksContext(route)?.total
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function toggleRoute(route: SlideRoute) {
|
|
45
|
+
if (activeSlide.value === route)
|
|
46
|
+
activeSlide.value = undefined
|
|
47
|
+
else
|
|
48
|
+
activeSlide.value = route
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
function wordCount(str: string) {
|
|
44
|
-
return str.match(/[\w
|
|
52
|
+
return str.match(/[\w`'\-]+/g)?.length || 0
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
function isElementInViewport(el: HTMLElement) {
|
|
@@ -68,7 +76,7 @@ function checkActiveBlocks() {
|
|
|
68
76
|
function openSlideInNewTab(path: string) {
|
|
69
77
|
const a = document.createElement('a')
|
|
70
78
|
a.target = '_blank'
|
|
71
|
-
a.href = path
|
|
79
|
+
a.href = pathPrefix + path.slice(1)
|
|
72
80
|
a.click()
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -185,9 +193,11 @@ onMounted(() => {
|
|
|
185
193
|
</div>
|
|
186
194
|
<ClicksSlider
|
|
187
195
|
v-if="getSlideClicks(route)"
|
|
196
|
+
:active="activeSlide === route"
|
|
188
197
|
:clicks-context="getClicksContext(route)"
|
|
189
198
|
class="w-full mt-2"
|
|
190
|
-
@dblclick="
|
|
199
|
+
@dblclick="toggleRoute(route)"
|
|
200
|
+
@click="activeSlide = route"
|
|
191
201
|
/>
|
|
192
202
|
</div>
|
|
193
203
|
<div class="py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100">
|
|
@@ -204,6 +214,7 @@ onMounted(() => {
|
|
|
204
214
|
:no="route.no"
|
|
205
215
|
class="max-w-250 w-250 text-lg rounded p3"
|
|
206
216
|
:auto-height="true"
|
|
217
|
+
:highlight="activeSlide === route"
|
|
207
218
|
:editing="edittingNote === route.no"
|
|
208
219
|
:clicks-context="getClicksContext(route)"
|
|
209
220
|
@dblclick="edittingNote !== route.no ? edittingNote = route.no : null"
|
package/setup/root.ts
CHANGED
|
@@ -67,10 +67,12 @@ export default function setupRoot() {
|
|
|
67
67
|
if (isPresenter.value) {
|
|
68
68
|
patch('page', +currentSlideNo.value)
|
|
69
69
|
patch('clicks', clicksContext.value.current)
|
|
70
|
+
patch('clicksTotal', clicksContext.value.total)
|
|
70
71
|
}
|
|
71
72
|
else {
|
|
72
73
|
patch('viewerPage', +currentSlideNo.value)
|
|
73
74
|
patch('viewerClicks', clicksContext.value.current)
|
|
75
|
+
patch('viewerClicksTotal', clicksContext.value.total)
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
patch('lastUpdate', {
|
package/setup/routes.ts
CHANGED
|
@@ -74,6 +74,11 @@ export default function setupRoutes() {
|
|
|
74
74
|
path: '',
|
|
75
75
|
redirect: { path: '/1' },
|
|
76
76
|
},
|
|
77
|
+
{
|
|
78
|
+
path: '/:pathMatch(.*)*',
|
|
79
|
+
name: 'NotFound',
|
|
80
|
+
component: () => import('../pages/404.vue'),
|
|
81
|
+
},
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
return setups.reduce((routes, setup) => setup(routes), routes)
|
package/state/shared.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { createSyncState } from './syncState'
|
|
|
4
4
|
export interface SharedState {
|
|
5
5
|
page: number
|
|
6
6
|
clicks: number
|
|
7
|
+
clicksTotal: number
|
|
8
|
+
|
|
7
9
|
cursor?: {
|
|
8
10
|
x: number
|
|
9
11
|
y: number
|
|
@@ -11,6 +13,7 @@ export interface SharedState {
|
|
|
11
13
|
|
|
12
14
|
viewerPage: number
|
|
13
15
|
viewerClicks: number
|
|
16
|
+
viewerClicksTotal: number
|
|
14
17
|
|
|
15
18
|
lastUpdate?: {
|
|
16
19
|
id: string
|
|
@@ -22,8 +25,10 @@ export interface SharedState {
|
|
|
22
25
|
const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
|
|
23
26
|
page: 1,
|
|
24
27
|
clicks: 0,
|
|
28
|
+
clicksTotal: 0,
|
|
25
29
|
viewerPage: 1,
|
|
26
30
|
viewerClicks: 0,
|
|
31
|
+
viewerClicksTotal: 0,
|
|
27
32
|
})
|
|
28
33
|
|
|
29
34
|
export {
|