@slidev/client 0.48.0-beta.3 → 0.48.0-beta.5

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.
@@ -14,11 +14,12 @@ Learn more: https://sli.dev/guide/syntax.html#line-highlighting
14
14
  <script setup lang="ts">
15
15
  import { parseRangeString } from '@slidev/parser/core'
16
16
  import { useClipboard } from '@vueuse/core'
17
- import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
17
+ import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
18
18
  import type { PropType } from 'vue'
19
19
  import { configs } from '../env'
20
20
  import { makeId } from '../logic/utils'
21
- import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
21
+ import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET } from '../constants'
22
+ import { useSlideContext } from '../context'
22
23
 
23
24
  const props = defineProps({
24
25
  ranges: {
@@ -47,7 +48,7 @@ const props = defineProps({
47
48
  },
48
49
  })
49
50
 
50
- const clicks = inject(injectionClicksContext)?.value
51
+ const { $clicksContext: clicks } = useSlideContext()
51
52
  const el = ref<HTMLDivElement>()
52
53
  const id = makeId()
53
54
 
@@ -20,11 +20,12 @@ Learn more: https://sli.dev/guide/syntax.html#latex-line-highlighting
20
20
  -->
21
21
 
22
22
  <script setup lang="ts">
23
- import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
23
+ import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
24
24
  import type { PropType } from 'vue'
25
25
  import { parseRangeString } from '@slidev/parser'
26
- import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
26
+ import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET } from '../constants'
27
27
  import { makeId } from '../logic/utils'
28
+ import { useSlideContext } from '../context'
28
29
 
29
30
  const props = defineProps({
30
31
  ranges: {
@@ -45,7 +46,7 @@ const props = defineProps({
45
46
  },
46
47
  })
47
48
 
48
- const clicks = inject(injectionClicksContext)?.value
49
+ const { $clicksContext: clicks } = useSlideContext()
49
50
  const el = ref<HTMLDivElement>()
50
51
  const id = makeId()
51
52
 
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { RenderContext } from '@slidev/types'
3
- import { computed, inject, ref } from 'vue'
3
+ import { computed, ref } from 'vue'
4
4
  import { useElementVisibility } from '@vueuse/core'
5
- import { injectionRenderContext } from '../constants'
5
+ import { useSlideContext } from '../context'
6
6
 
7
7
  type Context = 'main' | 'visible' | RenderContext
8
8
 
@@ -16,7 +16,7 @@ const targetVisible = useElementVisibility(target)
16
16
  // When context has `visible`, we need to wrap the content with a div to track the visibility
17
17
  const needsDomWrapper = Array.isArray(context) ? context.includes('visible') : context === 'visible'
18
18
 
19
- const currentContext = inject(injectionRenderContext)
19
+ const { $renderContext: currentContext } = useSlideContext()
20
20
  const shouldRender = computed(() => {
21
21
  const anyContext = Array.isArray(context) ? context.some(contextMatch) : contextMatch(context)
22
22
  const allConditions = Array.isArray(context) ? context.every(conditionsMatch) : conditionsMatch(context)
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { inject } from 'vue'
3
- import { injectionCurrentPage } from '../constants'
2
+ import { useSlideContext } from '../context'
4
3
 
5
- const $page = inject(injectionCurrentPage)
4
+ const { $page } = useSlideContext()
6
5
  </script>
7
6
 
8
7
  <template>
@@ -1,10 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { inject } from 'vue'
3
- import { injectionSlidevContext } from '../constants'
2
+ import { useSlideContext } from '../context'
4
3
 
5
- const $slidev = inject(injectionSlidevContext)
4
+ const { $nav } = useSlideContext()
6
5
  </script>
7
6
 
8
7
  <template>
9
- <span>{{ $slidev.nav.total }}</span>
8
+ <span>{{ $nav.total }}</span>
10
9
  </template>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
3
- import { injectionClicksContext, injectionRenderContext, injectionRoute, injectionSlidevContext } from '../constants'
2
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
3
+ import { useSlideContext } from '../context'
4
4
 
5
5
  const props = defineProps<{
6
6
  autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
@@ -8,10 +8,12 @@ const props = defineProps<{
8
8
  autoReset?: 'slide' | 'click'
9
9
  }>()
10
10
 
11
- const $slidev = inject(injectionSlidevContext)
12
- const route = inject(injectionRoute)
13
- const currentContext = inject(injectionRenderContext)
14
- const clicks = inject(injectionClicksContext)?.value
11
+ const {
12
+ $slidev,
13
+ $clicksContext: clicks,
14
+ $renderContext: currentContext,
15
+ $route: route,
16
+ } = useSlideContext()
15
17
 
16
18
  const video = ref<HTMLMediaElement>()
17
19
  const played = ref(false)
package/builtin/Toc.vue CHANGED
@@ -8,9 +8,9 @@ Usage:
8
8
  <Toc columns='2' maxDepth='3' mode='onlySiblings'/>
9
9
  -->
10
10
  <script setup lang='ts'>
11
- import { computed, inject } from 'vue'
11
+ import { computed } from 'vue'
12
12
  import type { TocItem } from '@slidev/types'
13
- import { injectionSlidevContext } from '../constants'
13
+ import { useSlideContext } from '../context'
14
14
 
15
15
  const props = withDefaults(
16
16
  defineProps<{
@@ -33,7 +33,7 @@ const props = withDefaults(
33
33
  },
34
34
  )
35
35
 
36
- const $slidev = inject(injectionSlidevContext)
36
+ const { $slidev } = useSlideContext()
37
37
 
38
38
  function filterTreeDepth(tree: TocItem[], level = 1): TocItem[] {
39
39
  if (level > Number(props.maxDepth)) {
package/builtin/Tweet.vue CHANGED
@@ -7,9 +7,9 @@ Usage:
7
7
  -->
8
8
 
9
9
  <script setup lang="ts">
10
- import { useScriptTag } from '@vueuse/core'
11
10
  import { getCurrentInstance, onMounted, ref } from 'vue'
12
11
  import { isDark } from '../logic/dark'
12
+ import { useTweetScript } from '../composables/useTweetScript'
13
13
 
14
14
  const props = defineProps<{
15
15
  id: string | number
@@ -41,21 +41,10 @@ async function create() {
41
41
  }
42
42
 
43
43
  // @ts-expect-error global
44
- if (window?.twttr?.widgets) {
44
+ if (window?.twttr?.widgets)
45
45
  onMounted(create)
46
- }
47
- else {
48
- useScriptTag(
49
- 'https://platform.twitter.com/widgets.js',
50
- () => {
51
- if (vm.isMounted)
52
- create()
53
- else
54
- onMounted(create, vm)
55
- },
56
- { async: true },
57
- )
58
- }
46
+ else
47
+ useTweetScript(vm, create)
59
48
  </script>
60
49
 
61
50
  <template>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { Fragment, inject, onMounted, watchEffect } from 'vue'
3
- import { injectionClicksContext } from '../constants'
2
+ import { Fragment, onMounted, watchEffect } from 'vue'
4
3
  import { makeId } from '../logic/utils'
4
+ import { useSlideContext } from '../context'
5
5
 
6
6
  const props = defineProps({
7
7
  size: {
@@ -10,12 +10,10 @@ const props = defineProps({
10
10
  },
11
11
  })
12
12
 
13
- const clicksRef = inject(injectionClicksContext)
13
+ const { $clicksContext: clicks } = useSlideContext()
14
14
 
15
15
  onMounted(() => {
16
16
  watchEffect((onCleanup) => {
17
- const clicks = clicksRef?.value
18
-
19
17
  if (!clicks || clicks.disabled)
20
18
  return
21
19
 
@@ -96,7 +96,7 @@ export default defineComponent({
96
96
  if (depth < +this.depth && Array.isArray(i.children))
97
97
  vNode = h(i, {}, mapSubList(i.children, depth))
98
98
  else
99
- vNode = i
99
+ vNode = h(i)
100
100
 
101
101
  const delta = thisShowIdx - execIdx
102
102
  execIdx = thisShowIdx
@@ -6,7 +6,7 @@ import type { RouteRecordRaw } from 'vue-router'
6
6
  import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
7
7
  import { normalizeAtProp } from '../logic/utils'
8
8
 
9
- function useClicksContextBase(route: RouteRecordRaw | undefined, getCurrent: () => number): ClicksContext {
9
+ function useClicksContextBase(getCurrent: () => number, clicksOverrides?: number): ClicksContext {
10
10
  const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
11
11
  const map: ClicksContext['map'] = shallowReactive(new Map())
12
12
 
@@ -53,7 +53,7 @@ function useClicksContextBase(route: RouteRecordRaw | undefined, getCurrent: ()
53
53
  get total() {
54
54
  // eslint-disable-next-line no-unused-expressions
55
55
  routeForceRefresh.value
56
- return route?.meta?.clicks
56
+ return clicksOverrides
57
57
  ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
58
58
  },
59
59
  }
@@ -63,21 +63,24 @@ export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksConte
63
63
  if (route?.meta?.__clicksContext)
64
64
  return route.meta.__clicksContext
65
65
  const thisPath = +(route?.path ?? 99999)
66
- const context = useClicksContextBase(route, () => {
67
- const currentPath = +(currentRoute.value?.path ?? 99999)
68
- if (currentPath === thisPath)
69
- return queryClicks.value
70
- else if (currentPath > thisPath)
71
- return 99999
72
- else
73
- return 0
74
- })
66
+ const context = useClicksContextBase(
67
+ () => {
68
+ const currentPath = +(currentRoute.value?.path ?? 99999)
69
+ if (currentPath === thisPath)
70
+ return queryClicks.value
71
+ else if (currentPath > thisPath)
72
+ return 99999
73
+ else
74
+ return 0
75
+ },
76
+ route?.meta?.clicks,
77
+ )
75
78
  if (route?.meta)
76
79
  route.meta.__clicksContext = context
77
80
  return context
78
81
  }
79
82
 
80
- export function useFixedClicks(route: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
83
+ export function useFixedClicks(route?: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
81
84
  const current = ref(currentInit)
82
- return [current, useClicksContextBase(route, () => current.value)]
85
+ return [current, useClicksContextBase(() => current.value, route?.meta?.clicks)]
83
86
  }
@@ -0,0 +1,17 @@
1
+ import { createSharedComposable, useScriptTag } from '@vueuse/core'
2
+ import type { ComponentInternalInstance } from 'vue'
3
+ import { onMounted } from 'vue'
4
+
5
+ export const useTweetScript = createSharedComposable(
6
+ (vm: ComponentInternalInstance, create: () => void) =>
7
+ useScriptTag(
8
+ 'https://platform.twitter.com/widgets.js',
9
+ () => {
10
+ if (vm.isMounted)
11
+ create()
12
+ else
13
+ onMounted(create, vm)
14
+ },
15
+ { async: true },
16
+ ),
17
+ )
package/constants.ts CHANGED
@@ -24,3 +24,49 @@ export const TRUST_ORIGINS = [
24
24
  'localhost',
25
25
  '127.0.0.1',
26
26
  ]
27
+
28
+ export const FRONTMATTER_FIELDS = [
29
+ 'clicks',
30
+ 'disabled',
31
+ 'hide',
32
+ 'hideInToc',
33
+ 'layout',
34
+ 'level',
35
+ 'preload',
36
+ 'routeAlias',
37
+ 'src',
38
+ 'title',
39
+ 'transition',
40
+ 'zoom',
41
+ ]
42
+
43
+ export const HEADMATTER_FIELDS = [
44
+ ...FRONTMATTER_FIELDS,
45
+ 'theme',
46
+ 'titleTemplate',
47
+ 'info',
48
+ 'author',
49
+ 'keywords',
50
+ 'presenter',
51
+ 'download',
52
+ 'exportFilename',
53
+ 'export',
54
+ 'highlighter',
55
+ 'lineNumbers',
56
+ 'monaco',
57
+ 'remoteAssets',
58
+ 'selectable',
59
+ 'record',
60
+ 'colorSchema',
61
+ 'routerMode',
62
+ 'aspectRatio',
63
+ 'canvasWidth',
64
+ 'themeConfig',
65
+ 'favicon',
66
+ 'plantUmlServer',
67
+ 'fonts',
68
+ 'defaults',
69
+ 'drawings',
70
+ 'htmlAttrs',
71
+ 'mdc',
72
+ ]
package/context.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { shallowRef, toRef } from 'vue'
2
+ import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
3
+ import { useFixedClicks } from './composables/useClicks'
4
+ import {
5
+ FRONTMATTER_FIELDS,
6
+ HEADMATTER_FIELDS,
7
+ injectionClicksContext,
8
+ injectionCurrentPage,
9
+ injectionFrontmatter,
10
+ injectionRenderContext,
11
+ injectionRoute,
12
+ injectionSlidevContext,
13
+ } from './constants'
14
+
15
+ const clicksContextFallback = shallowRef(useFixedClicks()[1])
16
+
17
+ /**
18
+ * Get the current slide context, should be called inside the setup function of a component inside slide
19
+ */
20
+ export function useSlideContext() {
21
+ const $slidev = injectLocal(injectionSlidevContext)!
22
+ const $nav = toRef($slidev, 'nav')
23
+ const $clicksContext = injectLocal(injectionClicksContext, clicksContextFallback)!.value
24
+ const $clicks = toRef($clicksContext, 'current')
25
+ const $page = injectLocal(injectionCurrentPage)!
26
+ const $renderContext = injectLocal(injectionRenderContext)!
27
+ const $frontmatter = injectLocal(injectionFrontmatter, {})
28
+ const $route = injectLocal(injectionRoute, undefined)
29
+
30
+ return {
31
+ $slidev,
32
+ $nav,
33
+ $clicksContext,
34
+ $clicks,
35
+ $page,
36
+ $route,
37
+ $renderContext,
38
+ $frontmatter,
39
+ }
40
+ }
41
+
42
+ export function provideFrontmatter(frontmatter: Record<string, any>) {
43
+ provideLocal(injectionFrontmatter, frontmatter)
44
+
45
+ const {
46
+ $slidev,
47
+ $page,
48
+ } = useSlideContext()
49
+
50
+ // update frontmatter in router to make HMR work better
51
+ const route = $slidev.nav.rawRoutes.find(i => i.path === String($page.value))
52
+ if (route?.meta?.slide?.frontmatter) {
53
+ for (const key of Object.keys(route.meta.slide.frontmatter)) {
54
+ if (!(key in frontmatter))
55
+ delete route.meta.slide.frontmatter[key]
56
+ }
57
+ Object.assign(route.meta.slide.frontmatter, frontmatter)
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Convert frontmatter options to props for v-bind
63
+ * It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter
64
+ */
65
+ export function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {
66
+ return {
67
+ ...objectOmit(frontmatter, pageNo === 0 ? HEADMATTER_FIELDS : FRONTMATTER_FIELDS),
68
+ frontmatter,
69
+ }
70
+ }
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { Menu } from 'floating-vue'
3
+ import 'floating-vue/dist/style.css'
2
4
  import {
3
5
  brush,
4
6
  brushColors,
@@ -21,19 +23,24 @@ function undo() {
21
23
  function redo() {
22
24
  drauu.redo()
23
25
  }
26
+
27
+ let lastDrawingMode: typeof drawingMode.value = 'stylus'
24
28
  function setDrawingMode(mode: typeof drawingMode.value) {
25
29
  drawingMode.value = mode
26
30
  drawingEnabled.value = true
31
+ if (mode !== 'eraseLine')
32
+ lastDrawingMode = mode
27
33
  }
28
34
  function setBrushColor(color: typeof brush.color) {
29
35
  brush.color = color
30
36
  drawingEnabled.value = true
37
+ drawingMode.value = lastDrawingMode
31
38
  }
32
39
  </script>
33
40
 
34
41
  <template>
35
42
  <Draggable
36
- class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200"
43
+ class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-20"
37
44
  dark="border border-gray-400 border-opacity-10"
38
45
  :class="drawingEnabled ? '' : drawingPinned ? 'opacity-40 hover:opacity-90' : 'opacity-0 pointer-events-none'"
39
46
  storage-key="slidev-drawing-pos"
@@ -57,23 +64,41 @@ function setBrushColor(color: typeof brush.color) {
57
64
  <IconButton title="Draw a rectangle" :class="{ shallow: drawingMode !== 'rectangle' }" @click="setDrawingMode('rectangle')">
58
65
  <carbon:checkbox />
59
66
  </IconButton>
60
- <!-- TODO: not sure why it's not working! -->
61
- <!-- <IconButton title="Erase" :class="{ shallow: drawingMode != 'eraseLine' }" @click="setDrawingMode('eraseLine')">
67
+ <IconButton title="Erase" :class="{ shallow: drawingMode !== 'eraseLine' }" @click="setDrawingMode('eraseLine')">
62
68
  <carbon:erase />
63
- </IconButton> -->
69
+ </IconButton>
64
70
 
65
71
  <VerticalDivider />
66
72
 
73
+ <Menu>
74
+ <IconButton title="Adjust stroke width" :class="{ shallow: drawingMode === 'eraseLine' }">
75
+ <svg viewBox="0 0 32 32" width="1.2em" height="1.2em">
76
+ <line x1="2" y1="15" x2="22" y2="4" stroke="currentColor" stroke-width="1" stroke-linecap="round" />
77
+ <line x1="2" y1="24" x2="28" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
78
+ <line x1="7" y1="31" x2="29" y2="19" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
79
+ </svg>
80
+ </IconButton>
81
+ <template #popper>
82
+ <div class="flex bg-main p-2">
83
+ <div class="inline-block w-7 text-center">
84
+ {{ brush.size }}
85
+ </div>
86
+ <div class="pt-.5">
87
+ <input v-model="brush.size" type="range" min="1" max="15" @change="drawingMode = lastDrawingMode">
88
+ </div>
89
+ </div>
90
+ </template>
91
+ </Menu>
67
92
  <IconButton
68
93
  v-for="color of brushColors"
69
94
  :key="color"
70
95
  title="Set brush color"
71
- :class="brush.color === color ? 'active' : 'shallow'"
96
+ :class="brush.color === color && drawingMode !== 'eraseLine' ? 'active' : 'shallow'"
72
97
  @click="setBrushColor(color)"
73
98
  >
74
99
  <div
75
- class="w-6 h-6 transition-all transform border border-gray-400/50"
76
- :class="brush.color !== color ? 'rounded-1/2 scale-85' : 'rounded-md'"
100
+ class="w-6 h-6 transition-all transform border"
101
+ :class="brush.color !== color ? 'rounded-1/2 scale-85 border-white' : 'rounded-md border-gray-300/50'"
77
102
  :style="drawingEnabled ? { background: color } : { borderColor: color }"
78
103
  />
79
104
  </IconButton>
@@ -87,7 +112,7 @@ function setBrushColor(color: typeof brush.color) {
87
112
  <carbon:redo />
88
113
  </IconButton>
89
114
  <IconButton title="Delete" :class="{ disabled: !canClear }" @click="clearDrauu()">
90
- <carbon:delete />
115
+ <carbon:trash-can />
91
116
  </IconButton>
92
117
 
93
118
  <VerticalDivider />
@@ -106,3 +131,11 @@ function setBrushColor(color: typeof brush.color) {
106
131
  </IconButton>
107
132
  </Draggable>
108
133
  </template>
134
+
135
+ <style lang="postcss">
136
+ .v-popper--theme-menu {
137
+ .v-popper__arrow-inner {
138
+ @apply border-main;
139
+ }
140
+ }
141
+ </style>
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'
2
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
+ import { injectLocal } from '@vueuse/core'
3
4
  import { drauu, drawingEnabled, loadCanvas } from '../logic/drawings'
4
5
  import { injectionSlideScale } from '../constants'
5
6
 
6
- const scale = inject(injectionSlideScale)!
7
+ const scale = injectLocal(injectionSlideScale)!
7
8
  const svg = ref<SVGSVGElement>()
8
9
 
9
10
  onMounted(() => {
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { parseRangeString } from '@slidev/parser/core'
3
- import { computed, provide } from 'vue'
3
+ import { computed } from 'vue'
4
+ import { provideLocal } from '@vueuse/core'
4
5
  import { configs, slideAspect, slideWidth } from '../env'
5
6
  import { injectionSlideScale } from '../constants'
6
7
  import { route as currentRoute, rawRoutes } from '../logic/nav'
@@ -31,7 +32,7 @@ const className = computed(() => ({
31
32
  'select-none': !configs.selectable,
32
33
  }))
33
34
 
34
- provide(injectionSlideScale, scale)
35
+ provideLocal(injectionSlideScale, scale)
35
36
  </script>
36
37
 
37
38
  <template>
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { RouteRecordRaw } from 'vue-router'
3
- import { computed, provide, reactive, shallowRef } from 'vue'
3
+ import { computed, reactive, shallowRef } from 'vue'
4
4
  import type { ClicksContext } from '@slidev/types'
5
+ import { provideLocal } from '@vueuse/core'
5
6
  import { injectionSlidevContext } from '../constants'
6
7
  import { configs, slideHeight, slideWidth } from '../env'
7
8
  import { getSlideClass } from '../utils'
@@ -33,7 +34,7 @@ const id = computed(() =>
33
34
  `${props.route.path.toString().padStart(3, '0')}-${(props.nav.clicks.value + 1).toString().padStart(2, '0')}`,
34
35
  )
35
36
 
36
- provide(injectionSlidevContext, reactive({
37
+ provideLocal(injectionSlidevContext, reactive({
37
38
  nav: props.nav,
38
39
  configs,
39
40
  themeConfigs: computed(() => configs.themeConfig),
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { useElementSize, useStyleTag } from '@vueuse/core'
3
- import { computed, provide, ref, watchEffect } from 'vue'
2
+ import { provideLocal, useElementSize, useStyleTag } from '@vueuse/core'
3
+ import { computed, ref, watchEffect } from 'vue'
4
4
  import { configs, slideAspect, slideHeight, slideWidth } from '../env'
5
5
  import { injectionSlideScale } from '../constants'
6
6
  import { isPrintMode } from '../logic/nav'
@@ -64,7 +64,7 @@ if (props.isMain) {
64
64
  `))
65
65
  }
66
66
 
67
- provide(injectionSlideScale, scale as any)
67
+ provideLocal(injectionSlideScale, scale as any)
68
68
  </script>
69
69
 
70
70
  <template>
@@ -1,6 +1,8 @@
1
- import { defineComponent, h, provide, ref, toRef } from 'vue'
1
+ import { computed, defineComponent, h, ref, toRef } from 'vue'
2
2
  import type { PropType } from 'vue'
3
+ import { provideLocal } from '@vueuse/core'
3
4
  import type { ClicksContext, RenderContext } from '@slidev/types'
5
+ import type { RouteRecordRaw } from 'vue-router'
4
6
  import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
5
7
 
6
8
  export default defineComponent({
@@ -20,23 +22,37 @@ export default defineComponent({
20
22
  },
21
23
  is: {
22
24
  type: Object,
23
- default: undefined,
25
+ required: true,
24
26
  },
25
27
  route: {
26
- type: Object,
27
- default: undefined,
28
+ type: Object as PropType<RouteRecordRaw>,
29
+ required: true,
28
30
  },
29
31
  },
30
32
  setup(props) {
31
- provide(injectionRoute, props.route as any)
32
- provide(injectionCurrentPage, ref(+props.route?.path))
33
- provide(injectionRenderContext, ref(props.renderContext as RenderContext))
34
- provide(injectionActive, toRef(props, 'active'))
35
- provide(injectionClicksContext, toRef(props, 'clicksContext'))
33
+ provideLocal(injectionRoute, props.route)
34
+ provideLocal(injectionCurrentPage, ref(+props.route.path))
35
+ provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
36
+ provideLocal(injectionActive, toRef(props, 'active'))
37
+ provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
38
+
39
+ const style = computed(() => {
40
+ const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
41
+ return zoom === 1
42
+ ? undefined
43
+ : {
44
+ width: `${100 / zoom}%`,
45
+ height: `${100 / zoom}%`,
46
+ transformOrigin: 'top left',
47
+ transform: `scale(${zoom})`,
48
+ }
49
+ })
50
+
51
+ return {
52
+ style,
53
+ }
36
54
  },
37
55
  render() {
38
- if (this.$props.is)
39
- return h(this.$props.is)
40
- return this.$slots?.default?.()
56
+ return h(this.$props.is, { style: this.style })
41
57
  },
42
58
  })
package/logic/drawings.ts CHANGED
@@ -4,6 +4,7 @@ import { createDrauu } from 'drauu'
4
4
  import { toReactive, useLocalStorage } from '@vueuse/core'
5
5
  import { drawingState, onPatch, patch } from '../state/drawings'
6
6
  import { configs } from '../env'
7
+ import { isInputting } from '../state'
7
8
  import { currentPage, isPresenter } from './nav'
8
9
 
9
10
  export const brushColors = [
@@ -40,11 +41,13 @@ export const drawingMode = computed({
40
41
  set(v: DrawingMode | 'arrow') {
41
42
  _mode.value = v
42
43
  if (v === 'arrow') {
43
- brush.mode = 'line'
44
+ // eslint-disable-next-line ts/no-use-before-define
45
+ drauu.mode = 'line'
44
46
  brush.arrowEnd = true
45
47
  }
46
48
  else {
47
- brush.mode = v
49
+ // eslint-disable-next-line ts/no-use-before-define
50
+ drauu.mode = v
48
51
  brush.arrowEnd = false
49
52
  }
50
53
  },
@@ -110,7 +113,7 @@ drauu.on('start', () => isDrawing.value = true)
110
113
  drauu.on('end', () => isDrawing.value = false)
111
114
 
112
115
  window.addEventListener('keydown', (e) => {
113
- if (!drawingEnabled.value)
116
+ if (!drawingEnabled.value || isInputting.value)
114
117
  return
115
118
 
116
119
  const noModifier = !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey
package/logic/nav.ts CHANGED
@@ -177,7 +177,7 @@ export async function downloadPDF() {
177
177
  export async function openInEditor(url?: string) {
178
178
  if (url == null) {
179
179
  const slide = currentRoute.value?.meta?.slide
180
- if (!slide?.filepath)
180
+ if (!slide)
181
181
  return false
182
182
  url = `${slide.filepath}:${slide.start}`
183
183
  }
@@ -1,12 +1,12 @@
1
1
  import type { App } from 'vue'
2
- import { computed, reactive } from 'vue'
2
+ import { computed, reactive, ref } from 'vue'
3
3
  import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
4
4
  import type { ComputedRef } from '@vue/reactivity'
5
5
  import type { configs } from '../env'
6
6
  import * as nav from '../logic/nav'
7
7
  import { route } from '../logic/nav'
8
8
  import { isDark } from '../logic/dark'
9
- import { injectionCurrentPage, injectionSlidevContext } from '../constants'
9
+ import { injectionCurrentPage, injectionRenderContext, injectionSlidevContext } from '../constants'
10
10
  import { useContext } from '../composables/useContext'
11
11
 
12
12
  export type SlidevContextNavKey = 'path' | 'total' | 'clicksContext' | 'clicks' | 'clicksTotal' | 'currentPage' | 'currentPath' | 'currentRoute' | 'currentSlideId' | 'currentLayout' | 'nextRoute' | 'rawTree' | 'treeWithActiveStatuses' | 'tree' | 'downloadPDF' | 'next' | 'nextSlide' | 'openInEditor' | 'prev' | 'prevSlide' | 'rawRoutes' | 'go'
@@ -25,6 +25,7 @@ export default function createSlidevContext() {
25
25
  return {
26
26
  install(app: App) {
27
27
  const context = reactive(useContext(route))
28
+ app.provide(injectionRenderContext, ref('none'))
28
29
  app.provide(injectionSlidevContext, context)
29
30
  app.provide(injectionCurrentPage, computed(() => context.nav.currentPage))
30
31
 
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
- "version": "0.48.0-beta.3",
3
+ "type": "module",
4
+ "version": "0.48.0-beta.5",
4
5
  "description": "Presentation slides for developers",
5
6
  "author": "antfu <anthonyfu117@hotmail.com>",
6
7
  "license": "MIT",
@@ -13,6 +14,12 @@
13
14
  "bugs": "https://github.com/slidevjs/slidev/issues",
14
15
  "exports": {
15
16
  "./package.json": "./package.json",
17
+ "./constants": "./constants.ts",
18
+ "./context": "./context.ts",
19
+ "./env": "./env.ts",
20
+ "./layoutHelper": "./layoutHelper.ts",
21
+ "./routes": "./routes.ts",
22
+ "./utils": "./utils.ts",
16
23
  "./*": "./*"
17
24
  },
18
25
  "engines": {
@@ -22,15 +29,15 @@
22
29
  "@antfu/utils": "^0.7.7",
23
30
  "@iconify-json/carbon": "^1.1.30",
24
31
  "@iconify-json/ph": "^1.1.11",
25
- "@shikijs/vitepress-twoslash": "^1.1.5",
32
+ "@shikijs/vitepress-twoslash": "^1.1.6",
26
33
  "@unhead/vue": "^1.8.10",
27
34
  "@unocss/reset": "^0.58.5",
28
- "@vueuse/core": "^10.7.2",
29
- "@vueuse/math": "^10.7.2",
35
+ "@vueuse/core": "^10.8.0",
36
+ "@vueuse/math": "^10.8.0",
30
37
  "@vueuse/motion": "^2.0.0",
31
38
  "codemirror": "^5.65.16",
32
39
  "defu": "^6.1.4",
33
- "drauu": "^0.3.7",
40
+ "drauu": "^0.4.0",
34
41
  "file-saver": "^2.0.5",
35
42
  "floating-vue": "^5.2.2",
36
43
  "fuse.js": "^7.0.0",
@@ -39,15 +46,15 @@
39
46
  "katex": "^0.16.9",
40
47
  "mermaid": "^10.8.0",
41
48
  "monaco-editor": "^0.37.1",
42
- "nanoid": "^5.0.5",
49
+ "nanoid": "^5.0.6",
43
50
  "prettier": "^3.2.5",
44
51
  "recordrtc": "^5.6.2",
45
52
  "resolve": "^1.22.8",
46
53
  "unocss": "^0.58.5",
47
54
  "vue": "^3.4.19",
48
55
  "vue-router": "^4.2.5",
49
- "@slidev/parser": "0.48.0-beta.3",
50
- "@slidev/types": "0.48.0-beta.3"
56
+ "@slidev/parser": "0.48.0-beta.5",
57
+ "@slidev/types": "0.48.0-beta.5"
51
58
  },
52
59
  "devDependencies": {
53
60
  "vite": "^5.1.3"
package/routes.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
2
2
  import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
3
3
  import type { TransitionGroupProps } from 'vue'
4
- import type { ClicksContext } from '@slidev/types'
4
+ import type { ClicksContext, SlideInfo } from '@slidev/types'
5
5
 
6
6
  // @ts-expect-error missing types
7
7
  import _rawRoutes, { redirects } from '/@slidev/routes'
@@ -84,19 +84,12 @@ declare module 'vue-router' {
84
84
  preload?: boolean
85
85
 
86
86
  // slide info
87
- slide?: {
87
+ slide?: Omit<SlideInfo, 'source'> & {
88
+ noteHTML: string
89
+ filepath: string
88
90
  start: number
89
- end: number
90
- note?: string
91
- noteHTML?: string
92
91
  id: number
93
92
  no: number
94
- filepath: string
95
- title?: string
96
- level?: number
97
- raw: string
98
- content: string
99
- frontmatter: Record<string, any>
100
93
  }
101
94
 
102
95
  // private fields
@@ -1,4 +1,5 @@
1
1
  import type { Ref, WritableComputedRef } from 'vue'
2
+ import { onClickOutside } from '@vueuse/core'
2
3
  import { watch } from 'vue'
3
4
  import * as _CodeMirror from 'codemirror'
4
5
  import 'codemirror/mode/javascript/javascript'
@@ -47,5 +48,11 @@ export async function useCodeMirror(
47
48
  { immediate: true },
48
49
  )
49
50
 
51
+ onClickOutside(cm.getWrapperElement(), () => {
52
+ const el = cm.getInputField()
53
+ if (document.activeElement === el)
54
+ el.blur()
55
+ })
56
+
50
57
  return cm
51
58
  }
package/state/index.ts CHANGED
@@ -20,19 +20,19 @@ 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 = useLocalStorage<string>('slidev-camera', 'default')
24
- export const currentMic = useLocalStorage<string>('slidev-mic', 'default')
23
+ export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
24
+ export const currentMic = useLocalStorage<string>('slidev-mic', 'default', { listenToStorageChanges: false })
25
25
  export const slideScale = useLocalStorage<number>('slidev-scale', 0)
26
26
 
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 isEditorVertical = useLocalStorage('slidev-editor-vertical', false)
31
- export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318)
32
- export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300)
27
+ export const showOverview = useLocalStorage('slidev-show-overview', false, { listenToStorageChanges: false })
28
+ export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true, { listenToStorageChanges: false })
29
+ export const showEditor = useLocalStorage('slidev-show-editor', false, { listenToStorageChanges: false })
30
+ export const isEditorVertical = useLocalStorage('slidev-editor-vertical', false, { listenToStorageChanges: false })
31
+ export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 318, { listenToStorageChanges: false })
32
+ export const editorHeight = useLocalStorage('slidev-editor-height', isClient ? window.innerHeight * 0.4 : 300, { listenToStorageChanges: false })
33
33
 
34
- export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1)
35
- export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1)
34
+ export const presenterNotesFontSize = useLocalStorage('slidev-presenter-font-size', 1, { listenToStorageChanges: false })
35
+ export const presenterLayout = useLocalStorage('slidev-presenter-layout', 1, { listenToStorageChanges: false })
36
36
 
37
37
  export function togglePresenterLayout() {
38
38
  presenterLayout.value = presenterLayout.value + 1