@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 CHANGED
@@ -6,4 +6,5 @@ setupRoot()
6
6
 
7
7
  <template>
8
8
  <RouterView />
9
+ <StarportCarrier />
9
10
  </template>
@@ -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>
@@ -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>
@@ -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 { tree } from '../logic/nav'
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
- let tocTree = filterTreeDepth(tree.value)
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')
@@ -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
- <RouterLink :to="item.path" v-html="item.title" />
33
- <TocList :level="level + 1" :list="item.children" :list-class="listClass" />
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 />
@@ -16,6 +16,7 @@ defineProps<{
16
16
 
17
17
  <template>
18
18
  <iframe
19
+ class="youtube"
19
20
  :width="width"
20
21
  :height="height"
21
22
  :src="`https://www.youtube.com/embed/${id}`"
@@ -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
- : `${import.meta.env.BASE_URL}slidev-exported.pdf`,
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.value?.path,
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) => ({
@@ -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
- streamSlides.value = await navigator.mediaDevices.getDisplayMedia({
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
  }
@@ -1,37 +1,30 @@
1
1
  import type { App } from 'vue'
2
- import { computed, reactive, readonly } from 'vue'
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 { configs } from '../env'
8
+ import { injectionSlidevContext } from '../constants'
9
+ import { useContext } from '../composables/useContext'
8
10
 
9
- declare module '@vue/runtime-core' {
10
- interface ComponentCustomProperties {
11
- $slidev: {
12
- nav: UnwrapNestedRefs<typeof nav>
13
- configs: typeof configs
14
- themeConfigs: typeof configs['themeConfig']
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 navObj: typeof nav = {} as any
23
- // need to copy over to get rid of the "Module" object type (will not unwrap)
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
- context.nav.go(+data.no, +data.clicks || 0)
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.29.2",
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.0",
16
- "@slidev/parser": "0.29.2",
17
- "@slidev/types": "0.29.2",
18
- "@vueuse/core": "^8.1.2",
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.12",
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": "^8.14.0",
28
+ "mermaid": "^9.0.0",
28
29
  "monaco-editor": "^0.33.0",
29
- "nanoid": "^3.3.1",
30
- "prettier": "^2.6.0",
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.3",
34
- "vue": "^3.2.31",
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
@@ -1,4 +1,3 @@
1
-
2
1
  import { format } from 'prettier'
3
2
 
4
3
  export async function formatCode(code: string, lang: string) {