@slidev/client 0.49.15 → 0.49.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/builtin/VAfter.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * <v-after/> click animations component
3
3
  *
4
- * Learn more: https://sli.dev/guide/animations.html#click-animations
4
+ * Learn more: https://sli.dev/guide/animations.html#click-animation
5
5
  */
6
6
 
7
7
  import { toArray } from '@antfu/utils'
package/builtin/VClick.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * <v-click/> click animations component
3
3
  *
4
- * Learn more: https://sli.dev/guide/animations.html#click-animations
4
+ * Learn more: https://sli.dev/guide/animations.html#click-animation
5
5
  */
6
6
 
7
7
  import type { PropType, VNode } from 'vue'
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * <v-clicks/> click animations component
3
3
  *
4
- * Learn more: https://sli.dev/guide/animations.html#click-animations
4
+ * Learn more: https://sli.dev/guide/animations.html#click-animation
5
5
  */
6
6
 
7
7
  import { toArray } from '@antfu/utils'
@@ -1,8 +1,7 @@
1
1
  import { clamp, sum } from '@antfu/utils'
2
2
  import type { ClicksContext, NormalizedRangeClickValue, NormalizedSingleClickValue, RawAtValue, RawSingleAtValue, SlideRoute } from '@slidev/types'
3
3
  import type { Ref } from 'vue'
4
- import { computed, ref, shallowReactive } from 'vue'
5
- import { routeForceRefresh } from '../logic/route'
4
+ import { computed, onMounted, onUnmounted, ref, shallowReactive } from 'vue'
6
5
 
7
6
  export function normalizeSingleAtValue(at: RawSingleAtValue): NormalizedSingleClickValue {
8
7
  if (at === false || at === 'false')
@@ -54,17 +53,19 @@ export function createClicksContextBase(
54
53
  get isMounted() {
55
54
  return isMounted.value
56
55
  },
57
- onMounted: () => {
58
- isMounted.value = true
59
- // Convert maxMap to reactive
60
- maxMap = shallowReactive(maxMap)
61
- // Make sure the query is not greater than the total
62
- context.current = current.value
63
- },
64
- onUnmounted: () => {
65
- isMounted.value = false
66
- relativeSizeMap = new Map()
67
- maxMap = new Map()
56
+ setup() {
57
+ onMounted(() => {
58
+ isMounted.value = true
59
+ // Convert maxMap to reactive
60
+ maxMap = shallowReactive(maxMap)
61
+ // Make sure the query is not greater than the total
62
+ context.current = current.value
63
+ })
64
+ onUnmounted(() => {
65
+ isMounted.value = false
66
+ relativeSizeMap = new Map()
67
+ maxMap = new Map()
68
+ })
68
69
  },
69
70
  calculateSince(rawAt, size = 1) {
70
71
  const at = normalizeSingleAtValue(rawAt)
@@ -144,13 +145,9 @@ export function createClicksContextBase(
144
145
  maxMap.delete(el)
145
146
  },
146
147
  get currentOffset() {
147
- // eslint-disable-next-line no-unused-expressions
148
- routeForceRefresh.value
149
148
  return sum(...relativeSizeMap.values())
150
149
  },
151
150
  get total() {
152
- // eslint-disable-next-line no-unused-expressions
153
- routeForceRefresh.value
154
151
  return clicksTotalOverrides
155
152
  ?? (isMounted.value
156
153
  ? Math.max(0, ...maxMap.values())
@@ -113,7 +113,7 @@ export function useDragElementsUpdater(no: number) {
113
113
  }
114
114
  }
115
115
 
116
- export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource, isArrow?: boolean) {
116
+ export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource, isArrow = false) {
117
117
  function inject<T>(key: InjectionKey<T> | string): T | undefined {
118
118
  return directive
119
119
  ? directiveInject(directive, key)
@@ -275,7 +275,7 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
275
275
 
276
276
  const currentRoute = computed(() => router.currentRoute.value)
277
277
  const query = computed(() => {
278
- // eslint-disable-next-line no-unused-expressions
278
+ // eslint-disable-next-line ts/no-unused-expressions
279
279
  router.currentRoute.value.query
280
280
  return new URLSearchParams(location.search)
281
281
  })
@@ -17,7 +17,7 @@ export function useSlideInfo(no: number): UseSlideInfo {
17
17
  update: async () => {},
18
18
  }
19
19
  }
20
- const url = `/@slidev/slide/${no}.json`
20
+ const url = `/__slidev/slides/${no}.json`
21
21
  const { data: info, execute } = useFetch(url).json<SlideInfo>().get()
22
22
 
23
23
  execute()
package/env.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { computed, ref } from 'vue'
1
+ import { computed } from 'vue'
2
2
  import { objectMap } from '@antfu/utils'
3
3
  import configs from '#slidev/configs'
4
4
 
@@ -6,8 +6,8 @@ export { configs }
6
6
 
7
7
  export const mode = __DEV__ ? 'dev' : 'build'
8
8
 
9
- export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
10
- export const slideWidth = ref(configs.canvasWidth ?? 980)
9
+ export const slideAspect = computed(() => configs.aspectRatio)
10
+ export const slideWidth = computed(() => configs.canvasWidth)
11
11
 
12
12
  // To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
13
13
  // Doing this, we will prevent on print, to create an additional empty white page after each page.
@@ -41,14 +41,20 @@ function onMousedown() {
41
41
  :title="`Clicks in this slide: ${length}`"
42
42
  :class="length && props.clicksContext.isMounted ? '' : 'op50'"
43
43
  >
44
- <div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
44
+ <div class="flex gap-0.2 items-center min-w-16 font-mono mr1">
45
45
  <carbon:cursor-1 text-sm op50 />
46
- <div flex-auto />
47
46
  <template v-if="current >= 0 && current !== CLICKS_MAX && active">
47
+ <div flex-auto />
48
48
  <span text-primary>{{ current }}</span>
49
- <span op25>/</span>
49
+ <span op25 text-sm>/</span>
50
+ <span op50 text-sm>{{ total }}</span>
50
51
  </template>
51
- <span op50>{{ total }}</span>
52
+ <div
53
+ v-else
54
+ op50 flex-auto pl1
55
+ >
56
+ {{ total }}
57
+ </div>
52
58
  </div>
53
59
  <div
54
60
  relative flex-auto h5 font-mono flex="~"
@@ -62,10 +68,13 @@ function onMousedown() {
62
68
  ]"
63
69
  :style="{ width: length > 0 ? `${1 / length * 100}%` : '100%' }"
64
70
  >
65
- <div absolute inset-0 :class="i <= current ? 'bg-primary op15' : ''" />
71
+ <div
72
+ absolute inset-0
73
+ :class="(i <= current && active) ? 'bg-primary op15' : ''"
74
+ />
66
75
  <div
67
76
  :class="[
68
- +i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
77
+ (+i === +current && active) ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
69
78
  i === 0 ? 'rounded-l' : '',
70
79
  i === total ? 'rounded-r' : 'border-r-2',
71
80
  ]"
@@ -94,6 +94,14 @@ function calculateEditorHeight() {
94
94
  inputEl.value.style.height = `${inputEl.value.scrollHeight}px`
95
95
  }
96
96
 
97
+ function onKeyDown(e: KeyboardEvent) {
98
+ // Override save shortcut on editing mode
99
+ if (editing.value && e.metaKey && e.key === 's') {
100
+ e.preventDefault()
101
+ update({ note: note.value }, props.no)
102
+ }
103
+ }
104
+
97
105
  watch(
98
106
  [note, editing],
99
107
  () => {
@@ -129,5 +137,6 @@ watch(
129
137
  :class="props.class"
130
138
  :placeholder="placeholder"
131
139
  @keydown.esc="editing = false"
140
+ @keydown="onKeyDown"
132
141
  />
133
142
  </template>
@@ -39,7 +39,6 @@ provideLocal(injectionSlidevContext, reactive({
39
39
  <GlobalBottom />
40
40
 
41
41
  <SlideWrapper
42
- :is="route.component!"
43
42
  :clicks-context="nav.clicksContext.value"
44
43
  :class="getSlideClass(route)"
45
44
  :route="route"
@@ -98,11 +98,6 @@ watchEffect(() => {
98
98
  // Watch rowCount, make sure up and down shortcut work correctly.
99
99
  overviewRowCount.value = rowCount.value
100
100
  })
101
-
102
- const activeSlidesLoaded = ref(false)
103
- setTimeout(() => {
104
- activeSlidesLoaded.value = true
105
- }, 3000)
106
101
  </script>
107
102
 
108
103
  <template>
@@ -113,8 +108,7 @@ setTimeout(() => {
113
108
  leave-to-class="opacity-0 scale-102 !backdrop-blur-0px"
114
109
  >
115
110
  <div
116
- v-if="showOverview || activeSlidesLoaded"
117
- v-show="showOverview"
111
+ v-if="showOverview"
118
112
  class="fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)] z-20 bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px"
119
113
  @click="close"
120
114
  >
@@ -1,12 +1,11 @@
1
1
  <script setup lang="ts">
2
- import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
2
+ import { computed, ref, toRef } from 'vue'
3
3
  import type { CSSProperties, PropType } from 'vue'
4
4
  import { provideLocal } from '@vueuse/core'
5
5
  import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
6
  import { injectionClicksContext, injectionCurrentPage, injectionFrontmatter, injectionRenderContext, injectionRoute, injectionSlideZoom } from '../constants'
7
7
  import { getSlideClass } from '../utils'
8
8
  import { configs } from '../env'
9
- import SlideLoading from './SlideLoading.vue'
10
9
  import { SlideBottom, SlideTop } from '#slidev/global-layers'
11
10
 
12
11
  const props = defineProps({
@@ -47,19 +46,6 @@ const style = computed<CSSProperties>(() => ({
47
46
  ...zoomStyle.value,
48
47
  'user-select': configs.selectable ? undefined : 'none',
49
48
  }))
50
-
51
- const SlideComponent = computed(() => props.route && defineAsyncComponent({
52
- loader: async () => {
53
- const component = await props.route.component()
54
- return defineComponent({
55
- mounted: props.clicksContext?.onMounted,
56
- unmounted: props.clicksContext?.onUnmounted,
57
- render: () => h(component.default),
58
- })
59
- },
60
- delay: 300,
61
- loadingComponent: SlideLoading,
62
- }))
63
49
  </script>
64
50
 
65
51
  <template>
@@ -69,7 +55,7 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
69
55
  :style="style"
70
56
  >
71
57
  <SlideBottom />
72
- <SlideComponent />
58
+ <component :is="props.route.component" />
73
59
  <SlideTop />
74
60
  </div>
75
61
  </template>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { TransitionGroup, computed, shallowRef, watch } from 'vue'
2
+ import { TransitionGroup, computed, shallowRef, watchEffect } from 'vue'
3
3
  import { recomputeAllPoppers } from 'floating-vue'
4
+ import type { SlideRoute } from '@slidev/types'
4
5
  import { useNav } from '../composables/useNav'
5
6
  import { useViewTransition } from '../composables/useViewTransition'
6
7
  import { skipTransition } from '../logic/hmr'
@@ -19,6 +20,7 @@ const {
19
20
  currentSlideRoute,
20
21
  currentTransition,
21
22
  getPrimaryClicks,
23
+ prevRoute,
22
24
  nextRoute,
23
25
  slides,
24
26
  isPrintMode,
@@ -26,13 +28,26 @@ const {
26
28
  clicksDirection,
27
29
  } = useNav()
28
30
 
29
- // preload next route
30
- watch(currentSlideRoute, () => {
31
- if (currentSlideRoute.value?.meta && currentSlideRoute.value.meta.preload !== false)
32
- currentSlideRoute.value.meta.__preloaded = true
33
- if (nextRoute.value?.meta && nextRoute.value.meta.preload !== false)
34
- nextRoute.value.meta.__preloaded = true
35
- }, { immediate: true })
31
+ function preloadRoute(route: SlideRoute) {
32
+ if (route.meta.preload !== false) {
33
+ route.meta.__preloaded = true
34
+ route.load()
35
+ }
36
+ }
37
+ // preload current, prev and next slides
38
+ watchEffect(() => {
39
+ preloadRoute(currentSlideRoute.value)
40
+ preloadRoute(prevRoute.value)
41
+ preloadRoute(nextRoute.value)
42
+ })
43
+ // preload all slides after 3s
44
+ watchEffect((onCleanup) => {
45
+ const routes = slides.value
46
+ const timeout = setTimeout(() => {
47
+ routes.forEach(preloadRoute)
48
+ }, 3000)
49
+ onCleanup(() => clearTimeout(timeout))
50
+ })
36
51
 
37
52
  const hasViewTransition = useViewTransition()
38
53
 
@@ -67,14 +82,15 @@ function onAfterLeave() {
67
82
  }"
68
83
  @after-leave="onAfterLeave"
69
84
  >
70
- <SlideWrapper
71
- v-for="route of loadedRoutes"
72
- v-show="route === currentSlideRoute"
73
- :key="route.no"
74
- :clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
75
- :route="route"
76
- :render-context="renderContext"
77
- />
85
+ <template v-for="route of loadedRoutes" :key="route.no">
86
+ <SlideWrapper
87
+ v-if="Math.abs(route.no - currentSlideRoute.no) <= 20"
88
+ v-show="route === currentSlideRoute"
89
+ :clicks-context="isPrintMode && !isPrintWithClicks ? createFixedClicks(route, CLICKS_MAX) : getPrimaryClicks(route)"
90
+ :route="route"
91
+ :render-context="renderContext"
92
+ />
93
+ </template>
78
94
  </component>
79
95
 
80
96
  <DragControl v-if="activeDragElement" :data="activeDragElement" />
package/logic/route.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { WritableComputedRef } from 'vue'
2
- import { computed, nextTick, ref, unref } from 'vue'
2
+ import { computed, nextTick, unref } from 'vue'
3
3
  import { useRouter } from 'vue-router'
4
4
 
5
5
  export function useRouteQuery<T extends string | string[]>(
@@ -35,6 +35,3 @@ export function useRouteQuery<T extends string | string[]>(
35
35
  },
36
36
  })
37
37
  }
38
-
39
- // force update collected elements when the route is fully resolved
40
- export const routeForceRefresh = ref(0)
@@ -19,7 +19,7 @@ export async function renderMermaid(lzEncoded: string, options: any) {
19
19
 
20
20
  mermaid.initialize({
21
21
  startOnLoad: false,
22
- ...clearUndefined(setupMermaid() || {}),
22
+ ...clearUndefined(await setupMermaid() || {}),
23
23
  ...clearUndefined(options),
24
24
  })
25
25
  const code = lz.decompressFromBase64(lzEncoded)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.49.15",
4
+ "version": "0.49.17",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -32,12 +32,12 @@
32
32
  "@iconify-json/carbon": "^1.1.36",
33
33
  "@iconify-json/ph": "^1.1.13",
34
34
  "@iconify-json/svg-spinners": "^1.1.2",
35
- "@shikijs/monaco": "^1.10.0",
36
- "@shikijs/vitepress-twoslash": "^1.10.0",
35
+ "@shikijs/monaco": "^1.10.3",
36
+ "@shikijs/vitepress-twoslash": "^1.10.3",
37
37
  "@slidev/rough-notation": "^0.1.0",
38
38
  "@typescript/ata": "^0.9.6",
39
- "@unhead/vue": "^1.9.14",
40
- "@unocss/reset": "^0.61.2",
39
+ "@unhead/vue": "^1.9.15",
40
+ "@unocss/reset": "^0.61.3",
41
41
  "@vueuse/core": "^10.11.0",
42
42
  "@vueuse/math": "^10.11.0",
43
43
  "@vueuse/motion": "^2.2.3",
@@ -45,23 +45,23 @@
45
45
  "file-saver": "^2.0.5",
46
46
  "floating-vue": "^5.2.2",
47
47
  "fuse.js": "^7.0.0",
48
- "katex": "^0.16.10",
48
+ "katex": "^0.16.11",
49
49
  "lz-string": "^1.5.0",
50
50
  "mermaid": "^10.9.1",
51
51
  "monaco-editor": "^0.50.0",
52
52
  "prettier": "^3.3.2",
53
53
  "recordrtc": "^5.6.2",
54
- "shiki": "^1.10.0",
54
+ "shiki": "^1.10.3",
55
55
  "shiki-magic-move": "^0.4.2",
56
- "typescript": "^5.5.2",
57
- "unocss": "^0.61.2",
58
- "vue": "^3.4.31",
56
+ "typescript": "^5.5.3",
57
+ "unocss": "^0.61.3",
58
+ "vue": "^3.4.32",
59
59
  "vue-router": "^4.4.0",
60
60
  "yaml": "^2.4.5",
61
- "@slidev/parser": "0.49.15",
62
- "@slidev/types": "0.49.15"
61
+ "@slidev/parser": "0.49.17",
62
+ "@slidev/types": "0.49.17"
63
63
  },
64
64
  "devDependencies": {
65
- "vite": "^5.3.2"
65
+ "vite": "^5.3.3"
66
66
  }
67
67
  }
@@ -139,6 +139,14 @@ onMounted(() => {
139
139
  <carbon-moon v-if="isDark" />
140
140
  <carbon-sun v-else />
141
141
  </IconButton>
142
+ <IconButton
143
+ v-else
144
+ :title="isDark ? 'Dark mode' : 'Light mode'"
145
+ pointer-events-none op50
146
+ >
147
+ <carbon-moon v-if="isDark" />
148
+ <carbon-sun v-else />
149
+ </IconButton>
142
150
  </div>
143
151
  </nav>
144
152
  <main
@@ -173,7 +181,7 @@ onMounted(() => {
173
181
  <carbon:cics-program />
174
182
  </IconButton>
175
183
  </div>
176
- <div class="flex flex-col gap-2 my5">
184
+ <div class="flex flex-col gap-2 my5" :style="{ width: `${cardWidth}px` }">
177
185
  <div
178
186
  class="border rounded border-main overflow-hidden bg-main select-none h-max"
179
187
  @dblclick="openSlideInNewTab(getSlidePath(route, false))"
package/pages/play.vue CHANGED
@@ -37,18 +37,21 @@ registerShortcuts()
37
37
  if (__SLIDEV_FEATURE_WAKE_LOCK__)
38
38
  useWakeLock()
39
39
 
40
- useStyleTag(computed(() => `
41
- vite-error-overlay {
42
- --width: calc(100vw - ${isEditorVertical.value ? 0 : editorWidth.value}px);
43
- --height: calc(100vh - ${isEditorVertical.value ? editorHeight.value : 0}px);
44
- position: fixed;
45
- left: 0;
46
- top: 0;
47
- width: calc(var(--width) / var(--slidev-slide-scale));
48
- height: calc(var(--height) / var(--slidev-slide-scale));
49
- transform-origin: top left;
50
- transform: scale(var(--slidev-slide-scale));
51
- }`))
40
+ if (import.meta.hot) {
41
+ useStyleTag(computed(() => `
42
+ vite-error-overlay {
43
+ --width: calc(100vw - ${isEditorVertical.value ? 0 : editorWidth.value}px);
44
+ --height: calc(100vh - ${isEditorVertical.value ? editorHeight.value : 0}px);
45
+ position: fixed;
46
+ left: 0;
47
+ top: 0;
48
+ width: calc(var(--width) / var(--slidev-slide-scale));
49
+ height: calc(var(--height) / var(--slidev-slide-scale));
50
+ transform-origin: top left;
51
+ transform: scale(var(--slidev-slide-scale));
52
+ }`,
53
+ ))
54
+ }
52
55
 
53
56
  const persistNav = computed(() => isScreenVertical.value || showEditor.value)
54
57
 
@@ -56,7 +56,7 @@ const nextFrame = computed(() => {
56
56
  if (clicksContext.value.current < clicksContext.value.total)
57
57
  return [currentSlideRoute.value!, clicksContext.value.current + 1] as const
58
58
  else if (hasNext.value)
59
- return [nextRoute.value!, 0] as const
59
+ return [nextRoute.value, 0] as const
60
60
  else
61
61
  return null
62
62
  })
@@ -135,6 +135,11 @@ onMounted(() => {
135
135
  render-context="previewNext"
136
136
  />
137
137
  </SlideContainer>
138
+ <div v-else class="h-full flex justify-center items-center">
139
+ <div class="text-gray-500">
140
+ End of the presentation
141
+ </div>
142
+ </div>
138
143
  <div class="absolute left-0 top-0 bg-main border-b border-r border-main px2 py1 op50 text-sm">
139
144
  Next
140
145
  </div>
package/setup/main.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import type { AppContext } from '@slidev/types'
2
2
  import TwoSlashFloatingVue from '@shikijs/vitepress-twoslash/client'
3
3
  import type { App } from 'vue'
4
- import { nextTick } from 'vue'
5
4
  import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
6
5
  import { createHead } from '@unhead/vue'
7
- import { routeForceRefresh } from '../logic/route'
8
6
  import { createVClickDirectives } from '../modules/v-click'
9
7
  import { createVMarkDirective } from '../modules/v-mark'
10
8
  import { createVDragDirective } from '../modules/v-drag'
@@ -43,13 +41,6 @@ export default async function setupMain(app: App) {
43
41
  router,
44
42
  }
45
43
 
46
- nextTick(() => {
47
- router.afterEach(async () => {
48
- await nextTick()
49
- routeForceRefresh.value += 1
50
- })
51
- })
52
-
53
44
  for (const setup of setups)
54
45
  await setup(context)
55
46
  }
package/setup/mermaid.ts CHANGED
@@ -1,14 +1,14 @@
1
- import type { MermaidOptions } from '@slidev/types'
2
- import { defineMermaidSetup } from '@slidev/types'
1
+ import type { MermaidConfig } from 'mermaid'
2
+ import { createSingletonPromise } from '@antfu/utils'
3
3
  import setups from '#slidev/setups/mermaid'
4
4
 
5
- export default defineMermaidSetup(() => {
6
- const setupReturn: MermaidOptions = {
5
+ export default createSingletonPromise(async () => {
6
+ const setupReturn: MermaidConfig = {
7
7
  theme: 'default',
8
8
  }
9
9
 
10
10
  for (const setup of setups)
11
- Object.assign(setupReturn, setup())
11
+ Object.assign(setupReturn, await setup())
12
12
 
13
13
  return setupReturn
14
14
  })