@slidev/client 0.38.8 → 0.40.0
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/Tweet.vue +8 -1
- package/constants.ts +5 -0
- package/iframes/monaco/index.ts +2 -2
- package/internals/Draggable.vue +2 -2
- package/internals/{NoteViewer.vue → NoteDisplay.vue} +13 -3
- package/internals/NoteEditor.vue +3 -3
- package/internals/NoteStatic.vue +3 -3
- package/internals/NotesView.vue +76 -0
- package/internals/Presenter.vue +1 -1
- package/internals/PresenterPrint.vue +3 -3
- package/internals/SlidesShow.vue +8 -26
- package/internals/WebCamera.vue +3 -3
- package/logic/dark.ts +2 -2
- package/logic/drawings.ts +4 -4
- package/logic/nav.ts +58 -2
- package/logic/recording.ts +3 -3
- package/package.json +11 -11
- package/routes.ts +25 -16
- package/setup/main.ts +1 -1
- package/setup/monaco.ts +1 -1
- package/setup/root.ts +25 -2
- package/setup/shortcuts.ts +1 -1
- package/state/index.ts +8 -8
- package/state/shared.ts +18 -1
- package/styles/index.css +4 -0
- package/styles/layouts-base.css +5 -1
- package/styles/transitions.css +56 -0
- package/styles/vars.css +2 -0
- package/utils.ts +6 -3
package/builtin/Tweet.vue
CHANGED
|
@@ -58,7 +58,7 @@ else {
|
|
|
58
58
|
|
|
59
59
|
<template>
|
|
60
60
|
<Transform :scale="scale || 1">
|
|
61
|
-
<div ref="tweet" class="tweet">
|
|
61
|
+
<div ref="tweet" class="tweet slidev-tweet">
|
|
62
62
|
<div v-if="!loaded || tweetNotFound" class="w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50">
|
|
63
63
|
<div class="m-auto animate-pulse text-4xl">
|
|
64
64
|
<carbon:logo-twitter />
|
|
@@ -68,3 +68,10 @@ else {
|
|
|
68
68
|
</div>
|
|
69
69
|
</Transform>
|
|
70
70
|
</template>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.slidev-tweet iframe {
|
|
74
|
+
border-radius: 12px;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
package/constants.ts
CHANGED
|
@@ -19,3 +19,8 @@ export const CLASS_VCLICK_GONE = 'slidev-vclick-gone'
|
|
|
19
19
|
export const CLASS_VCLICK_HIDDEN_EXP = 'slidev-vclick-hidden-explicitly'
|
|
20
20
|
export const CLASS_VCLICK_CURRENT = 'slidev-vclick-current'
|
|
21
21
|
export const CLASS_VCLICK_PRIOR = 'slidev-vclick-prior'
|
|
22
|
+
|
|
23
|
+
export const TRUST_ORIGINS = [
|
|
24
|
+
'localhost',
|
|
25
|
+
'127.0.0.1',
|
|
26
|
+
]
|
package/iframes/monaco/index.ts
CHANGED
|
@@ -101,8 +101,8 @@ async function start() {
|
|
|
101
101
|
|
|
102
102
|
update = () => {
|
|
103
103
|
monaco.editor.setTheme(props.dark
|
|
104
|
-
? theme.dark || 'vitesse-dark'
|
|
105
|
-
: theme.light || 'vitesse-light',
|
|
104
|
+
? (theme.dark || 'vitesse-dark')
|
|
105
|
+
: (theme.light || 'vitesse-light'),
|
|
106
106
|
)
|
|
107
107
|
styleObject.innerHTML = `:root { ${props.style} }`
|
|
108
108
|
|
package/internals/Draggable.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
-
import { useDraggable,
|
|
3
|
+
import { useDraggable, useLocalStorage } from '@vueuse/core'
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
storageKey?: string
|
|
@@ -10,7 +10,7 @@ const props = defineProps<{
|
|
|
10
10
|
const el = ref<HTMLElement | null>(null)
|
|
11
11
|
const initial = props.initial ?? { x: 0, y: 0 }
|
|
12
12
|
const point = props.storageKey
|
|
13
|
-
?
|
|
13
|
+
? useLocalStorage(props.storageKey, initial)
|
|
14
14
|
: ref(initial)
|
|
15
15
|
const { style } = useDraggable(el, { initialValue: point })
|
|
16
16
|
</script>
|
|
@@ -3,6 +3,7 @@ const props = defineProps<{
|
|
|
3
3
|
class?: string
|
|
4
4
|
noteHtml?: string
|
|
5
5
|
note?: string
|
|
6
|
+
placeholder?: string
|
|
6
7
|
}>()
|
|
7
8
|
|
|
8
9
|
defineEmits(['click'])
|
|
@@ -17,10 +18,19 @@ defineEmits(['click'])
|
|
|
17
18
|
v-html="noteHtml"
|
|
18
19
|
/>
|
|
19
20
|
<div
|
|
20
|
-
v-else
|
|
21
|
+
v-else-if="note"
|
|
21
22
|
class="prose overflow-auto outline-none"
|
|
22
23
|
:class="props.class"
|
|
23
24
|
@click="$emit('click')"
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
>
|
|
26
|
+
<p v-text="note" />
|
|
27
|
+
</div>
|
|
28
|
+
<div
|
|
29
|
+
v-else
|
|
30
|
+
class="prose overflow-auto outline-none opacity-50 italic"
|
|
31
|
+
:class="props.class"
|
|
32
|
+
@click="$emit('click')"
|
|
33
|
+
>
|
|
34
|
+
<p v-text="props.placeholder || 'No notes.'" />
|
|
35
|
+
</div>
|
|
26
36
|
</template>
|
package/internals/NoteEditor.vue
CHANGED
|
@@ -3,7 +3,7 @@ import { ignorableWatch, onClickOutside } from '@vueuse/core'
|
|
|
3
3
|
import { nextTick, ref, watch } from 'vue'
|
|
4
4
|
import { currentSlideId } from '../logic/nav'
|
|
5
5
|
import { useDynamicSlideInfo } from '../logic/note'
|
|
6
|
-
import
|
|
6
|
+
import NoteDisplay from './NoteDisplay.vue'
|
|
7
7
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
class: {
|
|
@@ -60,11 +60,11 @@ onClickOutside(input, () => {
|
|
|
60
60
|
</script>
|
|
61
61
|
|
|
62
62
|
<template>
|
|
63
|
-
<
|
|
63
|
+
<NoteDisplay
|
|
64
64
|
v-if="!editing && note"
|
|
65
65
|
:class="props.class"
|
|
66
66
|
:note="note"
|
|
67
|
-
:note-html="info?.
|
|
67
|
+
:note-html="info?.noteHTML"
|
|
68
68
|
@click="switchNoteEdit"
|
|
69
69
|
/>
|
|
70
70
|
<textarea
|
package/internals/NoteStatic.vue
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import { currentRoute } from '../logic/nav'
|
|
4
|
-
import
|
|
4
|
+
import NoteDisplay from './NoteDisplay.vue'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
7
|
class?: string
|
|
8
8
|
}>()
|
|
9
9
|
|
|
10
10
|
const note = computed(() => currentRoute.value?.meta?.slide?.note)
|
|
11
|
-
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.
|
|
11
|
+
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
|
-
<
|
|
15
|
+
<NoteDisplay
|
|
16
16
|
:class="props.class"
|
|
17
17
|
:note="note"
|
|
18
18
|
:note-html="noteHtml"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useHead } from '@vueuse/head'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { useLocalStorage } from '@vueuse/core'
|
|
5
|
+
import { configs } from '../env'
|
|
6
|
+
import { sharedState } from '../state/shared'
|
|
7
|
+
import { fullscreen } from '../state'
|
|
8
|
+
import { total } from '../logic/nav'
|
|
9
|
+
import { rawRoutes } from '../routes'
|
|
10
|
+
import NoteDisplay from './NoteDisplay.vue'
|
|
11
|
+
|
|
12
|
+
const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
|
|
13
|
+
useHead({
|
|
14
|
+
title: `Notes - ${slideTitle}`,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const { isFullscreen, toggle: toggleFullscreen } = fullscreen
|
|
18
|
+
|
|
19
|
+
const fontSize = useLocalStorage('slidev-notes-font-size', 18)
|
|
20
|
+
const pageNo = computed(() => sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerPage : sharedState.page)
|
|
21
|
+
const currentRoute = computed(() => rawRoutes.find(i => i.path === `${pageNo.value}`))
|
|
22
|
+
const nextRoute = computed(() => rawRoutes.find(i => i.path === `${pageNo.value + 1}`))
|
|
23
|
+
|
|
24
|
+
function increaseFontSize() {
|
|
25
|
+
fontSize.value = fontSize.value + 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decreaseFontSize() {
|
|
29
|
+
fontSize.value = fontSize.value - 1
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div
|
|
35
|
+
class="fixed top-0 left-0 h-2px bg-teal-500 transition-all duration-500"
|
|
36
|
+
:style="{ width: `${(pageNo - 1) / total * 100}%` }"
|
|
37
|
+
/>
|
|
38
|
+
<div class="h-full flex flex-col">
|
|
39
|
+
<div
|
|
40
|
+
class="px-5 flex-auto h-full overflow-auto"
|
|
41
|
+
:style="{ fontSize: `${fontSize}px` }"
|
|
42
|
+
>
|
|
43
|
+
<NoteDisplay
|
|
44
|
+
:note="currentRoute?.meta?.slide?.note"
|
|
45
|
+
:note-html="currentRoute?.meta?.slide?.noteHTML"
|
|
46
|
+
:placeholder="`No notes for Slide ${pageNo}.`"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div v-if="nextRoute" class="px-5 py-2 max-h-60 overflow-auto border-t border-gray-400 border-opacity-20">
|
|
50
|
+
<NoteDisplay
|
|
51
|
+
class="opacity-50"
|
|
52
|
+
:note="nextRoute?.meta?.slide?.note"
|
|
53
|
+
:note-html="nextRoute?.meta?.slide?.noteHTML"
|
|
54
|
+
placeholder="No notes for next slide."
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="flex-none border-t border-gray-400 border-opacity-20">
|
|
58
|
+
<div class="flex gap-1 items-center px-6 py-3">
|
|
59
|
+
<button class="icon-btn" @click="toggleFullscreen">
|
|
60
|
+
<carbon:minimize v-if="isFullscreen" />
|
|
61
|
+
<carbon:maximize v-else />
|
|
62
|
+
</button>
|
|
63
|
+
<button class="icon-btn" @click="increaseFontSize">
|
|
64
|
+
<carbon:zoom-in />
|
|
65
|
+
</button>
|
|
66
|
+
<button class="icon-btn" @click="decreaseFontSize">
|
|
67
|
+
<carbon:zoom-out />
|
|
68
|
+
</button>
|
|
69
|
+
<div class="flex-auto" />
|
|
70
|
+
<div class="p2 text-center">
|
|
71
|
+
{{ pageNo }} / {{ total }}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
package/internals/Presenter.vue
CHANGED
|
@@ -118,7 +118,7 @@ onMounted(() => {
|
|
|
118
118
|
class="h-full w-full"
|
|
119
119
|
>
|
|
120
120
|
<SlideWrapper
|
|
121
|
-
:is="nextSlide.route?.component"
|
|
121
|
+
:is="nextSlide.route?.component as any"
|
|
122
122
|
v-model:clicks-elements="nextTabElements"
|
|
123
123
|
:clicks="nextSlide.clicks"
|
|
124
124
|
:clicks-disabled="false"
|
|
@@ -4,7 +4,7 @@ import { useStyleTag } from '@vueuse/core'
|
|
|
4
4
|
import { useHead } from '@vueuse/head'
|
|
5
5
|
import { configs, themeVars } from '../env'
|
|
6
6
|
import { rawRoutes, total } from '../logic/nav'
|
|
7
|
-
import
|
|
7
|
+
import NoteDisplay from './NoteDisplay.vue'
|
|
8
8
|
|
|
9
9
|
useStyleTag(`
|
|
10
10
|
@page {
|
|
@@ -29,7 +29,7 @@ useHead({ title: `Notes - ${configs.title}` })
|
|
|
29
29
|
const slidesWithNote = computed(() => rawRoutes
|
|
30
30
|
.slice(0, -1)
|
|
31
31
|
.map(route => route.meta?.slide)
|
|
32
|
-
.filter(slide => slide !== undefined && slide.
|
|
32
|
+
.filter(slide => slide !== undefined && slide.noteHTML !== ''))
|
|
33
33
|
</script>
|
|
34
34
|
|
|
35
35
|
<template>
|
|
@@ -55,7 +55,7 @@ const slidesWithNote = computed(() => rawRoutes
|
|
|
55
55
|
<div class="flex-auto" />
|
|
56
56
|
</div>
|
|
57
57
|
</h2>
|
|
58
|
-
<
|
|
58
|
+
<NoteDisplay :note-html="slide!.noteHTML" class="max-w-full" />
|
|
59
59
|
</div>
|
|
60
60
|
<hr v-if="index < slidesWithNote.length - 1" class="border-gray-400/50 mb-8">
|
|
61
61
|
</div>
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
3
|
-
import { clicks, currentRoute, isPresenter, nextRoute, rawRoutes } from '../logic/nav'
|
|
2
|
+
import { computed, shallowRef, watch } from 'vue'
|
|
3
|
+
import { clicks, currentRoute, isPresenter, nextRoute, rawRoutes, transition } from '../logic/nav'
|
|
4
4
|
import { getSlideClass } from '../utils'
|
|
5
|
-
import { configs } from '../env'
|
|
6
5
|
import SlideWrapper from './SlideWrapper'
|
|
7
6
|
// @ts-expect-error virtual module
|
|
8
7
|
import GlobalTop from '/@slidev/global-components/top'
|
|
@@ -24,17 +23,7 @@ const DrawingLayer = shallowRef<any>()
|
|
|
24
23
|
if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
25
24
|
import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
|
|
26
25
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const isLeaving = ref(false)
|
|
30
|
-
function onBeforeLeave() {
|
|
31
|
-
if (configs.pageTransition?.crossfade === false)
|
|
32
|
-
isLeaving.value = true
|
|
33
|
-
}
|
|
34
|
-
function onAfterLeave() {
|
|
35
|
-
if (configs.pageTransition?.crossfade === false)
|
|
36
|
-
isLeaving.value = false
|
|
37
|
-
}
|
|
26
|
+
const loadedRoutes = computed(() => rawRoutes.filter(r => r.meta?.__preloaded || r === currentRoute.value))
|
|
38
27
|
</script>
|
|
39
28
|
|
|
40
29
|
<template>
|
|
@@ -42,18 +31,11 @@ function onAfterLeave() {
|
|
|
42
31
|
<GlobalBottom />
|
|
43
32
|
|
|
44
33
|
<!-- Slides -->
|
|
45
|
-
<
|
|
46
|
-
v-for="route of
|
|
47
|
-
:key="route.path"
|
|
48
|
-
>
|
|
49
|
-
<Transition
|
|
50
|
-
v-bind="configs.pageTransition"
|
|
51
|
-
@before-leave="onBeforeLeave()"
|
|
52
|
-
@after-leave="onAfterLeave()"
|
|
53
|
-
>
|
|
34
|
+
<TransitionGroup v-bind="transition">
|
|
35
|
+
<template v-for="route of loadedRoutes" :key="route.path">
|
|
54
36
|
<SlideWrapper
|
|
55
37
|
:is="route?.component as any"
|
|
56
|
-
v-show="route === currentRoute
|
|
38
|
+
v-show="route === currentRoute"
|
|
57
39
|
:clicks="route === currentRoute ? clicks : 0"
|
|
58
40
|
:clicks-elements="route.meta?.__clicksElements || []"
|
|
59
41
|
:clicks-disabled="false"
|
|
@@ -61,8 +43,8 @@ function onAfterLeave() {
|
|
|
61
43
|
:route="route"
|
|
62
44
|
:context="context"
|
|
63
45
|
/>
|
|
64
|
-
</
|
|
65
|
-
</
|
|
46
|
+
</template>
|
|
47
|
+
</TransitionGroup>
|
|
66
48
|
|
|
67
49
|
<!-- Global Top -->
|
|
68
50
|
<GlobalTop />
|
package/internals/WebCamera.vue
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useDraggable, useEventListener,
|
|
2
|
+
import { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'
|
|
3
3
|
import { computed, onMounted, ref, watchEffect } from 'vue'
|
|
4
4
|
import { currentCamera } from '../state'
|
|
5
5
|
import { recorder } from '../logic/recording'
|
|
6
6
|
|
|
7
|
-
const size =
|
|
8
|
-
const position =
|
|
7
|
+
const size = useLocalStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))
|
|
8
|
+
const position = useLocalStorage('slidev-webcam-pos', {
|
|
9
9
|
x: window.innerWidth - size.value - 30,
|
|
10
10
|
y: window.innerHeight - size.value - 30,
|
|
11
11
|
}, undefined, { deep: true })
|
package/logic/dark.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { isClient,
|
|
1
|
+
import { isClient, useLocalStorage, usePreferredDark, useToggle } from '@vueuse/core'
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { configs } from '../env'
|
|
4
4
|
|
|
5
5
|
const preferredDark = usePreferredDark()
|
|
6
|
-
const store =
|
|
6
|
+
const store = useLocalStorage('slidev-color-schema', 'auto')
|
|
7
7
|
|
|
8
8
|
export const isColorSchemaConfigured = computed(() => configs.colorSchema !== 'auto')
|
|
9
9
|
export const isDark = computed<boolean>({
|
package/logic/drawings.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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
|
-
import { toReactive,
|
|
4
|
+
import { toReactive, useLocalStorage } from '@vueuse/core'
|
|
5
5
|
import { drawingState, onPatch, patch } from '../state/drawings'
|
|
6
6
|
import { configs } from '../env'
|
|
7
7
|
import { currentPage, isPresenter } from './nav'
|
|
@@ -16,14 +16,14 @@ export const brushColors = [
|
|
|
16
16
|
'#000000',
|
|
17
17
|
]
|
|
18
18
|
|
|
19
|
-
export const drawingEnabled =
|
|
20
|
-
export const drawingPinned =
|
|
19
|
+
export const drawingEnabled = useLocalStorage('slidev-drawing-enabled', false)
|
|
20
|
+
export const drawingPinned = useLocalStorage('slidev-drawing-pinned', false)
|
|
21
21
|
export const canUndo = ref(false)
|
|
22
22
|
export const canRedo = ref(false)
|
|
23
23
|
export const canClear = ref(false)
|
|
24
24
|
export const isDrawing = ref(false)
|
|
25
25
|
|
|
26
|
-
export const brush = toReactive(
|
|
26
|
+
export const brush = toReactive(useLocalStorage<Brush>('slidev-drawing-brush', {
|
|
27
27
|
color: brushColors[0],
|
|
28
28
|
size: 4,
|
|
29
29
|
mode: 'stylus',
|
package/logic/nav.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Ref } from 'vue'
|
|
1
|
+
import type { Ref, TransitionGroupProps } from 'vue'
|
|
2
2
|
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
-
import { computed, nextTick, ref } from 'vue'
|
|
3
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
4
4
|
import type { TocItem } from '@slidev/types'
|
|
5
5
|
import { SwipeDirection, isString, timestamp, usePointerSwipe } from '@vueuse/core'
|
|
6
6
|
import { rawRoutes, router } from '../routes'
|
|
@@ -18,6 +18,7 @@ nextTick(() => {
|
|
|
18
18
|
routeForceRefresh.value += 1
|
|
19
19
|
})
|
|
20
20
|
})
|
|
21
|
+
export const navDirection = ref(0)
|
|
21
22
|
|
|
22
23
|
export const route = computed(() => router.currentRoute.value)
|
|
23
24
|
|
|
@@ -25,6 +26,7 @@ export const isPrintMode = computed(() => route.value.query.print !== undefined)
|
|
|
25
26
|
export const isPrintWithClicks = computed(() => route.value.query.print === 'clicks')
|
|
26
27
|
export const isEmbedded = computed(() => route.value.query.embedded !== undefined)
|
|
27
28
|
export const isPresenter = computed(() => route.value.path.startsWith('/presenter'))
|
|
29
|
+
export const isNotesViewer = computed(() => route.value.path.startsWith('/notes'))
|
|
28
30
|
export const isClicksDisabled = computed(() => isPrintMode.value && !isPrintWithClicks.value)
|
|
29
31
|
export const presenterPassword = computed(() => route.value.query.password)
|
|
30
32
|
export const showPresenter = computed(() => !isPresenter.value && (!configs.remote || presenterPassword.value === configs.remote))
|
|
@@ -40,6 +42,7 @@ export const currentSlideId = computed(() => currentRoute.value?.meta?.slide?.id
|
|
|
40
42
|
export const currentLayout = computed(() => currentRoute.value?.meta?.layout || (currentPage.value === 1 ? 'cover' : 'default'))
|
|
41
43
|
|
|
42
44
|
export const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length, currentPage.value + 1)}`))
|
|
45
|
+
export const prevRoute = computed(() => rawRoutes.find(i => i.path === `${Math.max(1, currentPage.value - 1)}`))
|
|
43
46
|
|
|
44
47
|
export const clicksElements = computed<HTMLElement[]>(() => {
|
|
45
48
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -75,6 +78,12 @@ export const rawTree = computed(() => rawRoutes
|
|
|
75
78
|
export const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value, currentRoute.value))
|
|
76
79
|
export const tree = computed(() => filterTree(treeWithActiveStatuses.value))
|
|
77
80
|
|
|
81
|
+
export const transition = computed(() => getCurrentTransition(navDirection.value, currentRoute.value, prevRoute.value))
|
|
82
|
+
|
|
83
|
+
watch(currentRoute, (next, prev) => {
|
|
84
|
+
navDirection.value = Number(next?.path) - Number(prev?.path)
|
|
85
|
+
})
|
|
86
|
+
|
|
78
87
|
export function next() {
|
|
79
88
|
if (clicksTotal.value <= clicks.value)
|
|
80
89
|
nextSlide()
|
|
@@ -220,3 +229,50 @@ export function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
|
220
229
|
children: filterTree(item.children, level + 1),
|
|
221
230
|
}))
|
|
222
231
|
}
|
|
232
|
+
|
|
233
|
+
const transitionResolveMap: Record<string, string | undefined> = {
|
|
234
|
+
'slide-left': 'slide-left | slide-right',
|
|
235
|
+
'slide-right': 'slide-right | slide-left',
|
|
236
|
+
'slide-up': 'slide-up | slide-down',
|
|
237
|
+
'slide-down': 'slide-down | slide-up',
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function resolveTransition(transition?: string | TransitionGroupProps, isBackward = false): TransitionGroupProps | undefined {
|
|
241
|
+
if (!transition)
|
|
242
|
+
return undefined
|
|
243
|
+
if (typeof transition === 'string') {
|
|
244
|
+
transition = {
|
|
245
|
+
name: transition,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!transition.name)
|
|
250
|
+
return undefined
|
|
251
|
+
|
|
252
|
+
let name = transition.name.includes('|')
|
|
253
|
+
? transition.name
|
|
254
|
+
: (transitionResolveMap[transition.name] || transition.name)
|
|
255
|
+
|
|
256
|
+
if (name.includes('|')) {
|
|
257
|
+
const [forward, backward] = name.split('|').map(i => i.trim())
|
|
258
|
+
name = isBackward ? backward : forward
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!name)
|
|
262
|
+
return undefined
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
...transition,
|
|
266
|
+
name,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function getCurrentTransition(direction: number, currentRoute?: RouteRecordRaw, prevRoute?: RouteRecordRaw) {
|
|
271
|
+
let transition = direction > 0
|
|
272
|
+
? prevRoute?.meta?.transition
|
|
273
|
+
: currentRoute?.meta?.transition
|
|
274
|
+
if (!transition)
|
|
275
|
+
transition = configs.transition
|
|
276
|
+
|
|
277
|
+
return resolveTransition(transition, direction < 0)
|
|
278
|
+
}
|
package/logic/recording.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Ref } from 'vue'
|
|
2
2
|
import { nextTick, ref, shallowRef, watch } from 'vue'
|
|
3
|
-
import { useDevicesList, useEventListener,
|
|
3
|
+
import { useDevicesList, useEventListener, useLocalStorage } from '@vueuse/core'
|
|
4
4
|
import { isTruthy } from '@antfu/utils'
|
|
5
5
|
import type RecorderType from 'recordrtc'
|
|
6
6
|
import type { Options as RecorderOptions } from 'recordrtc'
|
|
@@ -11,7 +11,7 @@ type MimeType = Defined<RecorderOptions['mimeType']>
|
|
|
11
11
|
|
|
12
12
|
export const recordingName = ref('')
|
|
13
13
|
export const recordCamera = ref(true)
|
|
14
|
-
export const mimeType =
|
|
14
|
+
export const mimeType = useLocalStorage<MimeType>('slidev-record-mimetype', 'video/webm')
|
|
15
15
|
|
|
16
16
|
export const mimeExtMap: Record<string, string> = {
|
|
17
17
|
'video/webm': 'webm',
|
|
@@ -107,7 +107,7 @@ export function useRecording() {
|
|
|
107
107
|
return
|
|
108
108
|
|
|
109
109
|
streamCamera.value = await navigator.mediaDevices.getUserMedia({
|
|
110
|
-
video: currentCamera.value === 'none' || recordCamera.value !== true
|
|
110
|
+
video: (currentCamera.value === 'none' || recordCamera.value !== true)
|
|
111
111
|
? false
|
|
112
112
|
: {
|
|
113
113
|
deviceId: currentCamera.value,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "Presentation slides for developers",
|
|
5
5
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@antfu/utils": "^0.7.2",
|
|
19
|
-
"@unocss/reset": "^0.
|
|
20
|
-
"@vueuse/core": "^9.
|
|
21
|
-
"@vueuse/head": "^1.0.
|
|
22
|
-
"@vueuse/math": "^9.
|
|
23
|
-
"@vueuse/motion": "^2.0.0-beta.
|
|
19
|
+
"@unocss/reset": "^0.49.4",
|
|
20
|
+
"@vueuse/core": "^9.12.0",
|
|
21
|
+
"@vueuse/head": "^1.0.24",
|
|
22
|
+
"@vueuse/math": "^9.12.0",
|
|
23
|
+
"@vueuse/motion": "^2.0.0-beta.27",
|
|
24
24
|
"codemirror": "^5.65.5",
|
|
25
25
|
"defu": "^6.1.2",
|
|
26
26
|
"drauu": "^0.3.2",
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"prettier": "^2.8.3",
|
|
35
35
|
"recordrtc": "^5.6.2",
|
|
36
36
|
"resolve": "^1.22.1",
|
|
37
|
-
"unocss": "^0.
|
|
37
|
+
"unocss": "^0.49.4",
|
|
38
38
|
"vite-plugin-windicss": "^1.8.10",
|
|
39
|
-
"vue": "^3.2.
|
|
39
|
+
"vue": "^3.2.47",
|
|
40
40
|
"vue-router": "^4.1.6",
|
|
41
41
|
"vue-starport": "^0.3.0",
|
|
42
42
|
"windicss": "^3.5.6",
|
|
43
|
-
"@slidev/parser": "0.
|
|
44
|
-
"@slidev/types": "0.
|
|
43
|
+
"@slidev/parser": "0.40.0",
|
|
44
|
+
"@slidev/types": "0.40.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"vite": "^4.
|
|
47
|
+
"vite": "^4.1.1"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/routes.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { RouteRecordRaw } from 'vue-router'
|
|
1
|
+
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
|
|
2
2
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
3
|
+
import type { TransitionGroupProps } from 'vue'
|
|
3
4
|
import Play from './internals/Play.vue'
|
|
4
5
|
import Print from './internals/Print.vue'
|
|
5
6
|
// @ts-expect-error missing types
|
|
6
7
|
import _rawRoutes from '/@slidev/routes'
|
|
8
|
+
// @ts-expect-error missing types
|
|
7
9
|
import _configs from '/@slidev/configs'
|
|
8
10
|
|
|
9
11
|
export const rawRoutes = _rawRoutes as RouteRecordRaw[]
|
|
@@ -23,24 +25,31 @@ export const routes: RouteRecordRaw[] = [
|
|
|
23
25
|
]
|
|
24
26
|
|
|
25
27
|
if (__SLIDEV_FEATURE_PRESENTER__) {
|
|
28
|
+
function passwordGuard(to: RouteLocationNormalized) {
|
|
29
|
+
if (!_configs.remote || _configs.remote === to.query.password)
|
|
30
|
+
return true
|
|
31
|
+
if (_configs.remote && to.query.password === undefined) {
|
|
32
|
+
// eslint-disable-next-line no-alert
|
|
33
|
+
const password = prompt('Enter password')
|
|
34
|
+
if (_configs.remote === password)
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
if (to.params.no)
|
|
38
|
+
return { path: `/${to.params.no}` }
|
|
39
|
+
return { path: '' }
|
|
40
|
+
}
|
|
26
41
|
routes.push({ path: '/presenter/print', component: () => import('./internals/PresenterPrint.vue') })
|
|
42
|
+
routes.push({
|
|
43
|
+
name: 'notes',
|
|
44
|
+
path: '/notes',
|
|
45
|
+
component: () => import('./internals/NotesView.vue'),
|
|
46
|
+
beforeEnter: passwordGuard,
|
|
47
|
+
})
|
|
27
48
|
routes.push({
|
|
28
49
|
name: 'presenter',
|
|
29
50
|
path: '/presenter/:no',
|
|
30
51
|
component: () => import('./internals/Presenter.vue'),
|
|
31
|
-
beforeEnter:
|
|
32
|
-
if (!_configs.remote || _configs.remote === to.query.password)
|
|
33
|
-
return true
|
|
34
|
-
if (_configs.remote && to.query.password === undefined) {
|
|
35
|
-
// eslint-disable-next-line no-alert
|
|
36
|
-
const password = prompt('Enter password')
|
|
37
|
-
if (_configs.remote === password)
|
|
38
|
-
return true
|
|
39
|
-
}
|
|
40
|
-
if (to.params.no)
|
|
41
|
-
return { path: `/${to.params.no}` }
|
|
42
|
-
return { path: '' }
|
|
43
|
-
},
|
|
52
|
+
beforeEnter: passwordGuard,
|
|
44
53
|
})
|
|
45
54
|
routes.push({
|
|
46
55
|
path: '/presenter',
|
|
@@ -63,14 +72,14 @@ declare module 'vue-router' {
|
|
|
63
72
|
start: number
|
|
64
73
|
end: number
|
|
65
74
|
note?: string
|
|
66
|
-
|
|
75
|
+
noteHTML?: string
|
|
67
76
|
id: number
|
|
68
77
|
no: number
|
|
69
78
|
filepath: string
|
|
70
79
|
title?: string
|
|
71
80
|
level?: number
|
|
72
81
|
}
|
|
73
|
-
|
|
82
|
+
transition?: string | TransitionGroupProps | undefined
|
|
74
83
|
// private fields
|
|
75
84
|
__clicksElements: HTMLElement[]
|
|
76
85
|
__preloaded?: boolean
|
package/setup/main.ts
CHANGED
|
@@ -17,7 +17,7 @@ export default function setupMain(context: AppContext) {
|
|
|
17
17
|
context.app.use(StarportPlugin({ keepAlive: true }))
|
|
18
18
|
|
|
19
19
|
// @ts-expect-error inject in runtime
|
|
20
|
-
// eslint-disable-next-line
|
|
20
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
21
21
|
const injection_arg = context
|
|
22
22
|
|
|
23
23
|
/* __injections__ */
|
package/setup/monaco.ts
CHANGED
|
@@ -49,7 +49,7 @@ const setup = createSingletonPromise(async () => {
|
|
|
49
49
|
])
|
|
50
50
|
|
|
51
51
|
// @ts-expect-error injected in runtime
|
|
52
|
-
// eslint-disable-next-line
|
|
52
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
53
53
|
const injection_arg = monaco
|
|
54
54
|
// eslint-disable-next-line prefer-const
|
|
55
55
|
let injection_return: MonacoSetupReturn = {}
|
package/setup/root.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/* __imports__ */
|
|
2
2
|
import { watch } from 'vue'
|
|
3
3
|
import { useHead, useHtmlAttrs } from '@vueuse/head'
|
|
4
|
+
import { nanoid } from 'nanoid'
|
|
4
5
|
import { configs } from '../env'
|
|
5
6
|
import { initSharedState, onPatch, patch } from '../state/shared'
|
|
6
7
|
import { initDrawingState } from '../state/drawings'
|
|
7
|
-
import { clicks, currentPage, getPath, isPresenter } from '../logic/nav'
|
|
8
|
+
import { clicks, currentPage, getPath, isNotesViewer, isPresenter } from '../logic/nav'
|
|
8
9
|
import { router } from '../routes'
|
|
10
|
+
import { TRUST_ORIGINS } from '../constants'
|
|
9
11
|
|
|
10
12
|
export default function setupRoot() {
|
|
11
13
|
// @ts-expect-error injected in runtime
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
const injection_arg = undefined
|
|
14
16
|
|
|
15
17
|
/* __injections__ */
|
|
@@ -20,17 +22,38 @@ export default function setupRoot() {
|
|
|
20
22
|
initSharedState(`${title} - shared`)
|
|
21
23
|
initDrawingState(`${title} - drawings`)
|
|
22
24
|
|
|
25
|
+
const id = `${location.origin}_${nanoid()}`
|
|
26
|
+
|
|
23
27
|
// update shared state
|
|
24
28
|
function updateSharedState() {
|
|
29
|
+
if (isNotesViewer.value)
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
// we allow Presenter mode, or Viewer mode from trusted origins to update the shared state
|
|
33
|
+
if (!isPresenter.value && !TRUST_ORIGINS.includes(location.host.split(':')[0]))
|
|
34
|
+
return
|
|
35
|
+
|
|
25
36
|
if (isPresenter.value) {
|
|
26
37
|
patch('page', +currentPage.value)
|
|
27
38
|
patch('clicks', clicks.value)
|
|
28
39
|
}
|
|
40
|
+
else {
|
|
41
|
+
patch('viewerPage', +currentPage.value)
|
|
42
|
+
patch('viewerClicks', clicks.value)
|
|
43
|
+
}
|
|
44
|
+
patch('lastUpdate', {
|
|
45
|
+
id,
|
|
46
|
+
type: isPresenter.value ? 'presenter' : 'viewer',
|
|
47
|
+
time: new Date().getTime(),
|
|
48
|
+
})
|
|
29
49
|
}
|
|
30
50
|
router.afterEach(updateSharedState)
|
|
31
51
|
watch(clicks, updateSharedState)
|
|
32
52
|
|
|
33
53
|
onPatch((state) => {
|
|
54
|
+
const routePath = router.currentRoute.value.path
|
|
55
|
+
if (!routePath.match(/^\/(\d+|presenter)\/?/))
|
|
56
|
+
return
|
|
34
57
|
if (+state.page !== +currentPage.value || clicks.value !== state.clicks) {
|
|
35
58
|
router.replace({
|
|
36
59
|
path: getPath(state.page),
|
package/setup/shortcuts.ts
CHANGED
|
@@ -11,7 +11,7 @@ export default function setupShortcuts() {
|
|
|
11
11
|
const { escape, space, shift, left, right, up, down, enter, d, g, o } = magicKeys
|
|
12
12
|
|
|
13
13
|
// @ts-expect-error injected in runtime
|
|
14
|
-
// eslint-disable-next-line
|
|
14
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
15
15
|
const injection_arg: NavOperations = {
|
|
16
16
|
next,
|
|
17
17
|
prev,
|
package/state/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen,
|
|
1
|
+
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
3
|
import { slideAspect } from '../env'
|
|
4
4
|
|
|
@@ -20,13 +20,13 @@ export const activeElement = useActiveElement()
|
|
|
20
20
|
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || '') || activeElement.value?.classList.contains('CodeMirror-code'))
|
|
21
21
|
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))
|
|
22
22
|
|
|
23
|
-
export const currentCamera =
|
|
24
|
-
export const currentMic =
|
|
25
|
-
export const slideScale =
|
|
23
|
+
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default')
|
|
24
|
+
export const currentMic = useLocalStorage<string>('slidev-mic', 'default')
|
|
25
|
+
export const slideScale = useLocalStorage<number>('slidev-scale', 0)
|
|
26
26
|
|
|
27
|
-
export const showOverview =
|
|
28
|
-
export const showPresenterCursor =
|
|
29
|
-
export const showEditor =
|
|
30
|
-
export const editorWidth =
|
|
27
|
+
export const showOverview = useLocalStorage('slidev-show-overview', false)
|
|
28
|
+
export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true)
|
|
29
|
+
export const showEditor = useLocalStorage('slidev-show-editor', false)
|
|
30
|
+
export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 100)
|
|
31
31
|
|
|
32
32
|
export const toggleOverview = useToggle(showOverview)
|
package/state/shared.ts
CHANGED
|
@@ -8,10 +8,27 @@ export interface SharedState {
|
|
|
8
8
|
x: number
|
|
9
9
|
y: number
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
viewerPage: number
|
|
13
|
+
viewerClicks: number
|
|
14
|
+
|
|
15
|
+
lastUpdate?: {
|
|
16
|
+
id: string
|
|
17
|
+
type: 'presenter' | 'viewer'
|
|
18
|
+
time: number
|
|
19
|
+
}
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
|
|
14
23
|
page: 1,
|
|
15
24
|
clicks: 0,
|
|
25
|
+
viewerPage: 1,
|
|
26
|
+
viewerClicks: 0,
|
|
16
27
|
})
|
|
17
|
-
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
init as initSharedState,
|
|
31
|
+
onPatch,
|
|
32
|
+
patch,
|
|
33
|
+
state as sharedState,
|
|
34
|
+
}
|
package/styles/index.css
CHANGED
package/styles/layouts-base.css
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Sliding */
|
|
2
|
+
.slide-left-enter-active,
|
|
3
|
+
.slide-left-leave-active,
|
|
4
|
+
.slide-right-enter-active,
|
|
5
|
+
.slide-right-leave-active,
|
|
6
|
+
.slide-up-enter-active,
|
|
7
|
+
.slide-up-leave-active,
|
|
8
|
+
.slide-down-enter-active,
|
|
9
|
+
.slide-down-leave-active {
|
|
10
|
+
transition: all var(--slidev-transition-duration) ease;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.slide-left-enter-from,
|
|
14
|
+
.slide-right-leave-to {
|
|
15
|
+
transform: translateX(100%);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.slide-left-leave-to,
|
|
19
|
+
.slide-right-enter-from {
|
|
20
|
+
transform: translateX(-100%);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.slide-up-enter-from,
|
|
24
|
+
.slide-down-leave-to {
|
|
25
|
+
transform: translateY(100%);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.slide-up-leave-to,
|
|
29
|
+
.slide-down-enter-from {
|
|
30
|
+
transform: translateY(-100%);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Fading */
|
|
34
|
+
.fade-enter-active,
|
|
35
|
+
.fade-leave-active {
|
|
36
|
+
transition: opacity var(--slidev-transition-duration) ease;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.fade-enter-from,
|
|
40
|
+
.fade-leave-to {
|
|
41
|
+
opacity: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.fade-out-leave-active {
|
|
45
|
+
transition: opacity calc(var(--slidev-transition-duration) * 0.6) ease-out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.fade-out-enter-active {
|
|
49
|
+
transition: opacity calc(var(--slidev-transition-duration) * 0.8) ease-in;
|
|
50
|
+
transition-delay: calc(var(--slidev-transition-duration) * 0.6);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.fade-out-enter-from,
|
|
54
|
+
.fade-out-leave-to {
|
|
55
|
+
opacity: 0;
|
|
56
|
+
}
|
package/styles/vars.css
CHANGED
package/utils.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from 'vue-router'
|
|
2
2
|
|
|
3
|
-
export function getSlideClass(route?: RouteRecordRaw) {
|
|
3
|
+
export function getSlideClass(route?: RouteRecordRaw, extra = '') {
|
|
4
|
+
const classes = ['slidev-page', extra]
|
|
5
|
+
|
|
4
6
|
const no = route?.meta?.slide?.no
|
|
5
7
|
if (no != null)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
classes.push(`slidev-page-${no}`)
|
|
9
|
+
|
|
10
|
+
return classes.filter(Boolean).join(' ')
|
|
8
11
|
}
|