@slidev/client 0.50.0-beta.9 → 0.50.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.
Files changed (43) hide show
  1. package/composables/useClicks.ts +6 -5
  2. package/composables/useDragElements.ts +30 -24
  3. package/composables/useNav.ts +17 -15
  4. package/composables/usePrintStyles.ts +28 -0
  5. package/constants.ts +1 -0
  6. package/internals/ClicksSlider.vue +1 -1
  7. package/internals/ContextMenu.vue +1 -1
  8. package/internals/DragControl.vue +1 -1
  9. package/internals/DrawingControls.vue +1 -1
  10. package/internals/ExportPdfTip.vue +90 -0
  11. package/internals/FormCheckbox.vue +16 -0
  12. package/internals/FormItem.vue +41 -0
  13. package/internals/IconButton.vue +7 -2
  14. package/internals/MenuButton.vue +1 -1
  15. package/internals/Modal.vue +1 -1
  16. package/internals/NavControls.vue +10 -4
  17. package/internals/PrintContainer.vue +2 -21
  18. package/internals/PrintSlide.vue +4 -3
  19. package/internals/PrintSlideClick.vue +11 -3
  20. package/internals/QuickOverview.vue +2 -2
  21. package/internals/Settings.vue +5 -2
  22. package/internals/SideEditor.vue +1 -1
  23. package/internals/SlidesShow.vue +7 -3
  24. package/internals/WebCamera.vue +2 -2
  25. package/layouts/error.vue +5 -1
  26. package/logic/dark.ts +11 -0
  27. package/logic/screenshot.ts +61 -0
  28. package/logic/shortcuts.ts +36 -35
  29. package/logic/slides.ts +2 -1
  30. package/modules/v-mark.ts +6 -0
  31. package/package.json +18 -16
  32. package/pages/export.vue +369 -0
  33. package/pages/overview.vue +1 -1
  34. package/pages/play.vue +1 -4
  35. package/pages/presenter.vue +9 -0
  36. package/pages/print.vue +0 -2
  37. package/setup/monaco.ts +14 -14
  38. package/setup/root.ts +6 -2
  39. package/setup/routes.ts +23 -12
  40. package/state/index.ts +2 -1
  41. package/styles/index.css +5 -4
  42. package/uno.config.ts +14 -0
  43. package/internals/PrintStyle.vue +0 -16
@@ -109,7 +109,7 @@ watchEffect(() => {
109
109
  >
110
110
  <div
111
111
  v-if="showOverview"
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 select-none"
112
+ class="fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)] z-modal bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px select-none"
113
113
  @click="close"
114
114
  >
115
115
  <div
@@ -157,7 +157,7 @@ watchEffect(() => {
157
157
  </div>
158
158
  </div>
159
159
  </Transition>
160
- <div v-if="showOverview" class="fixed top-4 right-4 z-20 text-gray-400 flex flex-col items-center gap-2">
160
+ <div v-if="showOverview" class="fixed top-4 right-4 z-modal text-gray-400 flex flex-col items-center gap-2">
161
161
  <IconButton title="Close" class="text-2xl" @click="close">
162
162
  <div class="i-carbon:close" />
163
163
  </IconButton>
@@ -30,8 +30,11 @@ const wakeLockItems: SelectionItem<boolean>[] = [
30
30
  </script>
31
31
 
32
32
  <template>
33
- <div class="text-sm select-none">
33
+ <div class="text-sm select-none mb-2">
34
34
  <SelectList v-model="slideScale" title="Scale" :items="scaleItems" />
35
- <SelectList v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported" v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems" />
35
+ <SelectList
36
+ v-if="__SLIDEV_FEATURE_WAKE_LOCK__ && isSupported"
37
+ v-model="wakeLockEnabled" title="Wake lock" :items="wakeLockItems"
38
+ />
36
39
  </div>
37
40
  </template>
@@ -121,7 +121,7 @@ throttledWatch(
121
121
 
122
122
  <template>
123
123
  <div
124
- v-if="resize" class="fixed bg-gray-400 select-none opacity-0 hover:opacity-10 z-100"
124
+ v-if="resize" class="fixed bg-gray-400 select-none opacity-0 hover:opacity-10 z-dragging"
125
125
  :class="vertical ? 'left-0 right-0 w-full h-10px' : 'top-0 bottom-0 w-10px h-full'" :style="{
126
126
  opacity: handlerDown ? '0.3' : undefined,
127
127
  bottom: vertical ? `${editorHeight - 5}px` : undefined,
@@ -26,6 +26,7 @@ const {
26
26
  isPrintMode,
27
27
  isPrintWithClicks,
28
28
  clicksDirection,
29
+ printRange,
29
30
  } = useNav()
30
31
 
31
32
  function preloadRoute(route: SlideRoute) {
@@ -55,7 +56,10 @@ const DrawingLayer = shallowRef<any>()
55
56
  if (__SLIDEV_FEATURE_DRAWINGS__ || __SLIDEV_FEATURE_DRAWINGS_PERSIST__)
56
57
  import('./DrawingLayer.vue').then(v => DrawingLayer.value = v.default)
57
58
 
58
- const loadedRoutes = computed(() => slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value))
59
+ const loadedRoutes = computed(() => isPrintMode.value
60
+ ? printRange.value.map(no => slides.value[no - 1])
61
+ : slides.value.filter(r => r.meta?.__preloaded || r === currentSlideRoute.value),
62
+ )
59
63
 
60
64
  function onAfterLeave() {
61
65
  // After transition, we disable it so HMR won't trigger it again
@@ -72,8 +76,8 @@ function onAfterLeave() {
72
76
 
73
77
  <!-- Slides -->
74
78
  <component
75
- :is="hasViewTransition ? 'div' : TransitionGroup"
76
- v-bind="skipTransition ? {} : currentTransition"
79
+ :is="hasViewTransition && !isPrintMode ? 'div' : TransitionGroup"
80
+ v-bind="skipTransition || isPrintMode ? {} : currentTransition"
77
81
  id="slideshow"
78
82
  tag="div"
79
83
  :class="{
@@ -63,7 +63,7 @@ onMounted(fixPosition)
63
63
  <template>
64
64
  <div
65
65
  v-if="streamCamera && showAvatar && currentCamera !== 'none'"
66
- class="fixed z-10"
66
+ class="fixed z-camera"
67
67
  :style="containerStyle"
68
68
  >
69
69
  <div
@@ -83,7 +83,7 @@ onMounted(fixPosition)
83
83
 
84
84
  <div
85
85
  ref="handler"
86
- class="absolute bottom-0 right-0 rounded-full bg-main shadow opacity-0 shadow z-30 hover:opacity-100 dark:border dark:border-true-gray-700"
86
+ class="absolute bottom-0 right-0 rounded-full bg-main shadow opacity-0 shadow z-dragging hover:opacity-100 dark:border dark:border-true-gray-700"
87
87
  :style="handleStyle"
88
88
  :class="handlerDown ? '!opacity-100' : ''"
89
89
  />
package/layouts/error.vue CHANGED
@@ -1,5 +1,9 @@
1
1
  <template>
2
2
  <div class="px-4 py-10 text-center text-red-700 dark:text-red-500 font-bold font-mono">
3
- An error occurred on this slide. Check the terminal for more information.
3
+ {{
4
+ __SLIDEV_HAS_SERVER__
5
+ ? 'An error occurred on this slide. Check the terminal for more information.'
6
+ : 'Failed to fetch this slide. Please check your network connection.'
7
+ }}
4
8
  </div>
5
9
  </template>
package/logic/dark.ts CHANGED
@@ -26,12 +26,23 @@ export const isDark = computed<boolean>({
26
26
  export const toggleDark = useToggle(isDark)
27
27
 
28
28
  if (isClient) {
29
+ const CSS_DISABLE_TRANS = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'
30
+
29
31
  watch(
30
32
  isDark,
31
33
  (v) => {
34
+ const style = window!.document.createElement('style')
35
+ style.appendChild(document.createTextNode(CSS_DISABLE_TRANS))
36
+ window!.document.head.appendChild(style)
37
+
32
38
  const html = document.querySelector('html')!
33
39
  html.classList.toggle('dark', v)
34
40
  html.classList.toggle('light', !v)
41
+
42
+ // Calling getComputedStyle forces the browser to redraw
43
+ // @ts-expect-error unused variable
44
+ const _ = window!.getComputedStyle(style!).opacity
45
+ document.head.removeChild(style!)
35
46
  },
36
47
  { immediate: true },
37
48
  )
@@ -0,0 +1,61 @@
1
+ import { computed, ref } from 'vue'
2
+
3
+ export async function startScreenshotSession(width: number, height: number) {
4
+ const canvas = document.createElement('canvas')
5
+ canvas.width = width
6
+ canvas.height = height
7
+ const context = canvas.getContext('2d')!
8
+ const video = document.createElement('video')
9
+ video.width = width
10
+ video.height = height
11
+
12
+ const captureStream = ref<MediaStream | null>(await navigator.mediaDevices.getDisplayMedia({
13
+ video: {
14
+ // Use a rather small frame rate
15
+ frameRate: 10,
16
+ // @ts-expect-error missing types
17
+ cursor: 'never',
18
+ },
19
+ selfBrowserSurface: 'include',
20
+ preferCurrentTab: true,
21
+ }))
22
+ captureStream.value!.addEventListener('inactive', dispose)
23
+
24
+ video.srcObject = captureStream.value!
25
+ video.play()
26
+
27
+ function screenshot(element: HTMLElement) {
28
+ if (!captureStream.value)
29
+ throw new Error('captureStream inactive')
30
+ context.clearRect(0, 0, width, height)
31
+ const { left, top, width: elWidth } = element.getBoundingClientRect()
32
+ context.drawImage(
33
+ video,
34
+ left * window.devicePixelRatio,
35
+ top * window.devicePixelRatio,
36
+ elWidth * window.devicePixelRatio,
37
+ elWidth / width * height * window.devicePixelRatio,
38
+ 0,
39
+ 0,
40
+ width,
41
+ height,
42
+ )
43
+ return canvas.toDataURL('image/png')
44
+ }
45
+
46
+ function dispose() {
47
+ captureStream.value?.getTracks().forEach(track => track.stop())
48
+ captureStream.value = null
49
+ }
50
+
51
+ return {
52
+ isActive: computed(() => !!captureStream.value),
53
+ screenshot,
54
+ dispose,
55
+ }
56
+ }
57
+
58
+ export type ScreenshotSession = Awaited<ReturnType<typeof startScreenshotSession>>
59
+
60
+ const chromeVersion = window.navigator.userAgent.match(/Chrome\/(\d+)/)?.[1]
61
+ export const isScreenshotSupported = chromeVersion ? Number(chromeVersion) >= 94 : false
@@ -4,46 +4,15 @@ import type { Ref } from 'vue'
4
4
  import { onKeyStroke } from '@vueuse/core'
5
5
  import { and, not } from '@vueuse/math'
6
6
  import { watch } from 'vue'
7
+ import { useNav } from '../composables/useNav'
7
8
  import setupShortcuts from '../setup/shortcuts'
8
9
  import { fullscreen, isInputting, isOnFocus, magicKeys, shortcutsEnabled } from '../state'
9
10
 
10
- const _shortcut = and(not(isInputting), not(isOnFocus), shortcutsEnabled)
11
-
12
- export function shortcut(key: string | Ref<boolean>, fn: Fn, autoRepeat = false) {
13
- if (typeof key === 'string')
14
- key = magicKeys[key]
15
-
16
- const source = and(key, _shortcut)
17
- let count = 0
18
- let timer: any
19
- const trigger = () => {
20
- clearTimeout(timer)
21
- if (!source.value) {
22
- count = 0
23
- return
24
- }
25
- if (autoRepeat) {
26
- timer = setTimeout(trigger, Math.max(1000 - count * 250, 150))
27
- count++
28
- }
29
- fn()
30
- }
31
-
32
- return watch(source, trigger, { flush: 'sync' })
33
- }
34
-
35
- export function strokeShortcut(key: KeyFilter, fn: Fn) {
36
- return onKeyStroke(key, (ev) => {
37
- if (!_shortcut.value)
38
- return
39
- if (!ev.repeat)
40
- fn()
41
- })
42
- }
43
-
44
11
  export function registerShortcuts() {
45
- const allShortcuts = setupShortcuts()
12
+ const { isPrintMode } = useNav()
13
+ const enabled = and(not(isInputting), not(isOnFocus), not(isPrintMode), shortcutsEnabled)
46
14
 
15
+ const allShortcuts = setupShortcuts()
47
16
  const shortcuts = new Map<string | Ref<boolean>, ShortcutOptions>(
48
17
  allShortcuts.map((options: ShortcutOptions) => [options.key, options]),
49
18
  )
@@ -54,4 +23,36 @@ export function registerShortcuts() {
54
23
  })
55
24
 
56
25
  strokeShortcut('f', () => fullscreen.toggle())
26
+
27
+ function shortcut(key: string | Ref<boolean>, fn: Fn, autoRepeat = false) {
28
+ if (typeof key === 'string')
29
+ key = magicKeys[key]
30
+
31
+ const source = and(key, enabled)
32
+ let count = 0
33
+ let timer: any
34
+ const trigger = () => {
35
+ clearTimeout(timer)
36
+ if (!source.value) {
37
+ count = 0
38
+ return
39
+ }
40
+ if (autoRepeat) {
41
+ timer = setTimeout(trigger, Math.max(1000 - count * 250, 150))
42
+ count++
43
+ }
44
+ fn()
45
+ }
46
+
47
+ return watch(source, trigger, { flush: 'sync' })
48
+ }
49
+
50
+ function strokeShortcut(key: KeyFilter, fn: Fn) {
51
+ return onKeyStroke(key, (ev) => {
52
+ if (!enabled.value)
53
+ return
54
+ if (!ev.repeat)
55
+ fn()
56
+ })
57
+ }
57
58
  }
package/logic/slides.ts CHANGED
@@ -15,11 +15,12 @@ export function getSlide(no: number | string) {
15
15
  export function getSlidePath(
16
16
  route: SlideRoute | number | string,
17
17
  presenter: boolean,
18
+ exporting: boolean = false,
18
19
  ) {
19
20
  if (typeof route === 'number' || typeof route === 'string')
20
21
  route = getSlide(route)!
21
22
  const no = route.meta.slide?.frontmatter.routeAlias ?? route.no
22
- return presenter ? `/presenter/${no}` : `/${no}`
23
+ return exporting ? `/export/${no}` : presenter ? `/presenter/${no}` : `/${no}`
23
24
  }
24
25
 
25
26
  export function useIsSlideActive() {
package/modules/v-mark.ts CHANGED
@@ -3,6 +3,7 @@ import type { RawAtValue } from '@slidev/types'
3
3
  import type { App } from 'vue'
4
4
  import { annotate } from '@slidev/rough-notation'
5
5
  import { computed, watchEffect } from 'vue'
6
+ import { useNav } from '../composables/useNav'
6
7
  import { resolveClick } from './v-click'
7
8
 
8
9
  export interface RoughDirectiveOptions extends Partial<RoughAnnotationConfig> {
@@ -78,6 +79,8 @@ export function createVMarkDirective() {
78
79
  name: 'v-mark',
79
80
 
80
81
  mounted: (el, binding) => {
82
+ const { isPrintMode } = useNav()
83
+
81
84
  const options = computed(() => {
82
85
  const bindingOptions = (typeof binding.value === 'object' && !Array.isArray(binding.value))
83
86
  ? { ...binding.value }
@@ -109,6 +112,9 @@ export function createVMarkDirective() {
109
112
  }
110
113
  options.type ||= 'underline'
111
114
 
115
+ if (isPrintMode.value)
116
+ options.animationDuration = 1 /* millisecond */
117
+
112
118
  return options
113
119
  })
114
120
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.50.0-beta.9",
4
+ "version": "0.50.0",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -29,15 +29,15 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@antfu/utils": "^0.7.10",
32
- "@iconify-json/carbon": "^1.2.4",
33
- "@iconify-json/ph": "^1.2.1",
34
- "@iconify-json/svg-spinners": "^1.2.1",
35
- "@shikijs/monaco": "^1.24.0",
36
- "@shikijs/vitepress-twoslash": "^1.24.0",
32
+ "@iconify-json/carbon": "^1.2.5",
33
+ "@iconify-json/ph": "^1.2.2",
34
+ "@iconify-json/svg-spinners": "^1.2.2",
35
+ "@shikijs/monaco": "^1.24.2",
36
+ "@shikijs/vitepress-twoslash": "^1.24.2",
37
37
  "@slidev/rough-notation": "^0.1.0",
38
38
  "@typescript/ata": "^0.9.7",
39
- "@unhead/vue": "^1.11.13",
40
- "@unocss/reset": "^0.65.0",
39
+ "@unhead/vue": "^1.11.14",
40
+ "@unocss/reset": "^0.65.1",
41
41
  "@vueuse/core": "^12.0.0",
42
42
  "@vueuse/math": "^12.0.0",
43
43
  "@vueuse/motion": "^2.2.6",
@@ -46,23 +46,25 @@
46
46
  "floating-vue": "^5.2.2",
47
47
  "fuse.js": "^7.0.0",
48
48
  "html-to-image": "^1.11.11",
49
- "katex": "^0.16.11",
49
+ "katex": "^0.16.17",
50
50
  "lz-string": "^1.5.0",
51
51
  "mermaid": "^11.4.1",
52
52
  "monaco-editor": "0.51.0",
53
- "prettier": "^3.4.1",
53
+ "nanotar": "^0.1.1",
54
+ "pptxgenjs": "^3.12.0",
55
+ "prettier": "^3.4.2",
54
56
  "recordrtc": "^5.6.2",
55
- "shiki": "^1.24.0",
56
- "shiki-magic-move": "^0.5.0",
57
+ "shiki": "^1.24.2",
58
+ "shiki-magic-move": "^0.5.2",
57
59
  "typescript": "5.6.3",
58
- "unocss": "^0.65.0",
60
+ "unocss": "^0.65.1",
59
61
  "vue": "^3.5.13",
60
62
  "vue-router": "^4.5.0",
61
63
  "yaml": "^2.6.1",
62
- "@slidev/parser": "0.50.0-beta.9",
63
- "@slidev/types": "0.50.0-beta.9"
64
+ "@slidev/parser": "0.50.0",
65
+ "@slidev/types": "0.50.0"
64
66
  },
65
67
  "devDependencies": {
66
- "vite": "^6.0.2"
68
+ "vite": "^6.0.3"
67
69
  }
68
70
  }