@slidev/client 0.29.0 → 0.30.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/App.vue +1 -0
- package/builtin/Link.vue +26 -0
- package/builtin/Toc.vue +8 -3
- package/builtin/TocList.vue +1 -1
- 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 +53 -0
- package/internals/PrintSlide.vue +21 -0
- package/internals/PrintSlideClick.vue +69 -0
- package/logic/drawings.ts +2 -2
- package/logic/nav.ts +5 -4
- 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/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" v-html="title" />
|
|
21
|
+
<RouterLink v-else-if="!isPrintMode && !title" :to="to">
|
|
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/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
|
@@ -29,7 +29,7 @@ const classes = computed(() => {
|
|
|
29
29
|
<template>
|
|
30
30
|
<ol v-if="list && list.length > 0" :class="classes">
|
|
31
31
|
<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
|
-
<
|
|
32
|
+
<Link :to="item.path" :title="item.title" />
|
|
33
33
|
<TocList :level="level + 1" :list="item.children" :list-class="listClass" />
|
|
34
34
|
</li>
|
|
35
35
|
</ol>
|
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,53 @@
|
|
|
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) return width.value / slideWidth
|
|
19
|
+
return (height.value * slideAspect) / slideWidth
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Remove the "end" slide
|
|
23
|
+
const routes = rawRoutes.slice(0, -1)
|
|
24
|
+
|
|
25
|
+
const className = computed(() => ({
|
|
26
|
+
'select-none': !configs.selectable,
|
|
27
|
+
'slidev-code-line-numbers': configs.lineNumbers,
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
provide(injectionSlideScale, scale)
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div id="print-container" :class="className">
|
|
35
|
+
<div id="print-content">
|
|
36
|
+
<PrintSlide v-for="route of routes" :key="route.path" :route="route" />
|
|
37
|
+
</div>
|
|
38
|
+
<slot name="controls" />
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style lang="postcss">
|
|
43
|
+
#print-content {
|
|
44
|
+
@apply bg-main;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.slide-container {
|
|
48
|
+
@apply relative;
|
|
49
|
+
position: relative;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
page-break-before: always;
|
|
52
|
+
}
|
|
53
|
+
</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() {
|
|
@@ -192,24 +192,25 @@ export function addToTree(tree: TocItem[], route: RouteRecordRaw, level = 1) {
|
|
|
192
192
|
|
|
193
193
|
export function getTreeWithActiveStatuses(
|
|
194
194
|
tree: TocItem[],
|
|
195
|
+
currentRoute?: RouteRecordRaw,
|
|
195
196
|
hasActiveParent = false,
|
|
196
197
|
parent?: TocItem,
|
|
197
198
|
): TocItem[] {
|
|
198
199
|
return tree.map((item: TocItem) => {
|
|
199
200
|
const clone = {
|
|
200
201
|
...item,
|
|
201
|
-
active: item.path === currentRoute
|
|
202
|
+
active: item.path === currentRoute?.path,
|
|
202
203
|
hasActiveParent,
|
|
203
204
|
}
|
|
204
205
|
if (clone.children.length > 0)
|
|
205
|
-
clone.children = getTreeWithActiveStatuses(clone.children, clone.active || clone.hasActiveParent, clone)
|
|
206
|
+
clone.children = getTreeWithActiveStatuses(clone.children, currentRoute, clone.active || clone.hasActiveParent, clone)
|
|
206
207
|
if (parent && (clone.active || clone.activeParent))
|
|
207
208
|
parent.activeParent = true
|
|
208
209
|
return clone
|
|
209
210
|
})
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
213
|
+
export function filterTree(tree: TocItem[], level = 1): TocItem[] {
|
|
213
214
|
return tree
|
|
214
215
|
.filter((item: TocItem) => !item.hideInToc)
|
|
215
216
|
.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.0",
|
|
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.0",
|
|
17
|
+
"@slidev/types": "0.30.0",
|
|
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
|