@slidev/client 0.48.0-beta.9 → 0.48.1

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 (97) hide show
  1. package/App.vue +7 -0
  2. package/builtin/Arrow.vue +2 -4
  3. package/builtin/CodeBlockWrapper.vue +33 -28
  4. package/builtin/KaTexBlockWrapper.vue +1 -1
  5. package/builtin/Link.vue +3 -1
  6. package/builtin/Mermaid.vue +4 -3
  7. package/builtin/Monaco.vue +166 -93
  8. package/builtin/ShikiMagicMove.vue +103 -0
  9. package/builtin/SlidevVideo.vue +1 -1
  10. package/builtin/Toc.vue +1 -1
  11. package/builtin/TocList.vue +4 -3
  12. package/builtin/Tweet.vue +12 -9
  13. package/builtin/VClick.ts +2 -1
  14. package/composables/useClicks.ts +19 -32
  15. package/composables/useDarkMode.ts +9 -0
  16. package/composables/useDrawings.ts +181 -0
  17. package/composables/useNav.ts +346 -44
  18. package/{logic/note.ts → composables/useSlideInfo.ts} +13 -16
  19. package/composables/useSwipeControls.ts +43 -0
  20. package/composables/useTocTree.ts +81 -0
  21. package/composables/useViewTransition.ts +7 -4
  22. package/constants.ts +4 -3
  23. package/context.ts +13 -6
  24. package/env.ts +7 -16
  25. package/index.html +1 -0
  26. package/index.ts +12 -0
  27. package/internals/ClicksSlider.vue +97 -0
  28. package/internals/CodeRunner.vue +142 -0
  29. package/internals/Controls.vue +2 -2
  30. package/internals/DomElement.vue +18 -0
  31. package/internals/DrawingControls.vue +14 -15
  32. package/internals/DrawingLayer.vue +6 -5
  33. package/internals/DrawingPreview.vue +4 -2
  34. package/internals/Goto.vue +9 -6
  35. package/internals/IconButton.vue +3 -2
  36. package/internals/NavControls.vue +30 -11
  37. package/internals/NoteDisplay.vue +131 -8
  38. package/internals/NoteEditable.vue +129 -0
  39. package/internals/NoteStatic.vue +5 -2
  40. package/internals/PrintContainer.vue +11 -8
  41. package/internals/PrintSlide.vue +11 -12
  42. package/internals/PrintSlideClick.vue +14 -19
  43. package/internals/{SlidesOverview.vue → QuickOverview.vue} +27 -24
  44. package/internals/RecordingControls.vue +1 -1
  45. package/internals/RecordingDialog.vue +3 -3
  46. package/internals/{Editor.vue → SideEditor.vue} +24 -15
  47. package/internals/SlideContainer.vue +13 -9
  48. package/internals/SlideLoading.vue +19 -0
  49. package/internals/SlideWrapper.vue +79 -0
  50. package/internals/SlidesShow.vue +36 -22
  51. package/layouts/error.vue +5 -0
  52. package/layouts/two-cols-header.vue +9 -3
  53. package/logic/overview.ts +2 -2
  54. package/logic/route.ts +16 -5
  55. package/logic/slides.ts +20 -0
  56. package/logic/transition.ts +50 -0
  57. package/logic/utils.ts +24 -1
  58. package/main.ts +3 -15
  59. package/{setup → modules}/codemirror.ts +1 -3
  60. package/modules/context.ts +1 -46
  61. package/modules/mermaid.ts +9 -8
  62. package/package.json +21 -15
  63. package/pages/notes.vue +6 -3
  64. package/pages/overview.vue +139 -51
  65. package/pages/play.vue +16 -9
  66. package/pages/presenter/print.vue +10 -5
  67. package/pages/presenter.vue +122 -104
  68. package/pages/print.vue +4 -3
  69. package/routes.ts +8 -54
  70. package/setup/code-runners.ts +164 -0
  71. package/setup/main.ts +39 -9
  72. package/setup/mermaid.ts +5 -6
  73. package/setup/monaco.ts +114 -51
  74. package/setup/root.ts +62 -18
  75. package/setup/shortcuts.ts +15 -12
  76. package/shim-vue.d.ts +34 -0
  77. package/shim.d.ts +1 -13
  78. package/state/index.ts +2 -2
  79. package/styles/code.css +9 -5
  80. package/styles/index.css +63 -7
  81. package/styles/katex.css +1 -1
  82. package/styles/layouts-base.css +11 -8
  83. package/styles/shiki-twoslash.css +1 -1
  84. package/styles/vars.css +1 -1
  85. package/uno.config.ts +10 -1
  86. package/utils.ts +15 -2
  87. package/composables/useContext.ts +0 -17
  88. package/composables/useTweetScript.ts +0 -17
  89. package/iframes/monaco/index.css +0 -28
  90. package/iframes/monaco/index.html +0 -7
  91. package/iframes/monaco/index.ts +0 -260
  92. package/internals/NoteEditor.vue +0 -92
  93. package/internals/SlideWrapper.ts +0 -58
  94. package/logic/drawings.ts +0 -161
  95. package/logic/nav.ts +0 -278
  96. package/setup/prettier.ts +0 -43
  97. /package/{composables → logic}/hmr.ts +0 -0
@@ -9,14 +9,14 @@ export interface UseSlideInfo {
9
9
  update: (data: SlidePatch) => Promise<SlideInfo | void>
10
10
  }
11
11
 
12
- export function useSlideInfo(id: number | undefined): UseSlideInfo {
13
- if (id == null) {
12
+ export function useSlideInfo(no: number): UseSlideInfo {
13
+ if (no == null) {
14
14
  return {
15
15
  info: ref() as Ref<SlideInfo | undefined>,
16
16
  update: async () => {},
17
17
  }
18
18
  }
19
- const url = `/@slidev/slide/${id}.json`
19
+ const url = `/@slidev/slide/${no}.json`
20
20
  const { data: info, execute } = useFetch(url).json().get()
21
21
 
22
22
  execute()
@@ -36,12 +36,12 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
36
36
  }
37
37
 
38
38
  if (__DEV__) {
39
- import.meta.hot?.on('slidev-update', (payload) => {
40
- if (payload.id === id)
39
+ import.meta.hot?.on('slidev:update-slide', (payload) => {
40
+ if (payload.no === no)
41
41
  info.value = payload.data
42
42
  })
43
- import.meta.hot?.on('slidev-update-note', (payload) => {
44
- if (payload.id === id && info.value.note?.trim() !== payload.note?.trim())
43
+ import.meta.hot?.on('slidev:update-note', (payload) => {
44
+ if (payload.no === no && info.value.note?.trim() !== payload.note?.trim())
45
45
  info.value = { ...info.value, ...payload }
46
46
  })
47
47
  }
@@ -52,20 +52,17 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
52
52
  }
53
53
  }
54
54
 
55
- const map: Record<string, UseSlideInfo> = {}
55
+ const map: Record<number, UseSlideInfo> = {}
56
56
 
57
- export function useDynamicSlideInfo(id: MaybeRef<number | undefined>) {
58
- function get(id: number | undefined) {
59
- const i = `${id}`
60
- if (!map[i])
61
- map[i] = useSlideInfo(id)
62
- return map[i]
57
+ export function useDynamicSlideInfo(no: MaybeRef<number>) {
58
+ function get(no: number) {
59
+ return map[no] ??= useSlideInfo(no)
63
60
  }
64
61
 
65
62
  return {
66
- info: computed(() => get(unref(id)).info.value),
63
+ info: computed(() => get(unref(no)).info.value),
67
64
  update: async (data: SlidePatch, newId?: number) => {
68
- const info = get(newId ?? unref(id))
65
+ const info = get(newId ?? unref(no))
69
66
  const newData = await info.update(data)
70
67
  if (newData)
71
68
  info.info.value = newData
@@ -0,0 +1,43 @@
1
+ import type { Ref } from 'vue'
2
+ import { ref } from 'vue'
3
+ import { timestamp, usePointerSwipe } from '@vueuse/core'
4
+ import { useNav } from '../composables/useNav'
5
+ import { useDrawings } from './useDrawings'
6
+
7
+ export function useSwipeControls(root: Ref<HTMLElement | undefined>) {
8
+ const { next, nextSlide, prev, prevSlide } = useNav()
9
+ const { isDrawing } = useDrawings()
10
+
11
+ const swipeBegin = ref(0)
12
+ const { direction, distanceX, distanceY } = usePointerSwipe(root, {
13
+ pointerTypes: ['touch'],
14
+ onSwipeStart() {
15
+ if (isDrawing.value)
16
+ return
17
+ swipeBegin.value = timestamp()
18
+ },
19
+ onSwipeEnd() {
20
+ if (!swipeBegin.value)
21
+ return
22
+ if (isDrawing.value)
23
+ return
24
+
25
+ const x = Math.abs(distanceX.value)
26
+ const y = Math.abs(distanceY.value)
27
+ if (x / window.innerWidth > 0.3 || x > 75) {
28
+ if (direction.value === 'left')
29
+ next()
30
+
31
+ else
32
+ prev()
33
+ }
34
+ else if (y / window.innerHeight > 0.4 || y > 200) {
35
+ if (direction.value === 'down')
36
+ prevSlide()
37
+
38
+ else
39
+ nextSlide()
40
+ }
41
+ },
42
+ })
43
+ }
@@ -0,0 +1,81 @@
1
+ import type { SlideRoute, TocItem } from '@slidev/types'
2
+ import type { ComputedRef, Ref } from 'vue'
3
+ import { computed } from 'vue'
4
+ import { getSlidePath } from '../logic/slides'
5
+
6
+ function addToTree(tree: TocItem[], route: SlideRoute, level = 1) {
7
+ const titleLevel = route.meta?.slide?.level
8
+ if (titleLevel && titleLevel > level && tree.length > 0) {
9
+ addToTree(tree[tree.length - 1].children, route, level + 1)
10
+ }
11
+ else {
12
+ tree.push({
13
+ no: route.no,
14
+ children: [],
15
+ level,
16
+ path: getSlidePath(route.meta.slide?.frontmatter?.routeAlias ?? route.no, false),
17
+ hideInToc: Boolean(route.meta?.slide?.frontmatter?.hideInToc),
18
+ title: route.meta?.slide?.title,
19
+ })
20
+ }
21
+ }
22
+
23
+ function getTreeWithActiveStatuses(
24
+ tree: TocItem[],
25
+ currentRoute?: SlideRoute,
26
+ hasActiveParent = false,
27
+ parent?: TocItem,
28
+ currentSlideNo?: Ref<number>,
29
+ ): TocItem[] {
30
+ return tree.map((item: TocItem) => {
31
+ const clone = {
32
+ ...item,
33
+ active: item.no === currentSlideNo?.value,
34
+ hasActiveParent,
35
+ }
36
+ if (clone.children.length > 0) {
37
+ clone.children = getTreeWithActiveStatuses(
38
+ clone.children,
39
+ currentRoute,
40
+ clone.active || clone.hasActiveParent,
41
+ clone,
42
+ currentSlideNo,
43
+ )
44
+ }
45
+ if (parent && (clone.active || clone.activeParent))
46
+ parent.activeParent = true
47
+ return clone
48
+ })
49
+ }
50
+
51
+ function filterTree(tree: TocItem[], level = 1): TocItem[] {
52
+ return tree
53
+ .filter((item: TocItem) => !item.hideInToc)
54
+ .map((item: TocItem) => ({
55
+ ...item,
56
+ children: filterTree(item.children, level + 1),
57
+ }))
58
+ }
59
+
60
+ export function useTocTree(
61
+ slides: Ref<SlideRoute[]>,
62
+ currentSlideNo: Ref<number>,
63
+ currentSlideRoute: Ref<SlideRoute>,
64
+ ): ComputedRef<TocItem[]> {
65
+ const rawTree = computed(() => slides.value
66
+ .filter((route: SlideRoute) => route.meta?.slide?.title)
67
+ .reduce((acc: TocItem[], route: SlideRoute) => {
68
+ addToTree(acc, route)
69
+ return acc
70
+ }, []))
71
+
72
+ const treeWithActiveStatuses = computed(() => getTreeWithActiveStatuses(
73
+ rawTree.value,
74
+ currentSlideRoute.value,
75
+ undefined,
76
+ undefined,
77
+ currentSlideNo,
78
+ ))
79
+
80
+ return computed(() => filterTree(treeWithActiveStatuses.value))
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { ref } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
+ import { getSlide } from '../logic/slides'
3
4
 
4
5
  export function useViewTransition() {
5
6
  const router = useRouter()
@@ -11,13 +12,15 @@ export function useViewTransition() {
11
12
  const supportViewTransition = typeof document !== 'undefined' && 'startViewTransition' in document
12
13
 
13
14
  router.beforeResolve((to, from) => {
14
- const fromNo = from.meta.slide?.no
15
- const toNo = to.meta.slide?.no
15
+ const fromMeta = getSlide(from.params.no as string)?.meta
16
+ const toMeta = getSlide(to.params.no as string)?.meta
17
+ const fromNo = fromMeta?.slide?.no
18
+ const toNo = toMeta?.slide?.no
16
19
  if (
17
20
  !(
18
21
  fromNo !== undefined && toNo !== undefined && (
19
- (from.meta.transition === 'view-transition' && fromNo < toNo)
20
- || (to.meta.transition === 'view-transition' && toNo < fromNo)
22
+ (fromMeta?.transition === 'view-transition' && fromNo < toNo)
23
+ || (toMeta?.transition === 'view-transition' && toNo < fromNo)
21
24
  )
22
25
  )
23
26
  ) {
package/constants.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { ComputedRef, InjectionKey, Ref, UnwrapNestedRefs } from 'vue'
2
- import type { RouteRecordRaw } from 'vue-router'
3
- import type { ClicksContext, RenderContext } from '@slidev/types'
2
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
4
3
  import type { SlidevContext } from './modules/context'
5
4
 
6
5
  // Here we use string literal instead of symbols to make HMR more stable
@@ -9,7 +8,7 @@ export const injectionClicksContext = '$$slidev-clicks-context' as unknown as In
9
8
  export const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>
10
9
  export const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>
11
10
  export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>
12
- export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<RouteRecordRaw>
11
+ export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<SlideRoute>
13
12
  export const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey<Ref<RenderContext>>
14
13
  export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
15
14
  export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
@@ -22,6 +21,8 @@ export const CLASS_VCLICK_HIDDEN_EXP = 'slidev-vclick-hidden-explicitly'
22
21
  export const CLASS_VCLICK_CURRENT = 'slidev-vclick-current'
23
22
  export const CLASS_VCLICK_PRIOR = 'slidev-vclick-prior'
24
23
 
24
+ export const CLICKS_MAX = 999999
25
+
25
26
  export const TRUST_ORIGINS = [
26
27
  'localhost',
27
28
  '127.0.0.1',
package/context.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { shallowRef, toRef } from 'vue'
1
+ import { ref, toRef } from 'vue'
2
2
  import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
3
- import { useFixedClicks } from './composables/useClicks'
4
3
  import {
5
4
  FRONTMATTER_FIELDS,
6
5
  HEADMATTER_FIELDS,
@@ -9,23 +8,23 @@ import {
9
8
  injectionFrontmatter,
10
9
  injectionRenderContext,
11
10
  injectionRoute,
11
+ injectionSlideScale,
12
12
  injectionSlidevContext,
13
13
  } from './constants'
14
14
 
15
- const clicksContextFallback = shallowRef(useFixedClicks()[1])
16
-
17
15
  /**
18
16
  * Get the current slide context, should be called inside the setup function of a component inside slide
19
17
  */
20
18
  export function useSlideContext() {
21
19
  const $slidev = injectLocal(injectionSlidevContext)!
22
20
  const $nav = toRef($slidev, 'nav')
23
- const $clicksContext = injectLocal(injectionClicksContext, clicksContextFallback)!.value
21
+ const $clicksContext = injectLocal(injectionClicksContext)!.value
24
22
  const $clicks = toRef($clicksContext, 'current')
25
23
  const $page = injectLocal(injectionCurrentPage)!
26
24
  const $renderContext = injectLocal(injectionRenderContext)!
27
25
  const $frontmatter = injectLocal(injectionFrontmatter, {})
28
26
  const $route = injectLocal(injectionRoute, undefined)
27
+ const $scale = injectLocal(injectionSlideScale, ref(1))!
29
28
 
30
29
  return {
31
30
  $slidev,
@@ -36,9 +35,15 @@ export function useSlideContext() {
36
35
  $route,
37
36
  $renderContext,
38
37
  $frontmatter,
38
+ $scale,
39
39
  }
40
40
  }
41
41
 
42
+ export type SlideContext = ReturnType<typeof useSlideContext>
43
+
44
+ /**
45
+ * @internal
46
+ */
42
47
  export function provideFrontmatter(frontmatter: Record<string, any>) {
43
48
  provideLocal(injectionFrontmatter, frontmatter)
44
49
 
@@ -48,7 +53,7 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
48
53
  } = useSlideContext()
49
54
 
50
55
  // update frontmatter in router to make HMR work better
51
- const route = $slidev.nav.rawRoutes.find(i => i.path === String($page.value))
56
+ const route = $slidev.nav.slides.find(i => i.no === $page.value)
52
57
  if (route?.meta?.slide?.frontmatter) {
53
58
  for (const key of Object.keys(route.meta.slide.frontmatter)) {
54
59
  if (!(key in frontmatter))
@@ -61,6 +66,8 @@ export function provideFrontmatter(frontmatter: Record<string, any>) {
61
66
  /**
62
67
  * Convert frontmatter options to props for v-bind
63
68
  * It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter
69
+ *
70
+ * @internal
64
71
  */
65
72
  export function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {
66
73
  return {
package/env.ts CHANGED
@@ -1,25 +1,16 @@
1
- import type { SlidevConfig } from '@slidev/types'
2
- import type { UnwrapNestedRefs } from 'vue'
3
- import { computed } from 'vue'
1
+ import { computed, ref } from 'vue'
4
2
  import { objectMap } from '@antfu/utils'
3
+ import configs from '#slidev/configs'
5
4
 
6
- // @ts-expect-error missing types
7
- import _configs from '/@slidev/configs'
8
- import type { SlidevContext } from './modules/context'
5
+ export { configs }
6
+
7
+ export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
8
+ export const slideWidth = ref(configs.canvasWidth ?? 980)
9
9
 
10
- export const configs = _configs as SlidevConfig
11
- export const slideAspect = configs.aspectRatio ?? (16 / 9)
12
- export const slideWidth = configs.canvasWidth ?? 980
13
10
  // To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
14
11
  // Doing this, we will prevent on print, to create an additional empty white page after each page.
15
- export const slideHeight = Math.ceil(slideWidth / slideAspect)
12
+ export const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspect.value))
16
13
 
17
14
  export const themeVars = computed(() => {
18
15
  return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
19
16
  })
20
-
21
- declare module 'vue' {
22
- interface ComponentCustomProperties {
23
- $slidev: UnwrapNestedRefs<SlidevContext>
24
- }
25
- }
package/index.html CHANGED
@@ -8,6 +8,7 @@
8
8
  <body>
9
9
  <div id="app"></div>
10
10
  <script type="module" src="__ENTRY__"></script>
11
+ <div id="mermaid-rendering-container"></div>
11
12
  <!-- body -->
12
13
  </body>
13
14
  </html>
package/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * This file is the public APIs that you might use in your app.
3
+ *
4
+ * The other files despite they are accessable, are not meant to be used directly, breaking changes might happen.
5
+ */
6
+ export { useSlideContext } from './context'
7
+ export { useNav } from './composables/useNav'
8
+ export { useDrawings } from './composables/useDrawings'
9
+ export { useDarkMode } from './composables/useDarkMode'
10
+
11
+ export * from './layoutHelper'
12
+ export * from './env'
@@ -0,0 +1,97 @@
1
+ <script setup lang="ts">
2
+ import type { ClicksContext } from '@slidev/types'
3
+ import { computed } from 'vue'
4
+ import { CLICKS_MAX } from '../constants'
5
+
6
+ const props = defineProps<{
7
+ clicksContext: ClicksContext
8
+ }>()
9
+
10
+ const total = computed(() => props.clicksContext.total)
11
+ const current = computed({
12
+ get() {
13
+ return props.clicksContext.current > total.value ? -1 : props.clicksContext.current
14
+ },
15
+ set(value: number) {
16
+ // eslint-disable-next-line vue/no-mutating-props
17
+ props.clicksContext.current = value
18
+ },
19
+ })
20
+
21
+ const range = computed(() => Array.from({ length: total.value + 1 }, (_, i) => i))
22
+
23
+ function onMousedown() {
24
+ if (current.value < 0 || current.value > total.value)
25
+ current.value = 0
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <div
31
+ class="flex gap-0.5 items-center select-none"
32
+ :title="`Clicks in this slide: ${total}`"
33
+ :class="total ? '' : 'op50'"
34
+ >
35
+ <div class="flex gap-1 items-center min-w-16 tabular-nums">
36
+ <carbon:cursor-1 text-sm op50 />
37
+ <template v-if="current >= 0 && current !== CLICKS_MAX">
38
+ <div flex-auto />
39
+ <span text-primary>{{ current }}</span>
40
+ <span op25>/</span>
41
+ </template>
42
+ <span op50>{{ total }}</span>
43
+ </div>
44
+ <div
45
+ relative flex-auto h5 flex="~"
46
+ @dblclick="current = clicksContext.total"
47
+ >
48
+ <div
49
+ v-for="i of range" :key="i"
50
+ border="y main" of-hidden relative
51
+ :class="[
52
+ i === 0 ? 'rounded-l border-l' : '',
53
+ i === total ? 'rounded-r border-r' : '',
54
+ ]"
55
+ :style="{ width: total > 0 ? `${1 / total * 100}%` : '100%' }"
56
+ >
57
+ <div absolute inset-0 :class="i <= current ? 'bg-primary op20' : ''" />
58
+ <div
59
+ :class="[
60
+ +i === +current ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
61
+ i === 0 ? 'rounded-l' : '',
62
+ i === total ? 'rounded-r' : 'border-r-2',
63
+ ]"
64
+ w-full h-full text-xs flex items-center justify-center z-1
65
+ >
66
+ {{ i }}
67
+ </div>
68
+ </div>
69
+ <input
70
+ v-model="current"
71
+ class="range" absolute inset-0
72
+ type="range" :min="0" :max="total" :step="1" z-10 op0
73
+ :style="{ '--thumb-width': `${1 / (total + 1) * 100}%` }"
74
+ @mousedown="onMousedown"
75
+ @focus="event => (event.currentTarget as HTMLElement)?.blur()"
76
+ >
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <style scoped>
82
+ .range {
83
+ -webkit-appearance: none;
84
+ appearance: none;
85
+ background: transparent;
86
+ }
87
+ .range::-webkit-slider-thumb {
88
+ -webkit-appearance: none;
89
+ height: 100%;
90
+ width: var(--thumb-width, 0.5rem);
91
+ }
92
+
93
+ .range::-moz-range-thumb {
94
+ height: 100%;
95
+ width: var(--thumb-width, 0.5rem);
96
+ }
97
+ </style>
@@ -0,0 +1,142 @@
1
+ <script setup lang="ts">
2
+ import { debounce, toArray } from '@antfu/utils'
3
+ import { useVModel } from '@vueuse/core'
4
+ import type { CodeRunnerOutput } from '@slidev/types'
5
+ import { computed, ref, shallowRef, watch } from 'vue'
6
+ import { useSlideContext } from '../context'
7
+ import setupCodeRunners from '../setup/code-runners'
8
+ import { useNav } from '../composables/useNav'
9
+ import IconButton from './IconButton.vue'
10
+ import DomElement from './DomElement.vue'
11
+
12
+ const props = defineProps<{
13
+ modelValue: string
14
+ lang: string
15
+ autorun: boolean | 'once'
16
+ height?: string
17
+ highlightOutput: boolean
18
+ runnerOptions?: Record<string, unknown>
19
+ }>()
20
+
21
+ const emit = defineEmits(['update:modelValue'])
22
+
23
+ const { isPrintMode } = useNav()
24
+
25
+ const code = useVModel(props, 'modelValue', emit)
26
+
27
+ const { $renderContext } = useSlideContext()
28
+ const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value))
29
+
30
+ const autorun = isPrintMode.value ? 'once' : props.autorun
31
+ const isRunning = ref(autorun)
32
+ const outputs = shallowRef<CodeRunnerOutput[]>()
33
+ const runCount = ref(0)
34
+ const highlightFn = ref<(code: string, lang: string) => string>()
35
+
36
+ const triggerRun = debounce(200, async () => {
37
+ if (disabled.value)
38
+ return
39
+
40
+ const { highlight, run } = await setupCodeRunners()
41
+ highlightFn.value = highlight
42
+
43
+ const setAsRunning = setTimeout(() => {
44
+ isRunning.value = true
45
+ }, 500)
46
+
47
+ outputs.value = toArray(await run(code.value, props.lang, props.runnerOptions ?? {}))
48
+ runCount.value += 1
49
+ isRunning.value = false
50
+
51
+ clearTimeout(setAsRunning)
52
+ })
53
+
54
+ if (autorun === 'once')
55
+ triggerRun()
56
+ else if (autorun)
57
+ watch(code, triggerRun, { immediate: true })
58
+ </script>
59
+
60
+ <template>
61
+ <div
62
+ class="relative flex flex-col rounded-b border-t border-main"
63
+ :style="{ height: props.height }"
64
+ data-waitfor=".slidev-runner-output"
65
+ >
66
+ <div v-if="disabled" class="text-sm text-center opacity-50">
67
+ Code is disabled in the "{{ $renderContext }}" mode
68
+ </div>
69
+ <div v-else-if="isRunning" class="text-sm text-center opacity-50">
70
+ Running...
71
+ </div>
72
+ <div v-else-if="!outputs?.length" class="text-sm text-center opacity-50">
73
+ Click the play button to run the code
74
+ </div>
75
+ <div v-else :key="`run-${runCount}`" class="slidev-runner-output">
76
+ <template v-for="output, _idx1 of outputs" :key="_idx1">
77
+ <div v-if="'html' in output" v-html="output.html" />
78
+ <div v-else-if="'error' in output" class="text-red-500">
79
+ {{ output.error }}
80
+ </div>
81
+ <DomElement v-else-if="'element' in output" :element="output.element" />
82
+ <div v-else class="output-line">
83
+ <template
84
+ v-for="item, idx2 in toArray(output)"
85
+ :key="idx2"
86
+ >
87
+ <span
88
+ v-if="item.highlightLang && highlightFn"
89
+ class="highlighted"
90
+ v-html="highlightFn(item.text, item.highlightLang)"
91
+ />
92
+ <span v-else :class="item.class">{{ item.text }}</span>
93
+ <span v-if="idx2 < toArray(output).length - 1" class="separator">,</span>
94
+ </template>
95
+ </div>
96
+ </template>
97
+ </div>
98
+ </div>
99
+ <div v-if="code.trim()" class="absolute right-1 top-1 max-h-full flex gap-1">
100
+ <IconButton class="w-8 h-8 max-h-full flex justify-center items-center" title="Run code" @click="triggerRun">
101
+ <carbon:play />
102
+ </IconButton>
103
+ </div>
104
+ </template>
105
+
106
+ <style lang="postcss">
107
+ .slidev-runner-output {
108
+ @apply px-5 py-3 flex-grow text-xs leading-[.8rem] font-$slidev-code-font-family select-text;
109
+ }
110
+
111
+ .slidev-runner-output .log-type {
112
+ @apply font-bold op-70;
113
+
114
+ &.DBG {
115
+ @apply text-gray-500;
116
+ }
117
+
118
+ &.LOG {
119
+ @apply text-blue-500;
120
+ }
121
+
122
+ &.WRN {
123
+ @apply text-orange-500;
124
+ }
125
+
126
+ &.ERR {
127
+ @apply text-red-500;
128
+ }
129
+ }
130
+
131
+ .slidev-runner-output .output-line {
132
+ @apply flex my-1 w-full;
133
+ }
134
+
135
+ .slidev-runner-output .separator {
136
+ @apply op-40 mr-1;
137
+ }
138
+
139
+ .slidev-runner-output .highlighted > pre {
140
+ @apply inline text-wrap !bg-transparent;
141
+ }
142
+ </style>
@@ -2,7 +2,7 @@
2
2
  import { shallowRef } from 'vue'
3
3
  import { showInfoDialog, showOverview, showRecordingDialog } from '../state'
4
4
  import { configs } from '../env'
5
- import SlidesOverview from './SlidesOverview.vue'
5
+ import QuickOverview from './QuickOverview.vue'
6
6
  import InfoDialog from './InfoDialog.vue'
7
7
  import Goto from './Goto.vue'
8
8
 
@@ -15,7 +15,7 @@ if (__SLIDEV_FEATURE_RECORD__) {
15
15
  </script>
16
16
 
17
17
  <template>
18
- <SlidesOverview v-model="showOverview" />
18
+ <QuickOverview v-model="showOverview" />
19
19
  <Goto />
20
20
  <WebCamera v-if="WebCamera" />
21
21
  <RecordingDialog v-if="RecordingDialog" v-model="showRecordingDialog" />
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import { ref, watchEffect } from 'vue'
3
+
4
+ const props = defineProps<{
5
+ element: HTMLElement
6
+ }>()
7
+
8
+ const container = ref<HTMLElement>()
9
+
10
+ watchEffect(() => {
11
+ if (container.value)
12
+ container.value.appendChild(props.element)
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div ref="container" />
18
+ </template>