@slidev/client 0.30.1 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtin/Arrow.vue +2 -2
- package/builtin/AutoFitText.vue +4 -4
- package/builtin/Link.vue +6 -4
- package/builtin/PlantUml.vue +1 -3
- package/builtin/Toc.vue +2 -2
- package/builtin/TocList.vue +16 -3
- package/builtin/Tweet.vue +7 -3
- package/composables/useNavClicks.ts +1 -1
- package/env.ts +0 -15
- package/internals/Controls.vue +3 -5
- package/internals/DrawingControls.vue +6 -6
- package/internals/Editor.vue +3 -3
- package/internals/Goto.vue +21 -8
- package/internals/InfoDialog.vue +1 -1
- package/internals/MenuButton.vue +3 -3
- package/internals/Modal.vue +1 -1
- package/internals/NavControls.vue +4 -4
- package/internals/Play.vue +2 -2
- package/internals/Presenter.vue +3 -2
- package/internals/PresenterMouse.vue +3 -3
- package/internals/Print.vue +1 -1
- package/internals/PrintSlideClick.vue +5 -4
- package/internals/RecordingControls.vue +2 -2
- package/internals/RecordingDialog.vue +3 -3
- package/internals/SelectList.vue +3 -3
- package/internals/SlidesOverview.vue +1 -1
- package/logic/drawings.ts +16 -22
- package/logic/nav.ts +5 -3
- package/logic/note.ts +10 -8
- package/logic/recording.ts +5 -5
- package/modules/context.ts +1 -1
- package/package.json +12 -13
- package/routes.ts +9 -14
- package/setup/monaco.ts +2 -2
- package/setup/root.ts +22 -26
- package/state/drawings.ts +7 -0
- package/state/shared.ts +17 -0
- package/state/syncState.ts +67 -0
package/builtin/Arrow.vue
CHANGED
|
@@ -11,8 +11,6 @@ Simple Arrow
|
|
|
11
11
|
<script setup lang="ts">
|
|
12
12
|
import { customAlphabet } from 'nanoid'
|
|
13
13
|
|
|
14
|
-
const nanoid = customAlphabet('abcedfghijklmn', 10)
|
|
15
|
-
|
|
16
14
|
defineProps<{
|
|
17
15
|
x1: number | string
|
|
18
16
|
y1: number | string
|
|
@@ -22,6 +20,8 @@ defineProps<{
|
|
|
22
20
|
color?: string
|
|
23
21
|
}>()
|
|
24
22
|
|
|
23
|
+
const nanoid = customAlphabet('abcedfghijklmn', 10)
|
|
24
|
+
|
|
25
25
|
const id = nanoid()
|
|
26
26
|
</script>
|
|
27
27
|
|
package/builtin/AutoFitText.vue
CHANGED
|
@@ -18,9 +18,6 @@ or
|
|
|
18
18
|
import { useElementSize, useVModel } from '@vueuse/core'
|
|
19
19
|
import { computed, ref, watch } from 'vue'
|
|
20
20
|
|
|
21
|
-
const emit = defineEmits<{
|
|
22
|
-
(e: any): void
|
|
23
|
-
}>()
|
|
24
21
|
const props = defineProps({
|
|
25
22
|
modelValue: {
|
|
26
23
|
default: '',
|
|
@@ -33,6 +30,9 @@ const props = defineProps({
|
|
|
33
30
|
},
|
|
34
31
|
})
|
|
35
32
|
|
|
33
|
+
const emit = defineEmits<{
|
|
34
|
+
(e: any): void
|
|
35
|
+
}>()
|
|
36
36
|
const container = ref<HTMLDivElement>()
|
|
37
37
|
const inner = ref<HTMLDivElement>()
|
|
38
38
|
const size = ref(100)
|
|
@@ -45,7 +45,7 @@ const innerSize = useElementSize(inner)
|
|
|
45
45
|
const wrapLen = ref(0)
|
|
46
46
|
const wrap = ref('nowrap')
|
|
47
47
|
|
|
48
|
-
watch([container, value, containerSize.width, innerSize.width], async() => {
|
|
48
|
+
watch([container, value, containerSize.width, innerSize.width], async () => {
|
|
49
49
|
if (!container.value || innerSize.width.value <= 0)
|
|
50
50
|
return
|
|
51
51
|
const ratio = containerSize.width.value / innerSize.width.value
|
package/builtin/Link.vue
CHANGED
|
@@ -10,6 +10,8 @@ Usage:
|
|
|
10
10
|
<script setup lang="ts">
|
|
11
11
|
import { isPrintMode } from '../logic/nav'
|
|
12
12
|
|
|
13
|
+
/* eslint-disable vue/no-v-text-v-html-on-component */
|
|
14
|
+
|
|
13
15
|
defineProps<{
|
|
14
16
|
to: number | string
|
|
15
17
|
title?: string
|
|
@@ -17,10 +19,10 @@ defineProps<{
|
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
21
|
<template>
|
|
20
|
-
<RouterLink v-if="!isPrintMode && title" :to="to" v-html="title" />
|
|
21
|
-
<RouterLink v-else-if="!isPrintMode && !title" :to="to">
|
|
22
|
+
<RouterLink v-if="!isPrintMode && title" :to="to" @click="$event.target.blur()" v-html="title" />
|
|
23
|
+
<RouterLink v-else-if="!isPrintMode && !title" :to="to" @click="$event.target.blur()">
|
|
22
24
|
<slot />
|
|
23
25
|
</RouterLink>
|
|
24
|
-
<a v-else-if="isPrintMode && title" :href="
|
|
25
|
-
<a v-else :href="
|
|
26
|
+
<a v-else-if="isPrintMode && title" :href="`#${to}`" v-html="title" />
|
|
27
|
+
<a v-else :href="`#${to}`"><slot /></a>
|
|
26
28
|
</template>
|
package/builtin/PlantUml.vue
CHANGED
|
@@ -11,7 +11,6 @@ Alice -> Bob : Hello!
|
|
|
11
11
|
```
|
|
12
12
|
-->
|
|
13
13
|
<script setup lang="ts">
|
|
14
|
-
|
|
15
14
|
import { computed } from 'vue'
|
|
16
15
|
|
|
17
16
|
const props = defineProps<{
|
|
@@ -21,9 +20,8 @@ const props = defineProps<{
|
|
|
21
20
|
}>()
|
|
22
21
|
|
|
23
22
|
const uri = computed(() => `${props.server}/svg/${props.code}`)
|
|
24
|
-
|
|
25
23
|
</script>
|
|
26
24
|
|
|
27
25
|
<template>
|
|
28
|
-
<img alt="PlantUML diagram" :src="uri" :style="{scale}">
|
|
26
|
+
<img alt="PlantUML diagram" :src="uri" :style="{ scale }">
|
|
29
27
|
</template>
|
package/builtin/Toc.vue
CHANGED
|
@@ -12,8 +12,6 @@ import { computed, inject } from 'vue'
|
|
|
12
12
|
import type { TocItem } from '../logic/nav'
|
|
13
13
|
import { injectionSlidevContext } from '../constants'
|
|
14
14
|
|
|
15
|
-
const $slidev = inject(injectionSlidevContext)
|
|
16
|
-
|
|
17
15
|
const props = withDefaults(
|
|
18
16
|
defineProps<{
|
|
19
17
|
columns?: string | number
|
|
@@ -31,6 +29,8 @@ const props = withDefaults(
|
|
|
31
29
|
},
|
|
32
30
|
)
|
|
33
31
|
|
|
32
|
+
const $slidev = inject(injectionSlidevContext)
|
|
33
|
+
|
|
34
34
|
function filterTreeDepth(tree: TocItem[], level = 1): TocItem[] {
|
|
35
35
|
if (level > Number(props.maxDepth)) {
|
|
36
36
|
return []
|
package/builtin/TocList.vue
CHANGED
|
@@ -9,6 +9,8 @@ Usage:
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
10
|
import { computed } from 'vue'
|
|
11
11
|
import { toArray } from '@antfu/utils'
|
|
12
|
+
// @ts-expect-error virtual module
|
|
13
|
+
import Titles from '/@slidev/titles.md'
|
|
12
14
|
import type { TocItem } from '../logic/nav'
|
|
13
15
|
|
|
14
16
|
const props = withDefaults(defineProps<{
|
|
@@ -28,9 +30,20 @@ const classes = computed(() => {
|
|
|
28
30
|
|
|
29
31
|
<template>
|
|
30
32
|
<ol v-if="list && list.length > 0" :class="classes">
|
|
31
|
-
<li
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
<li
|
|
34
|
+
v-for="item in list"
|
|
35
|
+
:key="item.path" class="slidev-toc-item"
|
|
36
|
+
:class="[{ 'slidev-toc-item-active': item.active }, { 'slidev-toc-item-parent-active': item.activeParent }]"
|
|
37
|
+
>
|
|
38
|
+
<Link :to="item.path">
|
|
39
|
+
<Titles :no="item.path" />
|
|
40
|
+
</Link>
|
|
41
|
+
<TocList
|
|
42
|
+
v-if="item.children.length > 0"
|
|
43
|
+
:level="level + 1"
|
|
44
|
+
:list="item.children"
|
|
45
|
+
:list-class="listClass"
|
|
46
|
+
/>
|
|
34
47
|
</li>
|
|
35
48
|
</ol>
|
|
36
49
|
</template>
|
package/builtin/Tweet.vue
CHANGED
|
@@ -21,10 +21,11 @@ const tweet = ref<HTMLElement | null>()
|
|
|
21
21
|
|
|
22
22
|
const vm = getCurrentInstance()!
|
|
23
23
|
const loaded = ref(false)
|
|
24
|
+
const tweetNotFound = ref(false)
|
|
24
25
|
|
|
25
26
|
async function create() {
|
|
26
27
|
// @ts-expect-error global
|
|
27
|
-
await window.twttr.widgets.createTweet(
|
|
28
|
+
const element = await window.twttr.widgets.createTweet(
|
|
28
29
|
props.id.toString(),
|
|
29
30
|
tweet.value,
|
|
30
31
|
{
|
|
@@ -33,6 +34,8 @@ async function create() {
|
|
|
33
34
|
},
|
|
34
35
|
)
|
|
35
36
|
loaded.value = true
|
|
37
|
+
if (element === undefined)
|
|
38
|
+
tweetNotFound.value = true
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// @ts-expect-error global
|
|
@@ -55,10 +58,11 @@ else {
|
|
|
55
58
|
|
|
56
59
|
<template>
|
|
57
60
|
<Transform :scale="scale || 1">
|
|
58
|
-
<div ref="tweet" class="tweet" data-waitfor="iframe">
|
|
59
|
-
<div v-if="!loaded" class="w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50">
|
|
61
|
+
<div ref="tweet" class="tweet" :data-waitfor="tweetNotFound ? '' : 'iframe'">
|
|
62
|
+
<div v-if="!loaded || tweetNotFound" class="w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50">
|
|
60
63
|
<div class="m-auto animate-pulse text-4xl">
|
|
61
64
|
<carbon:logo-twitter />
|
|
65
|
+
<span v-if="tweetNotFound">Could not load tweet with id="{{ props.id }}"</span>
|
|
62
66
|
</div>
|
|
63
67
|
</div>
|
|
64
68
|
</div>
|
|
@@ -11,7 +11,7 @@ export function useNavClicks(
|
|
|
11
11
|
// force update collected elements when the route is fully resolved
|
|
12
12
|
const routeForceRefresh = ref(0)
|
|
13
13
|
nextTick(() => {
|
|
14
|
-
router.afterEach(async() => {
|
|
14
|
+
router.afterEach(async () => {
|
|
15
15
|
await nextTick()
|
|
16
16
|
routeForceRefresh.value += 1
|
|
17
17
|
})
|
package/env.ts
CHANGED
|
@@ -3,23 +3,8 @@ import { computed } from 'vue'
|
|
|
3
3
|
import { objectMap } from '@antfu/utils'
|
|
4
4
|
// @ts-expect-error missing types
|
|
5
5
|
import _configs from '/@slidev/configs'
|
|
6
|
-
import _serverState from 'server-reactive:nav'
|
|
7
|
-
import _serverDrawingState from 'server-reactive:drawings?diff'
|
|
8
|
-
import type { ServerReactive } from 'vite-plugin-vue-server-ref'
|
|
9
6
|
|
|
10
|
-
export interface ServerState {
|
|
11
|
-
page: number
|
|
12
|
-
clicks: number
|
|
13
|
-
cursor?: {
|
|
14
|
-
x: number
|
|
15
|
-
y: number
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const serverState = _serverState as ServerReactive<ServerState>
|
|
20
|
-
export const serverDrawingState = _serverDrawingState as ServerReactive<Record<number, string | undefined>>
|
|
21
7
|
export const configs = _configs as SlidevConfig
|
|
22
|
-
|
|
23
8
|
export const slideAspect = configs.aspectRatio ?? (16 / 9)
|
|
24
9
|
export const slideWidth = configs.canvasWidth ?? 980
|
|
25
10
|
export const slideHeight = Math.round(slideWidth / slideAspect)
|
package/internals/Controls.vue
CHANGED
|
@@ -8,7 +8,7 @@ import Goto from './Goto.vue'
|
|
|
8
8
|
|
|
9
9
|
const WebCamera = shallowRef<any>()
|
|
10
10
|
const RecordingDialog = shallowRef<any>()
|
|
11
|
-
if (
|
|
11
|
+
if (__SLIDEV_FEATURE_RECORD__) {
|
|
12
12
|
import('./WebCamera.vue').then(v => WebCamera.value = v.default)
|
|
13
13
|
import('./RecordingDialog.vue').then(v => RecordingDialog.value = v.default)
|
|
14
14
|
}
|
|
@@ -17,9 +17,7 @@ if (__DEV__) {
|
|
|
17
17
|
<template>
|
|
18
18
|
<SlidesOverview v-model="showOverview" />
|
|
19
19
|
<Goto />
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
<RecordingDialog v-if="RecordingDialog" v-model="showRecordingDialog" />
|
|
23
|
-
</template>
|
|
20
|
+
<WebCamera v-if="WebCamera" />
|
|
21
|
+
<RecordingDialog v-if="RecordingDialog" v-model="showRecordingDialog" />
|
|
24
22
|
<InfoDialog v-if="configs.info" v-model="showInfoDialog" />
|
|
25
23
|
</template>
|
|
@@ -31,21 +31,21 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
31
31
|
:initial-x="10"
|
|
32
32
|
:initial-y="10"
|
|
33
33
|
>
|
|
34
|
-
<button class="icon-btn" :class="{ shallow: drawingMode
|
|
34
|
+
<button class="icon-btn" :class="{ shallow: drawingMode !== 'stylus' }" @click="setDrawingMode('stylus')">
|
|
35
35
|
<carbon:pen />
|
|
36
36
|
</button>
|
|
37
|
-
<button class="icon-btn" :class="{ shallow: drawingMode
|
|
37
|
+
<button class="icon-btn" :class="{ shallow: drawingMode !== 'line' }" @click="setDrawingMode('line')">
|
|
38
38
|
<svg width="1em" height="1em" class="-mt-0.5" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
|
|
39
39
|
<path d="M21.71 3.29a1 1 0 0 0-1.42 0l-18 18a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l18-18a1 1 0 0 0 0-1.42z" fill="currentColor" />
|
|
40
40
|
</svg>
|
|
41
41
|
</button>
|
|
42
|
-
<button class="icon-btn" :class="{ shallow: drawingMode
|
|
42
|
+
<button class="icon-btn" :class="{ shallow: drawingMode !== 'arrow' }" @click="setDrawingMode('arrow')">
|
|
43
43
|
<carbon:arrow-up-right />
|
|
44
44
|
</button>
|
|
45
|
-
<button class="icon-btn" :class="{ shallow: drawingMode
|
|
45
|
+
<button class="icon-btn" :class="{ shallow: drawingMode !== 'ellipse' }" @click="setDrawingMode('ellipse')">
|
|
46
46
|
<carbon:radio-button />
|
|
47
47
|
</button>
|
|
48
|
-
<button class="icon-btn" :class="{ shallow: drawingMode
|
|
48
|
+
<button class="icon-btn" :class="{ shallow: drawingMode !== 'rectangle' }" @click="setDrawingMode('rectangle')">
|
|
49
49
|
<carbon:checkbox />
|
|
50
50
|
</button>
|
|
51
51
|
<!-- TODO: not sure why it's not working! -->
|
|
@@ -65,7 +65,7 @@ function setBrushColor(color: typeof brush.color) {
|
|
|
65
65
|
<div
|
|
66
66
|
class="w-6 h-6 transition-all transform border border-gray-400/50"
|
|
67
67
|
:class="brush.color !== color ? 'rounded-1/2 scale-85' : 'rounded-md'"
|
|
68
|
-
:style="drawingEnabled? { background: color } : { borderColor: color }"
|
|
68
|
+
:style="drawingEnabled ? { background: color } : { borderColor: color }"
|
|
69
69
|
/>
|
|
70
70
|
</button>
|
|
71
71
|
|
package/internals/Editor.vue
CHANGED
|
@@ -127,13 +127,13 @@ throttledWatch(
|
|
|
127
127
|
<template>
|
|
128
128
|
<div
|
|
129
129
|
class="fixed h-full top-0 bottom-0 w-10px bg-gray-400 select-none opacity-0 hover:opacity-10 z-100"
|
|
130
|
-
:class="{'!opacity-30': handlerDown}"
|
|
131
|
-
:style="{right: `${editorWidth - 5}px`, cursor: 'col-resize'}"
|
|
130
|
+
:class="{ '!opacity-30': handlerDown }"
|
|
131
|
+
:style="{ right: `${editorWidth - 5}px`, cursor: 'col-resize' }"
|
|
132
132
|
@pointerdown="onHandlerDown"
|
|
133
133
|
/>
|
|
134
134
|
<div
|
|
135
135
|
class="shadow bg-main p-4 grid grid-rows-[max-content,1fr] h-full overflow-hidden border-l border-gray-400 border-opacity-20"
|
|
136
|
-
:style="{width: `${editorWidth}px`}"
|
|
136
|
+
:style="{ width: `${editorWidth}px` }"
|
|
137
137
|
>
|
|
138
138
|
<div class="flex pb-2 text-xl -mt-1">
|
|
139
139
|
<div class="mr-4 rounded flex">
|
package/internals/Goto.vue
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, nextTick, ref, watch } from 'vue'
|
|
3
|
-
import { go, total } from '../logic/nav'
|
|
3
|
+
import { go, rawRoutes, total } from '../logic/nav'
|
|
4
4
|
import { showGotoDialog } from '../state'
|
|
5
5
|
|
|
6
6
|
const input = ref<HTMLInputElement>()
|
|
7
7
|
const text = ref('')
|
|
8
|
-
|
|
9
|
-
const valid = computed(() =>
|
|
8
|
+
|
|
9
|
+
const valid = computed(() => {
|
|
10
|
+
if (text.value.startsWith('/')) {
|
|
11
|
+
return !!rawRoutes.find(r => r.path === text.value.substring(1))
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const num = +text.value
|
|
15
|
+
return !isNaN(num) && num > 0 && num <= total.value
|
|
16
|
+
}
|
|
17
|
+
})
|
|
10
18
|
|
|
11
19
|
function goTo() {
|
|
12
|
-
if (valid.value)
|
|
13
|
-
|
|
20
|
+
if (valid.value) {
|
|
21
|
+
if (text.value.startsWith('/'))
|
|
22
|
+
go(text.value.substring(1))
|
|
23
|
+
|
|
24
|
+
else
|
|
25
|
+
go(+text.value)
|
|
26
|
+
}
|
|
14
27
|
close()
|
|
15
28
|
}
|
|
16
29
|
|
|
@@ -18,7 +31,7 @@ function close() {
|
|
|
18
31
|
showGotoDialog.value = false
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
watch(showGotoDialog, async(show) => {
|
|
34
|
+
watch(showGotoDialog, async (show) => {
|
|
22
35
|
if (show) {
|
|
23
36
|
await nextTick()
|
|
24
37
|
text.value = ''
|
|
@@ -31,8 +44,8 @@ watch(showGotoDialog, async(show) => {
|
|
|
31
44
|
|
|
32
45
|
// remove the g character coming from the key that triggered showGotoDialog (e.g. in Firefox)
|
|
33
46
|
watch(text, (t) => {
|
|
34
|
-
if (t.match(/^[^0-9]/))
|
|
35
|
-
text.value = text.value.
|
|
47
|
+
if (t.match(/^[^0-9/]/))
|
|
48
|
+
text.value = text.value.substring(1)
|
|
36
49
|
})
|
|
37
50
|
</script>
|
|
38
51
|
|
package/internals/InfoDialog.vue
CHANGED
|
@@ -4,13 +4,13 @@ import { computed } from 'vue'
|
|
|
4
4
|
import { configs } from '../env'
|
|
5
5
|
import Modal from './Modal.vue'
|
|
6
6
|
|
|
7
|
-
const emit = defineEmits<{ (name: 'modelValue', v: boolean): void }>()
|
|
8
7
|
const props = defineProps({
|
|
9
8
|
modelValue: {
|
|
10
9
|
default: false,
|
|
11
10
|
},
|
|
12
11
|
})
|
|
13
12
|
|
|
13
|
+
const emit = defineEmits<{ (name: 'modelValue', v: boolean): void }>()
|
|
14
14
|
const value = useVModel(props, 'modelValue', emit)
|
|
15
15
|
const hasInfo = computed(() => typeof configs.info === 'string')
|
|
16
16
|
</script>
|
package/internals/MenuButton.vue
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
import { onClickOutside, useVModel } from '@vueuse/core'
|
|
3
3
|
import { ref } from 'vue'
|
|
4
4
|
|
|
5
|
-
const emit = defineEmits<{
|
|
6
|
-
(e: any): void
|
|
7
|
-
}>()
|
|
8
5
|
const props = defineProps({
|
|
9
6
|
modelValue: {
|
|
10
7
|
default: false,
|
|
@@ -14,6 +11,9 @@ const props = defineProps({
|
|
|
14
11
|
},
|
|
15
12
|
})
|
|
16
13
|
|
|
14
|
+
const emit = defineEmits<{
|
|
15
|
+
(e: any): void
|
|
16
|
+
}>()
|
|
17
17
|
const value = useVModel(props, 'modelValue', emit, { passive: true })
|
|
18
18
|
const el = ref<HTMLDivElement>()
|
|
19
19
|
|
package/internals/Modal.vue
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useVModel } from '@vueuse/core'
|
|
3
3
|
|
|
4
|
-
const emit = defineEmits<{ (name: 'modelValue', v: boolean): void }>()
|
|
5
4
|
const props = defineProps({
|
|
6
5
|
modelValue: {
|
|
7
6
|
default: false,
|
|
@@ -11,6 +10,7 @@ const props = defineProps({
|
|
|
11
10
|
},
|
|
12
11
|
})
|
|
13
12
|
|
|
13
|
+
const emit = defineEmits<{ (name: 'modelValue', v: boolean): void }>()
|
|
14
14
|
const value = useVModel(props, 'modelValue', emit)
|
|
15
15
|
|
|
16
16
|
function onClick() {
|
|
@@ -35,7 +35,7 @@ const barStyle = computed(() => props.persist
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
const RecordingControls = shallowRef<any>()
|
|
38
|
-
if (
|
|
38
|
+
if (__SLIDEV_FEATURE_RECORD__)
|
|
39
39
|
import('./RecordingControls.vue').then(v => RecordingControls.value = v.default)
|
|
40
40
|
|
|
41
41
|
const DrawingControls = shallowRef<any>()
|
|
@@ -79,7 +79,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
79
79
|
|
|
80
80
|
<VerticalDivider />
|
|
81
81
|
|
|
82
|
-
<template v-if="
|
|
82
|
+
<template v-if="!isEmbedded">
|
|
83
83
|
<template v-if="!isPresenter && !md && RecordingControls">
|
|
84
84
|
<RecordingControls />
|
|
85
85
|
<VerticalDivider />
|
|
@@ -108,7 +108,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
108
108
|
<VerticalDivider />
|
|
109
109
|
</template>
|
|
110
110
|
|
|
111
|
-
<template v-if="
|
|
111
|
+
<template v-if="!isEmbedded">
|
|
112
112
|
<RouterLink v-if="isPresenter" :to="nonPresenterLink" class="icon-btn" title="Play Mode">
|
|
113
113
|
<carbon:presentation-file />
|
|
114
114
|
</RouterLink>
|
|
@@ -116,7 +116,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
116
116
|
<carbon:user-speaker />
|
|
117
117
|
</RouterLink>
|
|
118
118
|
|
|
119
|
-
<button v-if="!isPresenter" class="icon-btn <md:hidden" @click="showEditor = !showEditor">
|
|
119
|
+
<button v-if="__DEV__ && !isPresenter" class="icon-btn <md:hidden" @click="showEditor = !showEditor">
|
|
120
120
|
<carbon:text-annotation-toggle />
|
|
121
121
|
</button>
|
|
122
122
|
</template>
|
package/internals/Play.vue
CHANGED
|
@@ -43,7 +43,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
43
43
|
<div id="page-root" ref="root" class="grid grid-cols-[1fr,max-content]" :style="themeVars">
|
|
44
44
|
<SlideContainer
|
|
45
45
|
class="w-full h-full"
|
|
46
|
-
:style="{ background: 'var(--slidev-slide-container-background, black)'}"
|
|
46
|
+
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
|
|
47
47
|
:width="isPrintMode ? windowSize.width.value : undefined"
|
|
48
48
|
:scale="slideScale"
|
|
49
49
|
@pointerdown="onClick"
|
|
@@ -56,7 +56,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__)
|
|
|
56
56
|
class="absolute bottom-0 left-0 transition duration-300 opacity-0 hover:opacity-100"
|
|
57
57
|
:class="[
|
|
58
58
|
persistNav ? 'opacity-100 right-0' : 'opacity-0 p-2',
|
|
59
|
-
isDrawing ? 'pointer-events-none': ''
|
|
59
|
+
isDrawing ? 'pointer-events-none' : '',
|
|
60
60
|
]"
|
|
61
61
|
>
|
|
62
62
|
<NavControls class="m-auto" :persist="persistNav" />
|
package/internals/Presenter.vue
CHANGED
|
@@ -4,7 +4,8 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
|
4
4
|
import { useMouse, useWindowFocus } from '@vueuse/core'
|
|
5
5
|
import { clicks, clicksTotal, currentPage, currentRoute, hasNext, nextRoute, total, useSwipeControls } from '../logic/nav'
|
|
6
6
|
import { showOverview, showPresenterCursor } from '../state'
|
|
7
|
-
import { configs,
|
|
7
|
+
import { configs, themeVars } from '../env'
|
|
8
|
+
import { sharedState } from '../state/shared'
|
|
8
9
|
import { registerShortcuts } from '../logic/shortcuts'
|
|
9
10
|
import { getSlideClass } from '../utils'
|
|
10
11
|
import { useTimer } from '../logic/utils'
|
|
@@ -72,7 +73,7 @@ onMounted(() => {
|
|
|
72
73
|
return { x, y }
|
|
73
74
|
},
|
|
74
75
|
(pos) => {
|
|
75
|
-
|
|
76
|
+
sharedState.cursor = pos
|
|
76
77
|
},
|
|
77
78
|
)
|
|
78
79
|
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { sharedState } from '../state/shared'
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
6
|
<div
|
|
7
|
-
v-if="
|
|
7
|
+
v-if="sharedState.cursor"
|
|
8
8
|
class="absolute top-0 left-0 right-0 bottom-0 pointer-events-none text-xl"
|
|
9
9
|
>
|
|
10
10
|
<ph:cursor-fill
|
|
11
11
|
class="absolute"
|
|
12
|
-
:style="{ left: `${
|
|
12
|
+
:style="{ left: `${sharedState.cursor.x}%`, top: `${sharedState.cursor.y}%` }"
|
|
13
13
|
/>
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
package/internals/Print.vue
CHANGED
|
@@ -30,7 +30,7 @@ watchEffect(() => {
|
|
|
30
30
|
<div id="page-root" class="grid grid-cols-[1fr,max-content]" :style="themeVars">
|
|
31
31
|
<PrintContainer
|
|
32
32
|
class="w-full h-full"
|
|
33
|
-
:style="{ background: 'var(--slidev-slide-container-background, black)'}"
|
|
33
|
+
:style="{ background: 'var(--slidev-slide-container-background, black)' }"
|
|
34
34
|
:width="windowSize.width.value"
|
|
35
35
|
/>
|
|
36
36
|
</div>
|
|
@@ -35,6 +35,7 @@ if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
|
35
35
|
|
|
36
36
|
const clicks = computed(() => props.clicks)
|
|
37
37
|
const navClicks = useNavClicks(clicks, props.nav.currentRoute, props.nav.currentPage)
|
|
38
|
+
const id = computed(() => `${props.route.path.toString().padStart(3, '0')}-${(clicks.value + 1).toString().padStart(2, '0')}`)
|
|
38
39
|
|
|
39
40
|
provide(injectionSlidevContext, reactive({
|
|
40
41
|
nav: { ...props.nav, ...navClicks },
|
|
@@ -44,7 +45,7 @@ provide(injectionSlidevContext, reactive({
|
|
|
44
45
|
</script>
|
|
45
46
|
|
|
46
47
|
<template>
|
|
47
|
-
<div :id="
|
|
48
|
+
<div :id="id" class="slide-container" :style="style">
|
|
48
49
|
<GlobalBottom />
|
|
49
50
|
|
|
50
51
|
<SlideWrapper
|
|
@@ -56,9 +57,9 @@ provide(injectionSlidevContext, reactive({
|
|
|
56
57
|
/>
|
|
57
58
|
<template
|
|
58
59
|
v-if="
|
|
59
|
-
(__SLIDEV_FEATURE_DRAWINGS__
|
|
60
|
-
__SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
61
|
-
DrawingPreview
|
|
60
|
+
(__SLIDEV_FEATURE_DRAWINGS__
|
|
61
|
+
|| __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
62
|
+
&& DrawingPreview
|
|
62
63
|
"
|
|
63
64
|
>
|
|
64
65
|
<DrawingPreview :page="+route.path" />
|
|
@@ -24,7 +24,7 @@ function toggleRecording() {
|
|
|
24
24
|
<button
|
|
25
25
|
v-if="currentCamera !== 'none'"
|
|
26
26
|
class="icon-btn <md:hidden"
|
|
27
|
-
:class="{'text-green-500': Boolean(showAvatar && streamCamera)}"
|
|
27
|
+
:class="{ 'text-green-500': Boolean(showAvatar && streamCamera) }"
|
|
28
28
|
title="Show camera view"
|
|
29
29
|
@click="toggleAvatar"
|
|
30
30
|
>
|
|
@@ -33,7 +33,7 @@ function toggleRecording() {
|
|
|
33
33
|
|
|
34
34
|
<button
|
|
35
35
|
class="icon-btn"
|
|
36
|
-
:class="{'text-red-500': recording}"
|
|
36
|
+
:class="{ 'text-red-500': recording }"
|
|
37
37
|
title="Recording"
|
|
38
38
|
@click="toggleRecording"
|
|
39
39
|
>
|
|
@@ -5,15 +5,15 @@ import { getFilename, mimeType, recordCamera, recorder, recordingName } from '..
|
|
|
5
5
|
import Modal from './Modal.vue'
|
|
6
6
|
import DevicesList from './DevicesList.vue'
|
|
7
7
|
|
|
8
|
-
const emit = defineEmits<{
|
|
9
|
-
(e: any): void
|
|
10
|
-
}>()
|
|
11
8
|
const props = defineProps({
|
|
12
9
|
modelValue: {
|
|
13
10
|
default: false,
|
|
14
11
|
},
|
|
15
12
|
})
|
|
16
13
|
|
|
14
|
+
const emit = defineEmits<{
|
|
15
|
+
(e: any): void
|
|
16
|
+
}>()
|
|
17
17
|
const value = useVModel(props, 'modelValue', emit)
|
|
18
18
|
|
|
19
19
|
const { startRecording } = recorder
|
package/internals/SelectList.vue
CHANGED
|
@@ -3,9 +3,6 @@ import { useVModel } from '@vueuse/core'
|
|
|
3
3
|
import type { PropType } from 'vue'
|
|
4
4
|
import type { SelectionItem } from './types'
|
|
5
5
|
|
|
6
|
-
const emit = defineEmits<{
|
|
7
|
-
(e: any): void
|
|
8
|
-
}>()
|
|
9
6
|
const props = defineProps({
|
|
10
7
|
modelValue: {
|
|
11
8
|
type: [Object, String, Number] as PropType<any>,
|
|
@@ -18,6 +15,9 @@ const props = defineProps({
|
|
|
18
15
|
},
|
|
19
16
|
})
|
|
20
17
|
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
(e: any): void
|
|
20
|
+
}>()
|
|
21
21
|
const value = useVModel(props, 'modelValue', emit, { passive: true })
|
|
22
22
|
</script>
|
|
23
23
|
|
|
@@ -8,9 +8,9 @@ import SlideContainer from './SlideContainer.vue'
|
|
|
8
8
|
import SlideWrapper from './SlideWrapper'
|
|
9
9
|
import DrawingPreview from './DrawingPreview.vue'
|
|
10
10
|
|
|
11
|
-
const emit = defineEmits([])
|
|
12
11
|
const props = defineProps<{ modelValue: boolean }>()
|
|
13
12
|
|
|
13
|
+
const emit = defineEmits([])
|
|
14
14
|
const value = useVModel(props, 'modelValue', emit)
|
|
15
15
|
|
|
16
16
|
function close() {
|
package/logic/drawings.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { computed, markRaw, nextTick, reactive, ref, watch
|
|
1
|
+
import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
|
|
2
2
|
import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'
|
|
3
3
|
import { createDrauu } from 'drauu'
|
|
4
4
|
import { toReactive, useStorage } from '@vueuse/core'
|
|
5
|
-
import {
|
|
5
|
+
import { drawingState, onPatch, patch } from '../state/drawings'
|
|
6
|
+
import { configs } from '../env'
|
|
6
7
|
import { currentPage, isPresenter } from './nav'
|
|
7
8
|
|
|
8
9
|
export const brushColors = [
|
|
@@ -29,7 +30,9 @@ export const brush = toReactive(useStorage<Brush>('slidev-drawing-brush', {
|
|
|
29
30
|
}))
|
|
30
31
|
|
|
31
32
|
const _mode = ref<DrawingMode | 'arrow'>('stylus')
|
|
33
|
+
const syncUp = computed(() => configs.drawings.syncAll || isPresenter.value)
|
|
32
34
|
let disableDump = false
|
|
35
|
+
|
|
33
36
|
export const drawingMode = computed({
|
|
34
37
|
get() {
|
|
35
38
|
return _mode.value
|
|
@@ -56,7 +59,8 @@ export const drauu = markRaw(createDrauu(drauuOptions))
|
|
|
56
59
|
|
|
57
60
|
export function clearDrauu() {
|
|
58
61
|
drauu.clear()
|
|
59
|
-
|
|
62
|
+
if (syncUp.value)
|
|
63
|
+
patch(currentPage.value, '')
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
export function updateState() {
|
|
@@ -80,24 +84,18 @@ drauu.on('changed', () => {
|
|
|
80
84
|
if (!disableDump) {
|
|
81
85
|
const dump = drauu.dump()
|
|
82
86
|
const key = currentPage.value
|
|
83
|
-
if ((drawingState[key] || '') !== dump)
|
|
84
|
-
|
|
85
|
-
drawingState.$patch({ [key]: drauu.dump() })
|
|
86
|
-
else
|
|
87
|
-
drawingState[key] = drauu.dump()
|
|
88
|
-
}
|
|
87
|
+
if ((drawingState[key] || '') !== dump && syncUp.value)
|
|
88
|
+
patch(key, drauu.dump())
|
|
89
89
|
}
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
100
|
-
}
|
|
92
|
+
onPatch((state) => {
|
|
93
|
+
disableDump = true
|
|
94
|
+
if (state[currentPage.value] != null)
|
|
95
|
+
drauu.load(state[currentPage.value] || '')
|
|
96
|
+
disableDump = false
|
|
97
|
+
updateState()
|
|
98
|
+
})
|
|
101
99
|
|
|
102
100
|
nextTick(() => {
|
|
103
101
|
watch(currentPage, () => {
|
|
@@ -105,10 +103,6 @@ nextTick(() => {
|
|
|
105
103
|
return
|
|
106
104
|
loadCanvas()
|
|
107
105
|
}, { immediate: true })
|
|
108
|
-
|
|
109
|
-
watchEffect(() => {
|
|
110
|
-
drawingState.$syncUp = configs.drawings.syncAll || isPresenter.value
|
|
111
|
-
})
|
|
112
106
|
})
|
|
113
107
|
|
|
114
108
|
drauu.on('start', () => isDrawing.value = true)
|
package/logic/nav.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { rawRoutes, router }
|
|
|
23
23
|
// force update collected elements when the route is fully resolved
|
|
24
24
|
const routeForceRefresh = ref(0)
|
|
25
25
|
nextTick(() => {
|
|
26
|
-
router.afterEach(async() => {
|
|
26
|
+
router.afterEach(async () => {
|
|
27
27
|
await nextTick()
|
|
28
28
|
routeForceRefresh.value += 1
|
|
29
29
|
})
|
|
@@ -113,7 +113,7 @@ export async function prevSlide(lastClicks = true) {
|
|
|
113
113
|
router.replace({ query: { ...route.value.query, clicks: clicksTotal.value } })
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export function go(page: number, clicks?: number) {
|
|
116
|
+
export function go(page: number | string, clicks?: number) {
|
|
117
117
|
return router.push({ path: getPath(page), query: { ...route.value.query, clicks } })
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -158,7 +158,9 @@ export async function downloadPDF() {
|
|
|
158
158
|
saveAs(
|
|
159
159
|
isString(configs.download)
|
|
160
160
|
? configs.download
|
|
161
|
-
:
|
|
161
|
+
: configs.exportFilename
|
|
162
|
+
? `${configs.exportFilename}.pdf`
|
|
163
|
+
: `${import.meta.env.BASE_URL}slidev-exported.pdf`,
|
|
162
164
|
`${configs.title}.pdf`,
|
|
163
165
|
)
|
|
164
166
|
}
|
package/logic/note.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Ref } from 'vue'
|
|
|
4
4
|
import { computed, ref, unref } from 'vue'
|
|
5
5
|
import type { SlideInfo, SlideInfoExtended } from '@slidev/types'
|
|
6
6
|
|
|
7
|
-
export interface UseSlideInfo{
|
|
7
|
+
export interface UseSlideInfo {
|
|
8
8
|
info: Ref<SlideInfoExtended | undefined>
|
|
9
9
|
update: (data: Partial<SlideInfo>) => Promise<SlideInfoExtended | void>
|
|
10
10
|
}
|
|
@@ -13,7 +13,7 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
13
13
|
if (id == null) {
|
|
14
14
|
return {
|
|
15
15
|
info: ref() as Ref<SlideInfoExtended | undefined>,
|
|
16
|
-
update: async() => {},
|
|
16
|
+
update: async () => {},
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
const url = `/@slidev/slide/${id}.json`
|
|
@@ -21,7 +21,7 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
21
21
|
|
|
22
22
|
execute()
|
|
23
23
|
|
|
24
|
-
const update = async(data: Partial<SlideInfo>) => {
|
|
24
|
+
const update = async (data: Partial<SlideInfo>) => {
|
|
25
25
|
return await fetch(
|
|
26
26
|
url,
|
|
27
27
|
{
|
|
@@ -35,10 +35,12 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
|
|
|
35
35
|
).then(r => r.json())
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (__DEV__) {
|
|
39
|
+
import.meta.hot?.on('slidev-update', (payload) => {
|
|
40
|
+
if (payload.id === id)
|
|
41
|
+
info.value = payload.data
|
|
42
|
+
})
|
|
43
|
+
}
|
|
42
44
|
|
|
43
45
|
return {
|
|
44
46
|
info,
|
|
@@ -58,7 +60,7 @@ export function useDynamicSlideInfo(id: MaybeRef<number | undefined>) {
|
|
|
58
60
|
|
|
59
61
|
return {
|
|
60
62
|
info: computed(() => get(unref(id)).info.value),
|
|
61
|
-
update: async(data: Partial<SlideInfo>, newId?: number) => {
|
|
63
|
+
update: async (data: Partial<SlideInfo>, newId?: number) => {
|
|
62
64
|
const info = get(newId ?? unref(id))
|
|
63
65
|
const newData = await info.update(data)
|
|
64
66
|
if (newData)
|
package/logic/recording.ts
CHANGED
|
@@ -110,18 +110,18 @@ export function useRecording() {
|
|
|
110
110
|
video: currentCamera.value === 'none' || recordCamera.value !== true
|
|
111
111
|
? false
|
|
112
112
|
: {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
deviceId: currentCamera.value,
|
|
114
|
+
},
|
|
115
115
|
audio: currentMic.value === 'none'
|
|
116
116
|
? false
|
|
117
117
|
: {
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
deviceId: currentMic.value,
|
|
119
|
+
},
|
|
120
120
|
})
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
watch(currentCamera, async(v) => {
|
|
124
|
+
watch(currentCamera, async (v) => {
|
|
125
125
|
if (v === 'none') {
|
|
126
126
|
closeStream(streamCamera)
|
|
127
127
|
}
|
package/modules/context.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { isDark } from '../logic/dark'
|
|
|
8
8
|
import { injectionSlidevContext } from '../constants'
|
|
9
9
|
import { useContext } from '../composables/useContext'
|
|
10
10
|
|
|
11
|
-
export type SlidevContextNavKey = 'route' | 'path' | 'total' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute'| 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide'
|
|
11
|
+
export type SlidevContextNavKey = 'route' | 'path' | 'total' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide'
|
|
12
12
|
export type SlidevContextNavClicksKey = 'clicks' | 'clicksElements' | 'clicksTotal' | 'hasNext' | 'hasPrev'
|
|
13
13
|
|
|
14
14
|
export type SlidevContextNav = Pick<typeof nav, SlidevContextNavKey>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "Presentation slides for developers",
|
|
5
5
|
"homepage": "https://sli.dev",
|
|
6
6
|
"bugs": "https://github.com/slidevjs/slidev/issues",
|
|
@@ -13,32 +13,31 @@
|
|
|
13
13
|
"funding": "https://github.com/sponsors/antfu",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@antfu/utils": "^0.5.1",
|
|
16
|
-
"@slidev/parser": "0.
|
|
17
|
-
"@slidev/types": "0.
|
|
18
|
-
"@vueuse/core": "^8.
|
|
19
|
-
"@vueuse/head": "^0.7.
|
|
16
|
+
"@slidev/parser": "0.31.1",
|
|
17
|
+
"@slidev/types": "0.31.1",
|
|
18
|
+
"@vueuse/core": "^8.4.1",
|
|
19
|
+
"@vueuse/head": "^0.7.6",
|
|
20
20
|
"@vueuse/motion": "^2.0.0-beta.18",
|
|
21
|
-
"codemirror": "^5.65.
|
|
21
|
+
"codemirror": "^5.65.3",
|
|
22
22
|
"defu": "^6.0.0",
|
|
23
23
|
"drauu": "^0.3.0",
|
|
24
24
|
"file-saver": "^2.0.5",
|
|
25
25
|
"js-base64": "^3.7.2",
|
|
26
26
|
"js-yaml": "^4.1.0",
|
|
27
27
|
"katex": "^0.15.3",
|
|
28
|
-
"mermaid": "^9.0.
|
|
28
|
+
"mermaid": "^9.0.1",
|
|
29
29
|
"monaco-editor": "^0.33.0",
|
|
30
|
-
"nanoid": "^3.3.
|
|
30
|
+
"nanoid": "^3.3.4",
|
|
31
31
|
"prettier": "^2.6.2",
|
|
32
32
|
"recordrtc": "^5.6.2",
|
|
33
33
|
"resolve": "^1.22.0",
|
|
34
34
|
"vite-plugin-windicss": "^1.8.4",
|
|
35
|
-
"vue": "^3.2.
|
|
36
|
-
"vue-router": "^4.0.
|
|
37
|
-
"vue-starport": "^0.2.
|
|
35
|
+
"vue": "^3.2.33",
|
|
36
|
+
"vue-router": "^4.0.15",
|
|
37
|
+
"vue-starport": "^0.2.10",
|
|
38
38
|
"windicss": "^3.5.1"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=14.0.0"
|
|
42
|
-
}
|
|
43
|
-
"readme": "# @slidev/client\n\n[](https://www.npmjs.com/package/@slidev/client)\n\nClient code for [Slidev](https://sli.dev). Shipped with [`@slidev/cli`](https://www.npmjs.com/package/@slidev/cli).\n\n## License\n\nMIT License © 2021 [Anthony Fu](https://github.com/antfu)\n\n"
|
|
42
|
+
}
|
|
44
43
|
}
|
package/routes.ts
CHANGED
|
@@ -19,22 +19,17 @@ export const routes: RouteRecordRaw[] = [
|
|
|
19
19
|
{ name: 'print', path: '/print', component: Print },
|
|
20
20
|
{ path: '', redirect: { path: '/1' } },
|
|
21
21
|
{ path: '/:pathMatch(.*)', redirect: { path: '/1' } },
|
|
22
|
+
{
|
|
23
|
+
name: 'presenter',
|
|
24
|
+
path: '/presenter/:no',
|
|
25
|
+
component: () => import('./internals/Presenter.vue'),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
path: '/presenter',
|
|
29
|
+
redirect: { path: '/presenter/1' },
|
|
30
|
+
},
|
|
22
31
|
]
|
|
23
32
|
|
|
24
|
-
if (import.meta.env.DEV) {
|
|
25
|
-
routes.push(
|
|
26
|
-
{
|
|
27
|
-
name: 'presenter',
|
|
28
|
-
path: '/presenter/:no',
|
|
29
|
-
component: () => import('./internals/Presenter.vue'),
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
path: '/presenter',
|
|
33
|
-
redirect: { path: '/presenter/1' },
|
|
34
|
-
},
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
33
|
export const router = createRouter({
|
|
39
34
|
history: __SLIDEV_HASH_ROUTE__ ? createWebHashHistory(import.meta.env.BASE_URL) : createWebHistory(import.meta.env.BASE_URL),
|
|
40
35
|
routes,
|
package/setup/monaco.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { createSingletonPromise } from '@antfu/utils'
|
|
|
4
4
|
import type { MonacoSetupReturn } from '@slidev/types'
|
|
5
5
|
/* __imports__ */
|
|
6
6
|
|
|
7
|
-
const setup = createSingletonPromise(async() => {
|
|
7
|
+
const setup = createSingletonPromise(async () => {
|
|
8
8
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
|
9
9
|
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
|
|
10
10
|
noUnusedLocals: false,
|
|
@@ -16,7 +16,7 @@ const setup = createSingletonPromise(async() => {
|
|
|
16
16
|
|
|
17
17
|
await Promise.all([
|
|
18
18
|
// load workers
|
|
19
|
-
(async() => {
|
|
19
|
+
(async () => {
|
|
20
20
|
const [
|
|
21
21
|
{ default: EditorWorker },
|
|
22
22
|
{ default: JsonWorker },
|
package/setup/root.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/* __imports__ */
|
|
2
|
-
import { useHead } from '@vueuse/head'
|
|
3
2
|
import { watch } from 'vue'
|
|
3
|
+
import { useHead } from '@vueuse/head'
|
|
4
|
+
import { configs } from '../env'
|
|
5
|
+
import { initSharedState, onPatch, patch } from '../state/shared'
|
|
6
|
+
import { initDrawingState } from '../state/drawings'
|
|
4
7
|
import { clicks, currentPage, getPath, isPresenter } from '../logic/nav'
|
|
5
8
|
import { router } from '../routes'
|
|
6
|
-
import { configs, serverState } from '../env'
|
|
7
9
|
|
|
8
10
|
export default function setupRoot() {
|
|
9
11
|
// @ts-expect-error injected in runtime
|
|
@@ -12,36 +14,30 @@ export default function setupRoot() {
|
|
|
12
14
|
|
|
13
15
|
/* __injections__ */
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
17
|
+
const title = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
|
|
18
|
+
useHead({ title })
|
|
19
|
+
initSharedState(`${title} - shared`)
|
|
20
|
+
initDrawingState(`${title} - drawings`)
|
|
21
|
+
|
|
22
|
+
// update shared state
|
|
23
|
+
function updateSharedState() {
|
|
24
|
+
if (isPresenter.value) {
|
|
25
|
+
patch('page', +currentPage.value)
|
|
26
|
+
patch('clicks', clicks.value)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
router.afterEach(updateSharedState)
|
|
30
|
+
watch(clicks, updateSharedState)
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
22
|
-
if (+serverState.page !== +currentPage.value || clicks.value !== serverState.clicks) {
|
|
32
|
+
onPatch((state) => {
|
|
33
|
+
if (+state.page !== +currentPage.value || clicks.value !== state.clicks) {
|
|
23
34
|
router.replace({
|
|
24
|
-
path: getPath(
|
|
35
|
+
path: getPath(state.page),
|
|
25
36
|
query: {
|
|
26
37
|
...router.currentRoute.value.query,
|
|
27
|
-
clicks:
|
|
38
|
+
clicks: state.clicks || 0,
|
|
28
39
|
},
|
|
29
40
|
})
|
|
30
41
|
}
|
|
31
|
-
}
|
|
32
|
-
function updateServerState() {
|
|
33
|
-
if (isPresenter.value) {
|
|
34
|
-
serverState.page = +currentPage.value
|
|
35
|
-
serverState.clicks = clicks.value
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// upload state to server
|
|
40
|
-
router.afterEach(updateServerState)
|
|
41
|
-
watch(clicks, updateServerState)
|
|
42
|
-
|
|
43
|
-
// sync with server state
|
|
44
|
-
router.isReady().then(() => {
|
|
45
|
-
watch(serverState, onServerStateChanged, { deep: true })
|
|
46
42
|
})
|
|
47
43
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import serverDrawingState from 'server-reactive:drawings?diff'
|
|
2
|
+
import { createSyncState } from './syncState'
|
|
3
|
+
|
|
4
|
+
export type DrawingsState = Record<number, string | undefined>
|
|
5
|
+
|
|
6
|
+
const { init, onPatch, patch, state } = createSyncState<DrawingsState>(serverDrawingState, {}, __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
7
|
+
export { init as initDrawingState, onPatch, patch, state as drawingState }
|
package/state/shared.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import serverState from 'server-reactive:nav'
|
|
2
|
+
import { createSyncState } from './syncState'
|
|
3
|
+
|
|
4
|
+
export interface SharedState {
|
|
5
|
+
page: number
|
|
6
|
+
clicks: number
|
|
7
|
+
cursor?: {
|
|
8
|
+
x: number
|
|
9
|
+
y: number
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
|
|
14
|
+
page: 1,
|
|
15
|
+
clicks: 0,
|
|
16
|
+
})
|
|
17
|
+
export { init as initSharedState, onPatch, patch, state as sharedState }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { reactive, toRaw, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function createSyncState<State extends object>(serverState: State, defaultState: State, persist = false) {
|
|
4
|
+
const onPatchCallbacks: ((state: State) => void)[] = []
|
|
5
|
+
let patching = false
|
|
6
|
+
let updating = false
|
|
7
|
+
let patchingTimeout: NodeJS.Timeout
|
|
8
|
+
let updatingTimeout: NodeJS.Timeout
|
|
9
|
+
|
|
10
|
+
const state = __DEV__
|
|
11
|
+
? reactive<State>(serverState) as State
|
|
12
|
+
: reactive<State>(defaultState) as State
|
|
13
|
+
|
|
14
|
+
function onPatch(fn: (state: State) => void) {
|
|
15
|
+
onPatchCallbacks.push(fn)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function patch<K extends keyof State>(key: K, value: State[K]) {
|
|
19
|
+
clearTimeout(patchingTimeout)
|
|
20
|
+
patching = true
|
|
21
|
+
state[key] = value
|
|
22
|
+
patchingTimeout = setTimeout(() => patching = false, 0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function onUpdate(patch: Partial<State>) {
|
|
26
|
+
if (!patching) {
|
|
27
|
+
clearTimeout(updatingTimeout)
|
|
28
|
+
updating = true
|
|
29
|
+
Object.entries(patch).forEach(([key, value]) => {
|
|
30
|
+
state[key as keyof State] = value as State[keyof State]
|
|
31
|
+
})
|
|
32
|
+
updatingTimeout = setTimeout(() => updating = false, 0)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function init(channelKey: string) {
|
|
37
|
+
let stateChannel: BroadcastChannel
|
|
38
|
+
if (!__DEV__ && !persist) {
|
|
39
|
+
stateChannel = new BroadcastChannel(channelKey)
|
|
40
|
+
stateChannel.addEventListener('message', (event: MessageEvent<Partial<State>>) => onUpdate(event.data))
|
|
41
|
+
}
|
|
42
|
+
else if (!__DEV__ && persist) {
|
|
43
|
+
window.addEventListener('storage', (event) => {
|
|
44
|
+
if (event && event.key === channelKey && event.newValue)
|
|
45
|
+
onUpdate(JSON.parse(event.newValue) as Partial<State>)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function onDrawingStateChanged() {
|
|
50
|
+
if (!persist && stateChannel && !updating)
|
|
51
|
+
stateChannel.postMessage(toRaw(state))
|
|
52
|
+
else if (persist && !updating)
|
|
53
|
+
window.localStorage.setItem(channelKey, JSON.stringify(state))
|
|
54
|
+
if (!patching)
|
|
55
|
+
onPatchCallbacks.forEach((fn: (state: State) => void) => fn(state))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
watch(state, onDrawingStateChanged, { deep: true })
|
|
59
|
+
if (!__DEV__ && persist) {
|
|
60
|
+
const serialzedState = window.localStorage.getItem(channelKey)
|
|
61
|
+
if (serialzedState)
|
|
62
|
+
onUpdate(JSON.parse(serialzedState) as Partial<State>)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { init, onPatch, patch, state }
|
|
67
|
+
}
|