@slidev/client 0.29.2 → 0.30.2
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/App.vue +1 -0
- package/builtin/Link.vue +26 -0
- package/builtin/Monaco.vue +4 -4
- package/builtin/PlantUml.vue +4 -4
- package/builtin/Toc.vue +8 -3
- package/builtin/TocList.vue +6 -2
- package/builtin/Tweet.vue +1 -1
- package/builtin/Youtube.vue +1 -0
- package/composables/useContext.ts +29 -0
- package/composables/useNav.ts +43 -0
- package/composables/useNavClicks.ts +37 -0
- package/constants.ts +2 -0
- package/internals/Print.vue +63 -0
- package/internals/PrintContainer.vue +54 -0
- package/internals/PrintSlide.vue +21 -0
- package/internals/PrintSlideClick.vue +69 -0
- package/logic/drawings.ts +2 -2
- package/logic/nav.ts +8 -5
- package/logic/recording.ts +9 -1
- package/modules/context.ts +18 -25
- package/package.json +13 -11
- package/routes.ts +2 -0
- package/setup/main.ts +2 -0
- package/setup/prettier.ts +0 -1
package/App.vue
CHANGED
package/builtin/Link.vue
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Create a link in the presentation
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
|
|
6
|
+
<Link :to="5" >Go to slide 5</Link>
|
|
7
|
+
|
|
8
|
+
<Link :to="5" title="Go to slide 5" />
|
|
9
|
+
-->
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { isPrintMode } from '../logic/nav'
|
|
12
|
+
|
|
13
|
+
defineProps<{
|
|
14
|
+
to: number | string
|
|
15
|
+
title?: string
|
|
16
|
+
}>()
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<RouterLink v-if="!isPrintMode && title" :to="to" @click="$event.target.blur()" v-html="title" />
|
|
21
|
+
<RouterLink v-else-if="!isPrintMode && !title" :to="to" @click="$event.target.blur()">
|
|
22
|
+
<slot />
|
|
23
|
+
</RouterLink>
|
|
24
|
+
<a v-else-if="isPrintMode && title" :href="'#' + to" v-html="title" />
|
|
25
|
+
<a v-else :href="'#' + to"><slot /></a>
|
|
26
|
+
</template>
|
package/builtin/Monaco.vue
CHANGED
|
@@ -11,10 +11,6 @@ const your_code = 'here'
|
|
|
11
11
|
Learn more: https://sli.dev/guide/syntax.html#monaco-editor
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
|
-
<template>
|
|
15
|
-
<iframe ref="iframe" class="text-base w-full rounded" :style="{ height }" />
|
|
16
|
-
</template>
|
|
17
|
-
|
|
18
14
|
<script setup lang="ts">
|
|
19
15
|
import { computed, onMounted, ref, watchEffect } from 'vue'
|
|
20
16
|
import { useEventListener } from '@vueuse/core'
|
|
@@ -135,3 +131,7 @@ watchEffect(() => {
|
|
|
135
131
|
postStyle()
|
|
136
132
|
})
|
|
137
133
|
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<iframe ref="iframe" class="text-base w-full rounded" :style="{ height }" />
|
|
137
|
+
</template>
|
package/builtin/PlantUml.vue
CHANGED
|
@@ -10,10 +10,6 @@ Alice -> Bob : Hello!
|
|
|
10
10
|
@enduml
|
|
11
11
|
```
|
|
12
12
|
-->
|
|
13
|
-
<template>
|
|
14
|
-
<img alt="PlantUML diagram" :src="uri" :style="{scale}">
|
|
15
|
-
</template>
|
|
16
|
-
|
|
17
13
|
<script setup lang="ts">
|
|
18
14
|
|
|
19
15
|
import { computed } from 'vue'
|
|
@@ -27,3 +23,7 @@ const props = defineProps<{
|
|
|
27
23
|
const uri = computed(() => `${props.server}/svg/${props.code}`)
|
|
28
24
|
|
|
29
25
|
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<img alt="PlantUML diagram" :src="uri" :style="{scale}">
|
|
29
|
+
</template>
|
package/builtin/Toc.vue
CHANGED
|
@@ -8,9 +8,11 @@ Usage:
|
|
|
8
8
|
<Toc columns='2' maxDepth='3' mode='onlySiblings'/>
|
|
9
9
|
-->
|
|
10
10
|
<script setup lang='ts'>
|
|
11
|
-
import { computed } from 'vue'
|
|
11
|
+
import { computed, inject } from 'vue'
|
|
12
12
|
import type { TocItem } from '../logic/nav'
|
|
13
|
-
import {
|
|
13
|
+
import { injectionSlidevContext } from '../constants'
|
|
14
|
+
|
|
15
|
+
const $slidev = inject(injectionSlidevContext)
|
|
14
16
|
|
|
15
17
|
const props = withDefaults(
|
|
16
18
|
defineProps<{
|
|
@@ -69,7 +71,10 @@ function filterOnlySiblings(tree: TocItem[]): TocItem[] {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const toc = computed(() => {
|
|
72
|
-
|
|
74
|
+
const tree = $slidev?.nav.tree
|
|
75
|
+
if (!tree)
|
|
76
|
+
return []
|
|
77
|
+
let tocTree = filterTreeDepth(tree)
|
|
73
78
|
if (props.mode === 'onlyCurrentTree')
|
|
74
79
|
tocTree = filterOnlyCurrentTree(tocTree)
|
|
75
80
|
else if (props.mode === 'onlySiblings')
|
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<{
|
|
@@ -29,8 +31,10 @@ const classes = computed(() => {
|
|
|
29
31
|
<template>
|
|
30
32
|
<ol v-if="list && list.length > 0" :class="classes">
|
|
31
33
|
<li v-for="item in list" :key="item.path" :class="['slidev-toc-item', {'slidev-toc-item-active': item.active}, {'slidev-toc-item-parent-active': item.activeParent}]">
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
+
<Link :to="item.path">
|
|
35
|
+
<Titles :no="item.path" />
|
|
36
|
+
</Link>
|
|
37
|
+
<TocList v-if="item.children.length > 0" :level="level + 1" :list="item.children" :list-class="listClass" />
|
|
34
38
|
</li>
|
|
35
39
|
</ol>
|
|
36
40
|
</template>
|
package/builtin/Tweet.vue
CHANGED
|
@@ -55,7 +55,7 @@ else {
|
|
|
55
55
|
|
|
56
56
|
<template>
|
|
57
57
|
<Transform :scale="scale || 1">
|
|
58
|
-
<div ref="tweet">
|
|
58
|
+
<div ref="tweet" class="tweet" data-waitfor="iframe">
|
|
59
59
|
<div v-if="!loaded" class="w-30 h-30 my-10px bg-gray-400 bg-opacity-10 rounded-lg flex opacity-50">
|
|
60
60
|
<div class="m-auto animate-pulse text-4xl">
|
|
61
61
|
<carbon:logo-twitter />
|
package/builtin/Youtube.vue
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComputedRef, WritableComputedRef } from 'vue'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
4
|
+
import { downloadPDF, next, nextSlide, openInEditor, prev, prevSlide } from '../logic/nav'
|
|
5
|
+
import { configs } from '../env'
|
|
6
|
+
import { useNav } from './useNav'
|
|
7
|
+
import { useNavClicks } from './useNavClicks'
|
|
8
|
+
|
|
9
|
+
export function useContext(
|
|
10
|
+
route: ComputedRef<RouteLocationNormalizedLoaded>,
|
|
11
|
+
clicks: WritableComputedRef<number>,
|
|
12
|
+
) {
|
|
13
|
+
const nav = useNav(route)
|
|
14
|
+
const navClicks = useNavClicks(clicks, nav.currentRoute, nav.currentPage)
|
|
15
|
+
return {
|
|
16
|
+
nav: {
|
|
17
|
+
...nav,
|
|
18
|
+
...navClicks,
|
|
19
|
+
downloadPDF,
|
|
20
|
+
next,
|
|
21
|
+
nextSlide,
|
|
22
|
+
openInEditor,
|
|
23
|
+
prev,
|
|
24
|
+
prevSlide,
|
|
25
|
+
},
|
|
26
|
+
configs,
|
|
27
|
+
themeConfigs: computed(() => configs.themeConfig),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
|
4
|
+
import type { TocItem } from '../logic/nav'
|
|
5
|
+
import { addToTree, filterTree, getPath, getTreeWithActiveStatuses } from '../logic/nav'
|
|
6
|
+
import { rawRoutes } from '../routes'
|
|
7
|
+
|
|
8
|
+
export function useNav(route: ComputedRef<RouteLocationNormalizedLoaded>) {
|
|
9
|
+
const path = computed(() => route.value.path)
|
|
10
|
+
const total = computed(() => rawRoutes.length - 1)
|
|
11
|
+
|
|
12
|
+
const currentPage = computed(() => parseInt(path.value.split(/\//g).slice(-1)[0]) || 1)
|
|
13
|
+
const currentPath = computed(() => getPath(currentPage.value))
|
|
14
|
+
const currentRoute = computed(() => rawRoutes.find(i => i.path === `${currentPage.value}`))
|
|
15
|
+
const currentSlideId = computed(() => currentRoute.value?.meta?.slide?.id)
|
|
16
|
+
const currentLayout = computed(() => currentRoute.value?.meta?.layout)
|
|
17
|
+
|
|
18
|
+
const nextRoute = computed(() => rawRoutes.find(i => i.path === `${Math.min(rawRoutes.length, currentPage.value + 1)}`))
|
|
19
|
+
|
|
20
|
+
const rawTree = computed(() => rawRoutes
|
|
21
|
+
.filter((route: RouteRecordRaw) => route.meta?.slide?.title)
|
|
22
|
+
.reduce((acc: TocItem[], route: RouteRecordRaw) => {
|
|
23
|
+
addToTree(acc, route)
|
|
24
|
+
return acc
|
|
25
|
+
}, []))
|
|
26
|
+
const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value, currentRoute.value))
|
|
27
|
+
const tree = computed(() => filterTree(treeWithActiveStatuses.value))
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
route,
|
|
31
|
+
path,
|
|
32
|
+
total,
|
|
33
|
+
currentPage,
|
|
34
|
+
currentPath,
|
|
35
|
+
currentRoute,
|
|
36
|
+
currentSlideId,
|
|
37
|
+
currentLayout,
|
|
38
|
+
nextRoute,
|
|
39
|
+
rawTree,
|
|
40
|
+
treeWithActiveStatuses,
|
|
41
|
+
tree,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ComputedRef, WritableComputedRef } from 'vue'
|
|
2
|
+
import { computed, nextTick, ref } from 'vue'
|
|
3
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
4
|
+
import { rawRoutes, router } from '../routes'
|
|
5
|
+
|
|
6
|
+
export function useNavClicks(
|
|
7
|
+
clicks: WritableComputedRef<number>,
|
|
8
|
+
currentRoute: ComputedRef<RouteRecordRaw | undefined>,
|
|
9
|
+
currentPage: ComputedRef<number>,
|
|
10
|
+
) {
|
|
11
|
+
// force update collected elements when the route is fully resolved
|
|
12
|
+
const routeForceRefresh = ref(0)
|
|
13
|
+
nextTick(() => {
|
|
14
|
+
router.afterEach(async() => {
|
|
15
|
+
await nextTick()
|
|
16
|
+
routeForceRefresh.value += 1
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const clicksElements = computed<HTMLElement[]>(() => {
|
|
21
|
+
// eslint-disable-next-line no-unused-expressions
|
|
22
|
+
routeForceRefresh.value
|
|
23
|
+
return currentRoute.value?.meta?.__clicksElements || []
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const clicksTotal = computed(() => +(currentRoute.value?.meta?.clicks ?? clicksElements.value.length))
|
|
27
|
+
|
|
28
|
+
const hasNext = computed(() => currentPage.value < rawRoutes.length - 1 || clicks.value < clicksTotal.value)
|
|
29
|
+
const hasPrev = computed(() => currentPage.value > 1 || clicks.value > 0)
|
|
30
|
+
return {
|
|
31
|
+
clicks,
|
|
32
|
+
clicksElements,
|
|
33
|
+
clicksTotal,
|
|
34
|
+
hasNext,
|
|
35
|
+
hasPrev,
|
|
36
|
+
}
|
|
37
|
+
}
|
package/constants.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey, Ref } from 'vue'
|
|
2
|
+
import type { SlidevContext } from './modules/context'
|
|
2
3
|
|
|
3
4
|
export const injectionClicks: InjectionKey<Ref<number>> = Symbol('v-click-clicks')
|
|
4
5
|
export const injectionClicksElements: InjectionKey<Ref<(Element | string)[]>> = Symbol('v-click-clicks-elements')
|
|
5
6
|
export const injectionOrderMap: InjectionKey<Ref<Map<number, HTMLElement[]>>> = Symbol('v-click-clicks-order-map')
|
|
6
7
|
export const injectionClicksDisabled: InjectionKey<Ref<boolean>> = Symbol('v-click-clicks-disabled')
|
|
7
8
|
export const injectionSlideScale: InjectionKey<ComputedRef<number>> = Symbol('slidev-slide-scale')
|
|
9
|
+
export const injectionSlidevContext: InjectionKey<SlidevContext> = Symbol('slidev-slidev-context')
|
|
8
10
|
|
|
9
11
|
export const CLASS_VCLICK_TARGET = 'slidev-vclick-target'
|
|
10
12
|
export const CLASS_VCLICK_HIDDEN = 'slidev-vclick-hidden'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComponentCustomProps, Slots } from 'vue'
|
|
3
|
+
import { h, watchEffect } from 'vue'
|
|
4
|
+
import _configs from '/@slidev/configs'
|
|
5
|
+
import { slideScale, windowSize } from '../state'
|
|
6
|
+
import { isPrintMode } from '../logic/nav'
|
|
7
|
+
import { themeVars } from '../env'
|
|
8
|
+
import PrintContainer from './PrintContainer.vue'
|
|
9
|
+
|
|
10
|
+
const width = _configs.canvasWidth
|
|
11
|
+
const height = Math.round(width / _configs.aspectRatio) + 1
|
|
12
|
+
|
|
13
|
+
function vStyle<Props>(props: Props, { slots }: { slots: Slots }) {
|
|
14
|
+
if (slots.default)
|
|
15
|
+
return h('style', slots.default())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
watchEffect(() => {
|
|
19
|
+
if (isPrintMode)
|
|
20
|
+
document.body.parentNode.classList.add('print')
|
|
21
|
+
else
|
|
22
|
+
document.body.parentNode.classList.remove('print')
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<vStyle>
|
|
28
|
+
@page { size: {{ width }}px {{ height }}px; margin: 0px; }
|
|
29
|
+
</vStyle>
|
|
30
|
+
<div id="page-root" class="grid grid-cols-[1fr,max-content]" :style="themeVars">
|
|
31
|
+
<PrintContainer
|
|
32
|
+
class="w-full h-full"
|
|
33
|
+
:style="{ background: 'var(--slidev-slide-container-background, black)'}"
|
|
34
|
+
:width="windowSize.width.value"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<style lang="postcss">
|
|
40
|
+
html.print,
|
|
41
|
+
html.print body,
|
|
42
|
+
html.print #app,
|
|
43
|
+
html.print #page-root {
|
|
44
|
+
height: auto;
|
|
45
|
+
overflow: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
html.print * {
|
|
49
|
+
-webkit-print-color-adjust: exact;
|
|
50
|
+
}
|
|
51
|
+
html.print {
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
overflow: visible;
|
|
55
|
+
}
|
|
56
|
+
html.print body {
|
|
57
|
+
margin: 0 auto;
|
|
58
|
+
border: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
float: none;
|
|
61
|
+
overflow: visible;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, provide } from 'vue'
|
|
3
|
+
import { configs, slideAspect, slideWidth } from '../env'
|
|
4
|
+
import { injectionSlideScale } from '../constants'
|
|
5
|
+
import { rawRoutes } from '../logic/nav'
|
|
6
|
+
import PrintSlide from './PrintSlide.vue'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
width: number
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const width = computed(() => props.width)
|
|
13
|
+
const height = computed(() => props.width / slideAspect)
|
|
14
|
+
|
|
15
|
+
const screenAspect = computed(() => width.value / height.value)
|
|
16
|
+
|
|
17
|
+
const scale = computed(() => {
|
|
18
|
+
if (screenAspect.value < slideAspect)
|
|
19
|
+
return width.value / slideWidth
|
|
20
|
+
return (height.value * slideAspect) / slideWidth
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Remove the "end" slide
|
|
24
|
+
const routes = rawRoutes.slice(0, -1)
|
|
25
|
+
|
|
26
|
+
const className = computed(() => ({
|
|
27
|
+
'select-none': !configs.selectable,
|
|
28
|
+
'slidev-code-line-numbers': configs.lineNumbers,
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
provide(injectionSlideScale, scale)
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div id="print-container" :class="className">
|
|
36
|
+
<div id="print-content">
|
|
37
|
+
<PrintSlide v-for="route of routes" :key="route.path" :route="route" />
|
|
38
|
+
</div>
|
|
39
|
+
<slot name="controls" />
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<style lang="postcss">
|
|
44
|
+
#print-content {
|
|
45
|
+
@apply bg-main;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.slide-container {
|
|
49
|
+
@apply relative;
|
|
50
|
+
position: relative;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
page-break-before: always;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
+
import { computed, reactive } from 'vue'
|
|
4
|
+
import { useNav } from '../composables/useNav'
|
|
5
|
+
import { isClicksDisabled } from '../logic/nav'
|
|
6
|
+
import PrintSlideClick from './PrintSlideClick.vue'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{ route: RouteRecordRaw }>()
|
|
9
|
+
|
|
10
|
+
const clicksElements = reactive(props.route.meta?.__clicksElements || [])
|
|
11
|
+
|
|
12
|
+
const route = computed(() => props.route)
|
|
13
|
+
const nav = useNav(route)
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<PrintSlideClick v-model:clicks-elements="clicksElements" :clicks="0" :nav="nav" :route="route" />
|
|
18
|
+
<template v-if="!isClicksDisabled">
|
|
19
|
+
<PrintSlideClick v-for="i of (clicksElements.length)" :key="i" :clicks="i" :nav="nav" :route="route" />
|
|
20
|
+
</template>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
3
|
+
import { computed, provide, reactive, shallowRef } from 'vue'
|
|
4
|
+
import { useVModel } from '@vueuse/core'
|
|
5
|
+
import { useNavClicks } from '../composables/useNavClicks'
|
|
6
|
+
import { injectionSlidevContext } from '../constants'
|
|
7
|
+
import { configs, slideHeight, slideWidth } from '../env'
|
|
8
|
+
import { getSlideClass } from '../utils'
|
|
9
|
+
import type { SlidevContextNav } from '../modules/context'
|
|
10
|
+
import SlideWrapper from './SlideWrapper'
|
|
11
|
+
// @ts-expect-error virtual module
|
|
12
|
+
import GlobalTop from '/@slidev/global-components/top'
|
|
13
|
+
// @ts-expect-error virtual module
|
|
14
|
+
import GlobalBottom from '/@slidev/global-components/bottom'
|
|
15
|
+
|
|
16
|
+
const props = defineProps<{
|
|
17
|
+
clicks: number
|
|
18
|
+
clicksElements?: HTMLElement[]
|
|
19
|
+
nav: SlidevContextNav
|
|
20
|
+
route: RouteRecordRaw
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits(['update:clicksElements'])
|
|
24
|
+
|
|
25
|
+
const clicksElements = useVModel(props, 'clicksElements', emit)
|
|
26
|
+
|
|
27
|
+
const style = computed(() => ({
|
|
28
|
+
height: `${slideHeight}px`,
|
|
29
|
+
width: `${slideWidth}px`,
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
const DrawingPreview = shallowRef<any>()
|
|
33
|
+
if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
|
|
34
|
+
import('./DrawingPreview.vue').then(v => (DrawingPreview.value = v.default))
|
|
35
|
+
|
|
36
|
+
const clicks = computed(() => props.clicks)
|
|
37
|
+
const navClicks = useNavClicks(clicks, props.nav.currentRoute, props.nav.currentPage)
|
|
38
|
+
|
|
39
|
+
provide(injectionSlidevContext, reactive({
|
|
40
|
+
nav: { ...props.nav, ...navClicks },
|
|
41
|
+
configs,
|
|
42
|
+
themeConfigs: computed(() => configs.themeConfig),
|
|
43
|
+
}))
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div :id="route.path" class="slide-container" :style="style">
|
|
48
|
+
<GlobalBottom />
|
|
49
|
+
|
|
50
|
+
<SlideWrapper
|
|
51
|
+
:is="route?.component"
|
|
52
|
+
v-model:clicks-elements="clicksElements"
|
|
53
|
+
:clicks="clicks"
|
|
54
|
+
:clicks-disabled="false"
|
|
55
|
+
:class="getSlideClass(route)"
|
|
56
|
+
/>
|
|
57
|
+
<template
|
|
58
|
+
v-if="
|
|
59
|
+
(__SLIDEV_FEATURE_DRAWINGS__ ||
|
|
60
|
+
__SLIDEV_FEATURE_DRAWINGS_PERSIST__) &&
|
|
61
|
+
DrawingPreview
|
|
62
|
+
"
|
|
63
|
+
>
|
|
64
|
+
<DrawingPreview :page="+route.path" />
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<GlobalTop />
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
package/logic/drawings.ts
CHANGED
|
@@ -65,9 +65,9 @@ export function updateState() {
|
|
|
65
65
|
canClear.value = !!drauu.el?.children.length
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export function loadCanvas() {
|
|
68
|
+
export function loadCanvas(page?: number) {
|
|
69
69
|
disableDump = true
|
|
70
|
-
const data = drawingState[currentPage.value]
|
|
70
|
+
const data = drawingState[page || currentPage.value]
|
|
71
71
|
if (data != null)
|
|
72
72
|
drauu.load(data)
|
|
73
73
|
else
|
package/logic/nav.ts
CHANGED
|
@@ -80,7 +80,7 @@ export const rawTree = computed(() => rawRoutes
|
|
|
80
80
|
addToTree(acc, route)
|
|
81
81
|
return acc
|
|
82
82
|
}, []))
|
|
83
|
-
export const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value))
|
|
83
|
+
export const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(rawTree.value, currentRoute.value))
|
|
84
84
|
export const tree = computed(() => filterTree(treeWithActiveStatuses.value))
|
|
85
85
|
|
|
86
86
|
export function next() {
|
|
@@ -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
|
}
|
|
@@ -192,24 +194,25 @@ export function addToTree(tree: TocItem[], route: RouteRecordRaw, level = 1) {
|
|
|
192
194
|
|
|
193
195
|
export function getTreeWithActiveStatuses(
|
|
194
196
|
tree: TocItem[],
|
|
197
|
+
currentRoute?: RouteRecordRaw,
|
|
195
198
|
hasActiveParent = false,
|
|
196
199
|
parent?: TocItem,
|
|
197
200
|
): TocItem[] {
|
|
198
201
|
return tree.map((item: TocItem) => {
|
|
199
202
|
const clone = {
|
|
200
203
|
...item,
|
|
201
|
-
active: item.path === currentRoute
|
|
204
|
+
active: item.path === currentRoute?.path,
|
|
202
205
|
hasActiveParent,
|
|
203
206
|
}
|
|
204
207
|
if (clone.children.length > 0)
|
|
205
|
-
clone.children = getTreeWithActiveStatuses(clone.children, clone.active || clone.hasActiveParent, clone)
|
|
208
|
+
clone.children = getTreeWithActiveStatuses(clone.children, currentRoute, clone.active || clone.hasActiveParent, clone)
|
|
206
209
|
if (parent && (clone.active || clone.activeParent))
|
|
207
210
|
parent.activeParent = true
|
|
208
211
|
return clone
|
|
209
212
|
})
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
215
|
+
export function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
213
216
|
return tree
|
|
214
217
|
.filter((item: TocItem) => !item.hideInToc)
|
|
215
218
|
.map((item: TocItem) => ({
|
package/logic/recording.ts
CHANGED
|
@@ -73,6 +73,7 @@ export function useRecording() {
|
|
|
73
73
|
const recorderCamera: Ref<RecorderType | undefined> = shallowRef()
|
|
74
74
|
const recorderSlides: Ref<RecorderType | undefined> = shallowRef()
|
|
75
75
|
const streamCamera: Ref<MediaStream | undefined> = shallowRef()
|
|
76
|
+
const streamCapture: Ref<MediaStream | undefined> = shallowRef()
|
|
76
77
|
const streamSlides: Ref<MediaStream | undefined> = shallowRef()
|
|
77
78
|
|
|
78
79
|
const config: RecorderOptions = {
|
|
@@ -140,7 +141,7 @@ export function useRecording() {
|
|
|
140
141
|
const { default: Recorder } = await import('recordrtc')
|
|
141
142
|
await startCameraStream()
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
streamCapture.value = await navigator.mediaDevices.getDisplayMedia({
|
|
144
145
|
video: {
|
|
145
146
|
// aspectRatio: 1.6,
|
|
146
147
|
frameRate: 15,
|
|
@@ -151,6 +152,11 @@ export function useRecording() {
|
|
|
151
152
|
resizeMode: 'crop-and-scale',
|
|
152
153
|
},
|
|
153
154
|
})
|
|
155
|
+
streamCapture.value.addEventListener('inactive', stopRecording)
|
|
156
|
+
|
|
157
|
+
// We need to create a new Stream to merge video and audio to have the inactive event working on streamCapture
|
|
158
|
+
streamSlides.value = new MediaStream()
|
|
159
|
+
streamCapture.value!.getVideoTracks().forEach(videoTrack => streamSlides.value!.addTrack(videoTrack))
|
|
154
160
|
|
|
155
161
|
// merge config
|
|
156
162
|
Object.assign(config, customConfig)
|
|
@@ -194,6 +200,7 @@ export function useRecording() {
|
|
|
194
200
|
const url = URL.createObjectURL(blob)
|
|
195
201
|
download(getFilename('screen', config.mimeType), url)
|
|
196
202
|
window.URL.revokeObjectURL(url)
|
|
203
|
+
closeStream(streamCapture)
|
|
197
204
|
closeStream(streamSlides)
|
|
198
205
|
recorderSlides.value = undefined
|
|
199
206
|
})
|
|
@@ -237,6 +244,7 @@ export function useRecording() {
|
|
|
237
244
|
recorderCamera,
|
|
238
245
|
recorderSlides,
|
|
239
246
|
streamCamera,
|
|
247
|
+
streamCapture,
|
|
240
248
|
streamSlides,
|
|
241
249
|
}
|
|
242
250
|
}
|
package/modules/context.ts
CHANGED
|
@@ -1,37 +1,30 @@
|
|
|
1
1
|
import type { App } from 'vue'
|
|
2
|
-
import {
|
|
3
|
-
import { objectKeys } from '@antfu/utils'
|
|
2
|
+
import { reactive } from 'vue'
|
|
4
3
|
import type { UnwrapNestedRefs } from '@vue/reactivity'
|
|
4
|
+
import type { configs } from '../env'
|
|
5
5
|
import * as nav from '../logic/nav'
|
|
6
|
+
import { clicks, route } from '../logic/nav'
|
|
6
7
|
import { isDark } from '../logic/dark'
|
|
7
|
-
import {
|
|
8
|
+
import { injectionSlidevContext } from '../constants'
|
|
9
|
+
import { useContext } from '../composables/useContext'
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
export type SlidevContextNavKey = 'route' | 'path' | 'total' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute'| 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide'
|
|
12
|
+
export type SlidevContextNavClicksKey = 'clicks' | 'clicksElements' | 'clicksTotal' | 'hasNext' | 'hasPrev'
|
|
13
|
+
|
|
14
|
+
export type SlidevContextNav = Pick<typeof nav, SlidevContextNavKey>
|
|
15
|
+
export type SlidevContextNavClicks = Pick<typeof nav, SlidevContextNavClicksKey>
|
|
16
|
+
|
|
17
|
+
export interface SlidevContext {
|
|
18
|
+
nav: UnwrapNestedRefs<SlidevContextNav & SlidevContextNavClicks>
|
|
19
|
+
configs: typeof configs
|
|
20
|
+
themeConfigs: typeof configs['themeConfig']
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export default function createSlidevContext() {
|
|
20
24
|
return {
|
|
21
25
|
install(app: App) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
for (const key of objectKeys(nav)) {
|
|
25
|
-
if (typeof key === 'string')
|
|
26
|
-
// @ts-expect-error I know :)
|
|
27
|
-
navObj[key] = nav[key]
|
|
28
|
-
}
|
|
29
|
-
const context = reactive({
|
|
30
|
-
nav: navObj,
|
|
31
|
-
configs,
|
|
32
|
-
themeConfigs: computed(() => configs.themeConfig),
|
|
33
|
-
})
|
|
34
|
-
app.config.globalProperties.$slidev = readonly(context)
|
|
26
|
+
const context = useContext(route, clicks)
|
|
27
|
+
app.provide(injectionSlidevContext, reactive(context))
|
|
35
28
|
|
|
36
29
|
// allows controls from postMessages
|
|
37
30
|
if (__DEV__) {
|
|
@@ -40,7 +33,7 @@ export default function createSlidevContext() {
|
|
|
40
33
|
window.addEventListener('message', ({ data }) => {
|
|
41
34
|
if (data && data.target === 'slidev') {
|
|
42
35
|
if (data.type === 'navigate') {
|
|
43
|
-
|
|
36
|
+
nav.go(+data.no, +data.clicks || 0)
|
|
44
37
|
}
|
|
45
38
|
else if (data.type === 'css-vars') {
|
|
46
39
|
const root = document.documentElement
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.2",
|
|
4
4
|
"description": "Presentation slides for developers",
|
|
5
5
|
"homepage": "https://sli.dev",
|
|
6
6
|
"bugs": "https://github.com/slidevjs/slidev/issues",
|
|
@@ -12,27 +12,29 @@
|
|
|
12
12
|
},
|
|
13
13
|
"funding": "https://github.com/sponsors/antfu",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@antfu/utils": "^0.5.
|
|
16
|
-
"@slidev/parser": "0.
|
|
17
|
-
"@slidev/types": "0.
|
|
18
|
-
"@vueuse/core": "^8.
|
|
15
|
+
"@antfu/utils": "^0.5.1",
|
|
16
|
+
"@slidev/parser": "0.30.2",
|
|
17
|
+
"@slidev/types": "0.30.2",
|
|
18
|
+
"@vueuse/core": "^8.2.5",
|
|
19
19
|
"@vueuse/head": "^0.7.5",
|
|
20
|
-
"@vueuse/motion": "^2.0.0-beta.
|
|
20
|
+
"@vueuse/motion": "^2.0.0-beta.18",
|
|
21
21
|
"codemirror": "^5.65.2",
|
|
22
|
+
"defu": "^6.0.0",
|
|
22
23
|
"drauu": "^0.3.0",
|
|
23
24
|
"file-saver": "^2.0.5",
|
|
24
25
|
"js-base64": "^3.7.2",
|
|
25
26
|
"js-yaml": "^4.1.0",
|
|
26
27
|
"katex": "^0.15.3",
|
|
27
|
-
"mermaid": "^
|
|
28
|
+
"mermaid": "^9.0.0",
|
|
28
29
|
"monaco-editor": "^0.33.0",
|
|
29
|
-
"nanoid": "^3.3.
|
|
30
|
-
"prettier": "^2.6.
|
|
30
|
+
"nanoid": "^3.3.2",
|
|
31
|
+
"prettier": "^2.6.2",
|
|
31
32
|
"recordrtc": "^5.6.2",
|
|
32
33
|
"resolve": "^1.22.0",
|
|
33
|
-
"vite-plugin-windicss": "^1.8.
|
|
34
|
-
"vue": "^3.2.
|
|
34
|
+
"vite-plugin-windicss": "^1.8.4",
|
|
35
|
+
"vue": "^3.2.32",
|
|
35
36
|
"vue-router": "^4.0.14",
|
|
37
|
+
"vue-starport": "^0.2.4",
|
|
36
38
|
"windicss": "^3.5.1"
|
|
37
39
|
},
|
|
38
40
|
"engines": {
|
package/routes.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from 'vue-router'
|
|
2
2
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
3
3
|
import Play from './internals/Play.vue'
|
|
4
|
+
import Print from './internals/Print.vue'
|
|
4
5
|
// @ts-expect-error missing types
|
|
5
6
|
import _rawRoutes from '/@slidev/routes'
|
|
6
7
|
|
|
@@ -15,6 +16,7 @@ export const routes: RouteRecordRaw[] = [
|
|
|
15
16
|
...rawRoutes,
|
|
16
17
|
],
|
|
17
18
|
},
|
|
19
|
+
{ name: 'print', path: '/print', component: Print },
|
|
18
20
|
{ path: '', redirect: { path: '/1' } },
|
|
19
21
|
{ path: '/:pathMatch(.*)', redirect: { path: '/1' } },
|
|
20
22
|
]
|
package/setup/main.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { AppContext } from '@slidev/types'
|
|
4
4
|
import { MotionPlugin } from '@vueuse/motion'
|
|
5
|
+
import StarportPlugin from 'vue-starport'
|
|
5
6
|
|
|
6
7
|
export default function setupMain(context: AppContext) {
|
|
7
8
|
function setMaxHeight() {
|
|
@@ -13,6 +14,7 @@ export default function setupMain(context: AppContext) {
|
|
|
13
14
|
window.addEventListener('resize', setMaxHeight)
|
|
14
15
|
|
|
15
16
|
context.app.use(MotionPlugin)
|
|
17
|
+
context.app.use(StarportPlugin({ keepAlive: true }))
|
|
16
18
|
|
|
17
19
|
// @ts-expect-error inject in runtime
|
|
18
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
package/setup/prettier.ts
CHANGED