@slidev/client 0.48.0-beta.22 → 0.48.0-beta.24

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.
@@ -12,11 +12,12 @@ Learn more: https://sli.dev/guide/syntax.html#monaco-editor
12
12
  -->
13
13
 
14
14
  <script setup lang="ts">
15
- import type * as monaco from 'monaco-editor'
16
- import { computed, nextTick, onMounted, ref } from 'vue'
17
15
  import { debounce } from '@antfu/utils'
18
16
  import lz from 'lz-string'
17
+ import type * as monaco from 'monaco-editor'
18
+ import { computed, nextTick, onMounted, ref } from 'vue'
19
19
  import { makeId } from '../logic/utils'
20
+ import CodeRunner from '../internals/CodeRunner.vue'
20
21
 
21
22
  const props = withDefaults(defineProps<{
22
23
  codeLz: string
@@ -27,6 +28,11 @@ const props = withDefaults(defineProps<{
27
28
  height?: number | string // Posible values: 'initial', 'auto', '100%', '200px', etc.
28
29
  editorOptions?: monaco.editor.IEditorOptions
29
30
  ata?: boolean
31
+ runnable?: boolean
32
+ autorun?: boolean | 'once'
33
+ outputHeight?: string
34
+ highlightOutput?: boolean
35
+ runnerOptions?: Record<string, unknown>
30
36
  }>(), {
31
37
  codeLz: '',
32
38
  lang: 'typescript',
@@ -34,10 +40,13 @@ const props = withDefaults(defineProps<{
34
40
  lineNumbers: 'off',
35
41
  height: 'initial',
36
42
  ata: true,
43
+ runnable: false,
44
+ autorun: true,
45
+ highlightOutput: true,
37
46
  })
38
47
 
39
- const code = lz.decompressFromBase64(props.codeLz).trimEnd()
40
- const diff = props.diffLz && lz.decompressFromBase64(props.diffLz).trimEnd()
48
+ const code = ref(lz.decompressFromBase64(props.codeLz).trimEnd())
49
+ const diff = props.diffLz && ref(lz.decompressFromBase64(props.diffLz).trimEnd())
41
50
 
42
51
  const langMap: Record<string, string> = {
43
52
  ts: 'typescript',
@@ -69,7 +78,8 @@ onMounted(async () => {
69
78
  // Lazy load monaco, so it will be bundled in async chunk
70
79
  const { default: setup } = await import('../setup/monaco')
71
80
  const { ata, monaco } = await setup()
72
- const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
81
+ const model = monaco.editor.createModel(code.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
82
+ model.onDidChangeContent(() => code.value = model.getValue())
73
83
  const commonOptions = {
74
84
  automaticLayout: true,
75
85
  readOnly: props.readonly,
@@ -89,7 +99,8 @@ onMounted(async () => {
89
99
 
90
100
  let editableEditor: monaco.editor.IStandaloneCodeEditor
91
101
  if (diff) {
92
- const diffModel = monaco.editor.createModel(diff, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
102
+ const diffModel = monaco.editor.createModel(diff.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
103
+ diffModel.onDidChangeContent(() => code.value = model.getValue())
93
104
  const editor = monaco.editor.createDiffEditor(container.value!, {
94
105
  renderOverviewRuler: false,
95
106
  ...commonOptions,
@@ -140,11 +151,56 @@ onMounted(async () => {
140
151
  : /* BELOW */ `` // reset
141
152
  }
142
153
  }
154
+ nextTick(() => monaco.editor.remeasureFonts())
143
155
  })
144
156
  </script>
145
157
 
146
158
  <template>
147
- <div ref="outer" class="slidev-monaco-container" :style="{ height }">
148
- <div ref="container" class="absolute inset-0.5" />
159
+ <div class="relative slidev-monaco-container">
160
+ <div ref="outer" class="relative slidev-monaco-container-inner" :style="{ height }">
161
+ <div ref="container" class="absolute inset-0.5" />
162
+ </div>
163
+ <CodeRunner
164
+ v-if="props.runnable"
165
+ v-model="code"
166
+ :lang="lang"
167
+ :autorun="props.autorun"
168
+ :height="props.outputHeight"
169
+ :highlight-output="props.highlightOutput"
170
+ :runner-options="props.runnerOptions"
171
+ />
149
172
  </div>
150
173
  </template>
174
+
175
+ <style>
176
+ div[widgetid='messageoverlay'] {
177
+ transform: translateY(calc(100% * (var(--slidev-slide-scale) - 1)));
178
+ }
179
+
180
+ .slidev-monaco-container {
181
+ position: relative;
182
+ margin: var(--slidev-code-margin);
183
+ line-height: var(--slidev-code-line-height);
184
+ border-radius: var(--slidev-code-radius);
185
+ background: var(--slidev-code-background);
186
+ }
187
+
188
+ .slidev-monaco-container-inner {
189
+ padding: var(--slidev-code-padding);
190
+ }
191
+
192
+ .slidev-monaco-container .monaco-editor {
193
+ --monaco-monospace-font: var(--slidev-code-font-family);
194
+ --vscode-editor-background: var(--slidev-code-background);
195
+ --vscode-editorGutter-background: var(--slidev-code-background);
196
+ }
197
+
198
+ /** Revert styles */
199
+ .slidev-monaco-container .monaco-editor a {
200
+ border-bottom: none;
201
+ }
202
+
203
+ .slidev-monaco-container .monaco-editor a:hover {
204
+ border-bottom: none;
205
+ }
206
+ </style>
@@ -6,8 +6,6 @@ import lz from 'lz-string'
6
6
  import { useSlideContext } from '../context'
7
7
  import { makeId, updateCodeHighlightRange } from '../logic/utils'
8
8
 
9
- import 'shiki-magic-move/style.css'
10
-
11
9
  const props = defineProps<{
12
10
  at?: string | number
13
11
  stepsLz: string
@@ -94,7 +92,12 @@ onMounted(() => {
94
92
  class="slidev-code relative shiki overflow-visible"
95
93
  :steps="steps"
96
94
  :step="stepIndex"
97
- :options="{ globalScale: scale }"
95
+ :options="{
96
+ globalScale: scale,
97
+ // TODO: make this configurable later
98
+ duration: 800,
99
+ stagger: 1,
100
+ }"
98
101
  />
99
102
  </div>
100
103
  </template>
package/builtin/Tweet.vue CHANGED
@@ -22,7 +22,14 @@ const tweet = ref<HTMLElement | null>()
22
22
  const loaded = ref(false)
23
23
  const tweetNotFound = ref(false)
24
24
 
25
- onMounted(async () => {
25
+ async function create(retries = 10) {
26
+ // @ts-expect-error global
27
+ if (!window.twttr?.widgets?.createTweet) {
28
+ if (retries <= 0)
29
+ return console.error('Failed to load Twitter widget after 10 retries.')
30
+ setTimeout(() => create(retries - 1), 1000)
31
+ return
32
+ }
26
33
  // @ts-expect-error global
27
34
  const element = await window.twttr.widgets.createTweet(
28
35
  props.id.toString(),
@@ -36,6 +43,10 @@ onMounted(async () => {
36
43
  loaded.value = true
37
44
  if (element === undefined)
38
45
  tweetNotFound.value = true
46
+ }
47
+
48
+ onMounted(() => {
49
+ create()
39
50
  })
40
51
  </script>
41
52
 
@@ -23,6 +23,7 @@ function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): C
23
23
  },
24
24
  relativeOffsets,
25
25
  map,
26
+ onMounted() {},
26
27
  resolve(at, size = 1) {
27
28
  const [isRelative, value] = normalizeAtProp(at)
28
29
  if (isRelative) {
@@ -57,8 +58,7 @@ function useClicksContextBase(current: Ref<number>, clicksOverrides?: number): C
57
58
  get total() {
58
59
  // eslint-disable-next-line no-unused-expressions
59
60
  routeForceRefresh.value
60
- return clicksOverrides
61
- ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
61
+ return clicksOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
62
62
  },
63
63
  }
64
64
  }
@@ -68,6 +68,7 @@ const queryClicksRaw = useRouteQuery('clicks', '0')
68
68
  export function usePrimaryClicks(route: SlideRoute): ClicksContext {
69
69
  if (route?.meta?.__clicksContext)
70
70
  return route.meta.__clicksContext
71
+
71
72
  const thisNo = route.no
72
73
  const current = computed({
73
74
  get() {
@@ -92,6 +93,13 @@ export function usePrimaryClicks(route: SlideRoute): ClicksContext {
92
93
  current,
93
94
  route?.meta?.clicks,
94
95
  )
96
+
97
+ // On slide mounted, make sure the query is not greater than the total
98
+ context.onMounted = () => {
99
+ if (queryClicksRaw.value)
100
+ queryClicksRaw.value = Math.min(queryClicksRaw.value, context.total)
101
+ }
102
+
95
103
  if (route?.meta)
96
104
  route.meta.__clicksContext = context
97
105
  return context
@@ -3,7 +3,8 @@ import type { ComputedRef, Ref, TransitionGroupProps } from 'vue'
3
3
  import { computed, ref, watch } from 'vue'
4
4
  import type { Router } from 'vue-router'
5
5
  import { getCurrentTransition } from '../logic/transition'
6
- import { getSlidePath } from '../logic/slides'
6
+ import { getSlide, getSlidePath } from '../logic/slides'
7
+ import { CLICKS_MAX } from '../constants'
7
8
  import { useTocTree } from './useTocTree'
8
9
  import { skipTransition } from './hmr'
9
10
  import { slides } from '#slidev/slides'
@@ -121,12 +122,12 @@ export function useNavBase(
121
122
  async function prevSlide(lastClicks = true) {
122
123
  clicksDirection.value = -1
123
124
  const next = Math.max(1, currentSlideNo.value - 1)
124
- await go(next)
125
- if (lastClicks && clicksTotal.value) {
126
- router?.replace({
127
- query: { ...router.currentRoute.value.query, clicks: clicksTotal.value },
128
- })
129
- }
125
+ await go(
126
+ next,
127
+ lastClicks
128
+ ? getSlide(next)?.meta.__clicksContext?.total ?? CLICKS_MAX
129
+ : undefined,
130
+ )
130
131
  }
131
132
 
132
133
  function goFirst() {
@@ -141,7 +142,10 @@ export function useNavBase(
141
142
  skipTransition.value = false
142
143
  await router?.push({
143
144
  path: getSlidePath(page),
144
- query: { ...router.currentRoute.value.query, clicks },
145
+ query: {
146
+ ...router.currentRoute.value.query,
147
+ clicks: clicks || undefined,
148
+ },
145
149
  })
146
150
  }
147
151
 
@@ -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()
@@ -37,11 +37,11 @@ export function useSlideInfo(id: number | undefined): UseSlideInfo {
37
37
 
38
38
  if (__DEV__) {
39
39
  import.meta.hot?.on('slidev:update-slide', (payload) => {
40
- if (payload.id === id)
40
+ if (payload.no === no)
41
41
  info.value = payload.data
42
42
  })
43
43
  import.meta.hot?.on('slidev:update-note', (payload) => {
44
- if (payload.id === id && info.value.note?.trim() !== payload.note?.trim())
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
@@ -1,5 +1,6 @@
1
1
  import { ref } from 'vue'
2
2
  import { useRouter } from 'vue-router'
3
+ import { getSlide } from '../logic/nav'
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/context.ts CHANGED
@@ -39,6 +39,8 @@ export function useSlideContext() {
39
39
  }
40
40
  }
41
41
 
42
+ export type SlideContext = ReturnType<typeof useSlideContext>
43
+
42
44
  export function provideFrontmatter(frontmatter: Record<string, any>) {
43
45
  provideLocal(injectionFrontmatter, frontmatter)
44
46
 
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>
@@ -0,0 +1,139 @@
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 { isPrintMode } from '../logic/nav'
7
+ import { useSlideContext } from '../context'
8
+ import setupCodeRunners from '../setup/code-runners'
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
+ const code = useVModel(props, 'modelValue', emit)
23
+
24
+ const { $renderContext } = useSlideContext()
25
+ const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value))
26
+
27
+ const autorun = isPrintMode.value ? 'once' : props.autorun
28
+ const isRunning = ref(autorun)
29
+ const outputs = shallowRef<CodeRunnerOutput[]>()
30
+ const runCount = ref(0)
31
+ const highlightFn = ref<(code: string, lang: string) => string>()
32
+
33
+ const triggerRun = debounce(200, async () => {
34
+ if (disabled.value)
35
+ return
36
+
37
+ const { highlight, run } = await setupCodeRunners()
38
+ highlightFn.value = highlight
39
+
40
+ const setAsRunning = setTimeout(() => {
41
+ isRunning.value = true
42
+ }, 500)
43
+
44
+ outputs.value = toArray(await run(code.value, props.lang, props.runnerOptions ?? {}))
45
+ runCount.value += 1
46
+ isRunning.value = false
47
+
48
+ clearTimeout(setAsRunning)
49
+ })
50
+
51
+ if (autorun === 'once')
52
+ triggerRun()
53
+ else if (autorun)
54
+ watch(code, triggerRun, { immediate: true })
55
+ </script>
56
+
57
+ <template>
58
+ <div
59
+ class="relative flex flex-col rounded-b border-t border-main"
60
+ :style="{ height: props.height }"
61
+ data-waitfor=".slidev-runner-output"
62
+ >
63
+ <div v-if="disabled" class="text-sm text-center opacity-50">
64
+ Code is disabled in the "{{ $renderContext }}" mode
65
+ </div>
66
+ <div v-else-if="isRunning" class="text-sm text-center opacity-50">
67
+ Running...
68
+ </div>
69
+ <div v-else-if="!outputs?.length" class="text-sm text-center opacity-50">
70
+ Click the play button to run the code
71
+ </div>
72
+ <div v-else :key="`run-${runCount}`" class="slidev-runner-output">
73
+ <template v-for="output, _idx1 of outputs" :key="_idx1">
74
+ <div v-if="'html' in output" v-html="output.html" />
75
+ <div v-else-if="'error' in output" class="text-red-500">
76
+ {{ output.error }}
77
+ </div>
78
+ <DomElement v-else-if="'element' in output" :element="output.element" />
79
+ <div v-else class="output-line">
80
+ <template
81
+ v-for="item, idx2 in toArray(output)"
82
+ :key="idx2"
83
+ >
84
+ <span
85
+ v-if="item.highlightLang && highlightFn"
86
+ class="highlighted"
87
+ v-html="highlightFn(item.text, item.highlightLang)"
88
+ />
89
+ <span v-else :class="item.class">{{ item.text }}</span>
90
+ <span v-if="idx2 < toArray(output).length - 1" class="separator">,</span>
91
+ </template>
92
+ </div>
93
+ </template>
94
+ </div>
95
+ </div>
96
+ <div v-if="code.trim()" class="absolute right-1 top-1 max-h-full flex gap-1">
97
+ <IconButton class="w-8 h-8 max-h-full flex justify-center items-center" title="Run code" @click="triggerRun">
98
+ <carbon:play />
99
+ </IconButton>
100
+ </div>
101
+ </template>
102
+
103
+ <style lang="postcss">
104
+ .slidev-runner-output {
105
+ @apply px-5 py-3 flex-grow text-xs leading-[.8rem] font-$slidev-code-font-family select-text;
106
+ }
107
+
108
+ .slidev-runner-output .log-type {
109
+ @apply font-bold op-70;
110
+
111
+ &.DBG {
112
+ @apply text-gray-500;
113
+ }
114
+
115
+ &.LOG {
116
+ @apply text-blue-500;
117
+ }
118
+
119
+ &.WRN {
120
+ @apply text-orange-500;
121
+ }
122
+
123
+ &.ERR {
124
+ @apply text-red-500;
125
+ }
126
+ }
127
+
128
+ .slidev-runner-output .output-line {
129
+ @apply flex my-1 w-full;
130
+ }
131
+
132
+ .slidev-runner-output .separator {
133
+ @apply op-40 mr-1;
134
+ }
135
+
136
+ .slidev-runner-output .highlighted > pre {
137
+ @apply inline text-wrap !bg-transparent;
138
+ }
139
+ </style>
@@ -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>
@@ -7,7 +7,7 @@ defineProps<{
7
7
  </script>
8
8
 
9
9
  <template>
10
- <component :is="as || 'button'" class="slidev-icon-btn" :title="title" v-bind="$attrs">
10
+ <component :is="as || 'button'" class="slidev-icon-btn" :title="title">
11
11
  <span class="sr-only">{{ title }}</span>
12
12
  <slot>
13
13
  <div :class="icon" />
@@ -1,14 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import type { PropType } from 'vue'
3
- import { nextTick, ref, watch, watchEffect } from 'vue'
3
+ import { nextTick, ref, toRef, watch, watchEffect } from 'vue'
4
4
  import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
5
5
  import type { ClicksContext } from '@slidev/types'
6
- import { useDynamicSlideInfo } from '../logic/note'
6
+ import { useDynamicSlideInfo } from '../composables/useSlideInfo'
7
7
  import NoteDisplay from './NoteDisplay.vue'
8
8
 
9
9
  const props = defineProps({
10
10
  no: {
11
11
  type: Number,
12
+ required: true,
12
13
  },
13
14
  class: {
14
15
  default: '',
@@ -38,7 +39,7 @@ const emit = defineEmits<{
38
39
 
39
40
  const editing = useVModel(props, 'editing', emit, { passive: true })
40
41
 
41
- const { info, update } = useDynamicSlideInfo(props.no)
42
+ const { info, update } = useDynamicSlideInfo(toRef(props, 'no'))
42
43
 
43
44
  const note = ref('')
44
45
  let timer: any
@@ -1,10 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import type { ClicksContext } from '@slidev/types'
3
- import { useSlideInfo } from '../logic/note'
3
+ import { useSlideInfo } from '../composables/useSlideInfo'
4
4
  import NoteDisplay from './NoteDisplay.vue'
5
5
 
6
6
  const props = defineProps<{
7
- no?: number
7
+ no: number
8
8
  class?: string
9
9
  clicksContext?: ClicksContext
10
10
  }>()
@@ -5,7 +5,7 @@ import { injectionSlidevContext } from '../constants'
5
5
  import { configs, slideHeight, slideWidth } from '../env'
6
6
  import { getSlideClass } from '../utils'
7
7
  import type { SlidevContextNav } from '../composables/useNav'
8
- import SlideWrapper from './SlideWrapper'
8
+ import SlideWrapper from './SlideWrapper.vue'
9
9
 
10
10
  import GlobalTop from '#slidev/global-components/top'
11
11
  import GlobalBottom from '#slidev/global-components/bottom'
@@ -8,7 +8,7 @@ import { useFixedClicks } from '../composables/useClicks'
8
8
  import { getSlideClass } from '../utils'
9
9
  import { CLICKS_MAX } from '../constants'
10
10
  import SlideContainer from './SlideContainer.vue'
11
- import SlideWrapper from './SlideWrapper'
11
+ import SlideWrapper from './SlideWrapper.vue'
12
12
  import DrawingPreview from './DrawingPreview.vue'
13
13
  import IconButton from './IconButton.vue'
14
14
 
@@ -167,6 +167,7 @@ watchEffect(() => {
167
167
  <carbon:close />
168
168
  </IconButton>
169
169
  <IconButton
170
+ v-if="__DEV__"
170
171
  as="a"
171
172
  title="Slides Overview"
172
173
  target="_blank"
@@ -4,7 +4,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
4
  import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
5
5
  import { useCodeMirror } from '../setup/codemirror'
6
6
  import { currentSlideNo, openInEditor } from '../logic/nav'
7
- import { useDynamicSlideInfo } from '../logic/note'
7
+ import { useDynamicSlideInfo } from '../composables/useSlideInfo'
8
8
  import IconButton from './IconButton.vue'
9
9
 
10
10
  const props = defineProps<{
@@ -10,7 +10,7 @@ onMounted(() => {
10
10
  </script>
11
11
 
12
12
  <template>
13
- <div class="h-full w-full flex items-center justify-center gap-2">
13
+ <div class="h-full w-full flex items-center justify-center gap-2 slidev-slide-loading">
14
14
  <template v-if="timeout">
15
15
  <div class="i-svg-spinners-90-ring-with-bg text-xl" />
16
16
  <div>Loading slide...</div>
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import { computed, defineAsyncComponent, defineComponent, h, onMounted, ref, toRef } from 'vue'
3
+ import type { PropType } from 'vue'
4
+ import { provideLocal } from '@vueuse/core'
5
+ import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
6
+ import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
7
+ import SlideLoading from './SlideLoading.vue'
8
+
9
+ const props = defineProps({
10
+ clicksContext: {
11
+ type: Object as PropType<ClicksContext>,
12
+ required: true,
13
+ },
14
+ renderContext: {
15
+ type: String as PropType<RenderContext>,
16
+ default: 'slide',
17
+ },
18
+ active: {
19
+ type: Boolean,
20
+ default: false,
21
+ },
22
+ is: {
23
+ type: Function as PropType<() => any>,
24
+ required: true,
25
+ },
26
+ route: {
27
+ type: Object as PropType<SlideRoute>,
28
+ required: true,
29
+ },
30
+ })
31
+
32
+ provideLocal(injectionRoute, props.route)
33
+ provideLocal(injectionCurrentPage, ref(props.route.no))
34
+ provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
35
+ provideLocal(injectionActive, toRef(props, 'active'))
36
+ provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
37
+
38
+ const style = computed(() => {
39
+ const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
40
+ return zoom === 1
41
+ ? undefined
42
+ : {
43
+ width: `${100 / zoom}%`,
44
+ height: `${100 / zoom}%`,
45
+ transformOrigin: 'top left',
46
+ transform: `scale(${zoom})`,
47
+ }
48
+ })
49
+
50
+ const SlideComponent = defineAsyncComponent({
51
+ loader: async () => {
52
+ const component = await props.is()
53
+ return defineComponent({
54
+ setup(_, { attrs }) {
55
+ onMounted(() => {
56
+ props.clicksContext.onMounted()
57
+ })
58
+ return () => h(component.default, attrs)
59
+ },
60
+ })
61
+ },
62
+ delay: 300,
63
+ loadingComponent: SlideLoading,
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <component
69
+ :is="SlideComponent"
70
+ :style="style"
71
+ :class="{ 'disable-view-transition': !['slide', 'presenter'].includes(props.renderContext) }"
72
+ />
73
+ </template>
74
+
75
+ <style scoped>
76
+ .disable-view-transition:deep(*) {
77
+ view-transition-name: none !important;
78
+ }
79
+ </style>
@@ -5,7 +5,7 @@ import { getSlideClass } from '../utils'
5
5
  import { useViewTransition } from '../composables/useViewTransition'
6
6
  import { skipTransition } from '../composables/hmr'
7
7
  import { usePrimaryClicks } from '../composables/useClicks'
8
- import SlideWrapper from './SlideWrapper'
8
+ import SlideWrapper from './SlideWrapper.vue'
9
9
  import PresenterMouse from './PresenterMouse.vue'
10
10
 
11
11
  import GlobalTop from '#slidev/global-components/top'
package/logic/route.ts CHANGED
@@ -19,7 +19,12 @@ export function useRouteQuery<T extends string | string[]>(
19
19
  },
20
20
  set(v) {
21
21
  nextTick(() => {
22
- router[unref(mode) as 'replace' | 'push']({ query: { ...router.currentRoute.value.query, [name]: v } })
22
+ router[unref(mode) as 'replace' | 'push']({
23
+ query: {
24
+ ...router.currentRoute.value.query,
25
+ [name]: `${v}` === defaultValue ? undefined : v,
26
+ },
27
+ })
23
28
  })
24
29
  },
25
30
  })
package/logic/utils.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { parseRangeString } from '@slidev/parser/core'
2
2
  import { useTimestamp } from '@vueuse/core'
3
3
  import { computed, ref } from 'vue'
4
- import { CLASS_VCLICK_TARGET } from '../constants'
5
4
 
6
5
  export function useTimer() {
7
6
  const tsStart = ref(Date.now())
@@ -26,7 +25,7 @@ export function useTimer() {
26
25
 
27
26
  export function makeId(length = 5) {
28
27
  const result = []
29
- const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
28
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
30
29
  const charactersLength = characters.length
31
30
  for (let i = 0; i < length; i++)
32
31
  result.push(characters.charAt(Math.floor(Math.random() * charactersLength)))
@@ -62,7 +61,7 @@ export function updateCodeHighlightRange(
62
61
  const tokens = getTokenOfLine(line)
63
62
  const isHighlighted = highlights.includes(line + startLine)
64
63
  for (const token of tokens) {
65
- token.classList.toggle(CLASS_VCLICK_TARGET, true)
64
+ // token.classList.toggle(CLASS_VCLICK_TARGET, true)
66
65
  token.classList.toggle('slidev-code-highlighted', isHighlighted)
67
66
  token.classList.toggle('slidev-code-dishonored', !isHighlighted)
68
67
 
package/main.ts CHANGED
@@ -10,6 +10,7 @@ import { createVMarkDirective } from './modules/v-mark'
10
10
  import { createSlidevContext } from './modules/context'
11
11
 
12
12
  import '#slidev/styles'
13
+ import 'shiki-magic-move/style.css'
13
14
 
14
15
  const app = createApp(App)
15
16
  app.use(router)
@@ -8,8 +8,10 @@ mermaid.startOnLoad = false
8
8
  mermaid.initialize({ startOnLoad: false })
9
9
 
10
10
  const cache = new Map<string, string>()
11
+ let containerElement: Element | undefined
11
12
 
12
13
  export async function renderMermaid(lzEncoded: string, options: any) {
14
+ containerElement ??= document.getElementById('mermaid-rendering-container')!
13
15
  const key = lzEncoded + JSON.stringify(options)
14
16
  const _cache = cache.get(key)
15
17
  if (_cache)
@@ -22,7 +24,7 @@ export async function renderMermaid(lzEncoded: string, options: any) {
22
24
  })
23
25
  const code = lz.decompressFromBase64(lzEncoded)
24
26
  const id = makeId()
25
- const { svg } = await mermaid.render(id, code)
27
+ const { svg } = await mermaid.render(id, code, containerElement)
26
28
  cache.set(key, svg)
27
29
  return svg
28
30
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@slidev/client",
3
3
  "type": "module",
4
- "version": "0.48.0-beta.22",
4
+ "version": "0.48.0-beta.24",
5
5
  "description": "Presentation slides for developers",
6
6
  "author": "antfu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -47,20 +47,21 @@
47
47
  "js-yaml": "^4.1.0",
48
48
  "katex": "^0.16.9",
49
49
  "lz-string": "^1.5.0",
50
- "mermaid": "^10.8.0",
50
+ "mermaid": "^10.9.0",
51
51
  "monaco-editor": "^0.46.0",
52
52
  "prettier": "^3.2.5",
53
53
  "recordrtc": "^5.6.2",
54
54
  "shiki": "^1.1.7",
55
- "shiki-magic-move": "^0.3.0",
56
- "typescript": "^5.3.3",
55
+ "shiki-magic-move": "^0.3.4",
56
+ "typescript": "^5.4.2",
57
57
  "unocss": "^0.58.5",
58
58
  "vue": "^3.4.21",
59
+ "vue-demi": "^0.14.7",
59
60
  "vue-router": "^4.3.0",
60
- "@slidev/parser": "0.48.0-beta.22",
61
- "@slidev/types": "0.48.0-beta.22"
61
+ "@slidev/parser": "0.48.0-beta.24",
62
+ "@slidev/types": "0.48.0-beta.24"
62
63
  },
63
64
  "devDependencies": {
64
- "vite": "^5.1.4"
65
+ "vite": "^5.1.5"
65
66
  }
66
67
  }
@@ -8,7 +8,7 @@ import { useFixedClicks } from '../composables/useClicks'
8
8
  import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
9
9
  import { getSlideClass } from '../utils'
10
10
  import SlideContainer from '../internals/SlideContainer.vue'
11
- import SlideWrapper from '../internals/SlideWrapper'
11
+ import SlideWrapper from '../internals/SlideWrapper.vue'
12
12
  import DrawingPreview from '../internals/DrawingPreview.vue'
13
13
  import IconButton from '../internals/IconButton.vue'
14
14
  import NoteEditable from '../internals/NoteEditable.vue'
@@ -139,6 +139,7 @@ onMounted(() => {
139
139
  :key="route.no"
140
140
  :ref="el => blocks.set(idx, el as any)"
141
141
  class="relative border-t border-main of-hidden flex gap-4 min-h-50 group"
142
+ :class="idx === 0 ? 'pt5' : ''"
142
143
  >
143
144
  <div class="select-none w-13 text-right my4 flex flex-col gap-1 items-end">
144
145
  <div class="text-3xl op20 mb2">
@@ -192,19 +193,19 @@ onMounted(() => {
192
193
  <IconButton
193
194
  title="Edit Note"
194
195
  class="rounded-full w-9 h-9 text-sm"
195
- :class="edittingNote === idx ? 'important:op0' : ''"
196
- @click="edittingNote = idx"
196
+ :class="edittingNote === route.no ? 'important:op0' : ''"
197
+ @click="edittingNote = route.no"
197
198
  >
198
199
  <carbon:pen />
199
200
  </IconButton>
200
201
  </div>
201
202
  <NoteEditable
202
- :no="idx"
203
+ :no="route.no"
203
204
  class="max-w-250 w-250 text-lg rounded p3"
204
205
  :auto-height="true"
205
- :editing="edittingNote === idx"
206
+ :editing="edittingNote === route.no"
206
207
  :clicks-context="getClicksContext(route)"
207
- @dblclick="edittingNote !== idx ? edittingNote = idx : null"
208
+ @dblclick="edittingNote !== route.no ? edittingNote = route.no : null"
208
209
  @update:editing="edittingNote = null"
209
210
  @marker-click="(e, clicks) => onMarkerClick(e, clicks, route)"
210
211
  />
@@ -12,7 +12,7 @@ import { getSlideClass } from '../utils'
12
12
  import { useTimer } from '../logic/utils'
13
13
  import { isDrawing } from '../logic/drawings'
14
14
  import { useFixedClicks, usePrimaryClicks } from '../composables/useClicks'
15
- import SlideWrapper from '../internals/SlideWrapper'
15
+ import SlideWrapper from '../internals/SlideWrapper.vue'
16
16
  import SlideContainer from '../internals/SlideContainer.vue'
17
17
  import NavControls from '../internals/NavControls.vue'
18
18
  import QuickOverview from '../internals/QuickOverview.vue'
@@ -0,0 +1,169 @@
1
+ /* __imports__ */
2
+ import { createSingletonPromise } from '@antfu/utils'
3
+ import type { CodeRunner, CodeRunnerContext, CodeRunnerOutput, CodeRunnerOutputText, CodeRunnerOutputs, CodeRunnerProviders } from '@slidev/types'
4
+ import type { CodeToHastOptions } from 'shiki'
5
+ import { isDark } from '../logic/dark'
6
+
7
+ export default createSingletonPromise(async () => {
8
+ const runners: Record<string, CodeRunner> = {
9
+ javascript: runJavaScript,
10
+ js: runJavaScript,
11
+ typescript: runTypeScript,
12
+ ts: runTypeScript,
13
+ }
14
+
15
+ const { shiki, themes } = await import('#slidev/shiki')
16
+ const highlighter = await shiki
17
+ const highlight = (code: string, lang: string, options: Partial<CodeToHastOptions> = {}) => highlighter.codeToHtml(code, {
18
+ lang,
19
+ theme: typeof themes === 'string'
20
+ ? themes
21
+ : isDark.value
22
+ ? themes.dark || 'vitesse-dark'
23
+ : themes.light || 'vitesse-light',
24
+ ...options,
25
+ })
26
+
27
+ const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {
28
+ try {
29
+ const runner = runners[lang]
30
+ if (!runner)
31
+ throw new Error(`Runner for language "${lang}" not found`)
32
+ return await runner(
33
+ code,
34
+ {
35
+ options,
36
+ highlight,
37
+ run: async (code, lang) => {
38
+ return await run(code, lang, options)
39
+ },
40
+ },
41
+ )
42
+ }
43
+ catch (e) {
44
+ console.error(e)
45
+ return {
46
+ error: `${e}`,
47
+ }
48
+ }
49
+ }
50
+
51
+ // @ts-expect-error injected in runtime
52
+ // eslint-disable-next-line unused-imports/no-unused-vars
53
+ const injection_arg = runners
54
+ // eslint-disable-next-line prefer-const
55
+ let injection_return: CodeRunnerProviders = {}
56
+
57
+ /* __async_injections__ */
58
+
59
+ Object.assign(runners, injection_return)
60
+
61
+ return {
62
+ highlight,
63
+ run,
64
+ }
65
+ })
66
+
67
+ // Ported from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/playground/src/sidebar/runtime.ts
68
+ export async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
69
+ const allLogs: CodeRunnerOutput[] = []
70
+
71
+ const replace = {} as any
72
+ const logger = function (...objs: any[]) {
73
+ allLogs.push(objs.map(printObject))
74
+ }
75
+ replace.info = replace.log = replace.debug = replace.warn = replace.error = logger
76
+ replace.clear = () => allLogs.length = 0
77
+ const vmConsole = Object.assign({}, console, replace)
78
+ try {
79
+ const safeJS = `return async (console) => {${sanitizeJS(code)}}`
80
+ // eslint-disable-next-line no-new-func
81
+ await (new Function(safeJS)())(vmConsole)
82
+ }
83
+ catch (error) {
84
+ return {
85
+ error: `ERROR: ${error}`,
86
+ }
87
+ }
88
+
89
+ function printObject(arg: any): CodeRunnerOutputText {
90
+ if (typeof arg === 'string') {
91
+ return {
92
+ text: arg,
93
+ }
94
+ }
95
+ return {
96
+ text: objectToText(arg),
97
+ highlightLang: 'javascript',
98
+ }
99
+ }
100
+
101
+ function objectToText(arg: any): string {
102
+ let textRep = ''
103
+ if (arg instanceof Error) {
104
+ textRep = `Error: ${JSON.stringify(arg.message)}`
105
+ }
106
+ else if (arg === null || arg === undefined || typeof arg === 'symbol') {
107
+ textRep = String(arg)
108
+ }
109
+ else if (Array.isArray(arg)) {
110
+ textRep = `[${arg.map(objectToText).join(', ')}]`
111
+ }
112
+ else if (arg instanceof Set) {
113
+ const setIter = [...arg]
114
+ textRep = `Set (${arg.size}) {${setIter.map(objectToText).join(', ')}}`
115
+ }
116
+ else if (arg instanceof Map) {
117
+ const mapIter = [...arg.entries()]
118
+ textRep
119
+ = `Map (${arg.size}) {${mapIter
120
+ .map(([k, v]) => `${objectToText(k)} => ${objectToText(v)}`)
121
+ .join(', ')
122
+ }}`
123
+ }
124
+ else if (arg instanceof RegExp) {
125
+ textRep = arg.toString()
126
+ }
127
+ else if (typeof arg === 'string') {
128
+ textRep = JSON.stringify(arg)
129
+ }
130
+ else if (typeof arg === 'object') {
131
+ const name = arg.constructor?.name ?? ''
132
+ // No one needs to know an obj is an obj
133
+ const nameWithoutObject = name && name === 'Object' ? '' : name
134
+ const prefix = nameWithoutObject ? `${nameWithoutObject}: ` : ''
135
+
136
+ // JSON.stringify omits any keys with a value of undefined. To get around this, we replace undefined with the text __undefined__ and then do a global replace using regex back to keyword undefined
137
+ textRep
138
+ = prefix
139
+ + JSON.stringify(arg, (_, value) => (value === undefined ? '__undefined__' : value), 2).replace(
140
+ /"__undefined__"/g,
141
+ 'undefined',
142
+ )
143
+
144
+ textRep = String(textRep)
145
+ }
146
+ else {
147
+ textRep = String(arg)
148
+ }
149
+ return textRep
150
+ }
151
+
152
+ // The reflect-metadata runtime is available, so allow that to go through
153
+ function sanitizeJS(code: string) {
154
+ return code.replace(`import "reflect-metadata"`, '').replace(`require("reflect-metadata")`, '')
155
+ }
156
+
157
+ return allLogs
158
+ }
159
+
160
+ let tsModule: typeof import('typescript') | undefined
161
+
162
+ export async function runTypeScript(code: string, context: CodeRunnerContext) {
163
+ const { transpile } = tsModule ??= await import('typescript')
164
+ code = transpile(code, {
165
+ module: tsModule.ModuleKind.ESNext,
166
+ target: tsModule.ScriptTarget.ES2022,
167
+ })
168
+ return await context.run(code, 'javascript')
169
+ }
package/setup/monaco.ts CHANGED
@@ -89,16 +89,25 @@ const setup = createSingletonPromise(async () => {
89
89
  })
90
90
  : () => { }
91
91
 
92
- // monaco.languages.register({ id: 'vue' })
92
+ monaco.languages.register({ id: 'vue' })
93
+ monaco.languages.register({ id: 'html' })
94
+ monaco.languages.register({ id: 'css' })
93
95
  monaco.languages.register({ id: 'typescript' })
94
96
  monaco.languages.register({ id: 'javascript' })
95
97
 
96
98
  const { shiki, themes, shikiToMonaco } = await import('#slidev/shiki')
97
99
  const highlighter = await shiki
98
100
 
101
+ // @ts-expect-error injected in runtime
102
+ // eslint-disable-next-line unused-imports/no-unused-vars
103
+ const injection_arg = monaco
104
+ // eslint-disable-next-line prefer-const
105
+ let injection_return: MonacoSetupReturn = {}
106
+
107
+ /* __async_injections__ */
108
+
99
109
  // Use Shiki to highlight Monaco
100
110
  shikiToMonaco(highlighter, monaco)
101
-
102
111
  if (typeof themes === 'string') {
103
112
  monaco.editor.setTheme(themes)
104
113
  }
@@ -110,14 +119,6 @@ const setup = createSingletonPromise(async () => {
110
119
  })
111
120
  }
112
121
 
113
- // @ts-expect-error injected in runtime
114
- // eslint-disable-next-line unused-imports/no-unused-vars
115
- const injection_arg = monaco
116
- // eslint-disable-next-line prefer-const
117
- let injection_return: MonacoSetupReturn = {}
118
-
119
- /* __async_injections__ */
120
-
121
122
  return {
122
123
  monaco,
123
124
  ata,
package/shim-vue.d.ts CHANGED
@@ -1,15 +1,11 @@
1
1
  declare module 'vue' {
2
- import type { UnwrapNestedRefs } from 'vue'
3
- import type { SlidevContext } from './modules/context'
4
-
5
- interface ComponentCustomProperties {
6
- $slidev: UnwrapNestedRefs<SlidevContext>
2
+ type SlideContext = import('./context').SlideContext
3
+ interface ComponentCustomProperties extends SlideContext {
7
4
  }
8
5
  }
9
6
 
10
7
  declare module 'vue-router' {
11
8
  import type { TransitionGroupProps } from 'vue'
12
- import type { ClicksContext, SlideInfo } from '@slidev/types'
13
9
 
14
10
  interface RouteMeta {
15
11
  // inherited from frontmatter
@@ -21,7 +17,7 @@ declare module 'vue-router' {
21
17
  preload?: boolean
22
18
 
23
19
  // slide info
24
- slide?: Omit<SlideInfo, 'source'> & {
20
+ slide?: Omit<import('@slidev/types').SlideInfo, 'source'> & {
25
21
  noteHTML: string
26
22
  filepath: string
27
23
  start: number
@@ -30,7 +26,7 @@ declare module 'vue-router' {
30
26
  }
31
27
 
32
28
  // private fields
33
- __clicksContext: null | ClicksContext
29
+ __clicksContext: import('@slidev/types').ClicksContext | undefined
34
30
  __preloaded?: boolean
35
31
  }
36
32
  }
@@ -1,58 +0,0 @@
1
- import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
2
- import type { PropType } from 'vue'
3
- import { provideLocal } from '@vueuse/core'
4
- import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
5
- import { injectionActive, injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionRoute } from '../constants'
6
- import SlideLoading from './SlideLoading.vue'
7
-
8
- export default defineComponent({
9
- name: 'SlideWrapper',
10
- props: {
11
- clicksContext: {
12
- type: Object as PropType<ClicksContext>,
13
- required: true,
14
- },
15
- renderContext: {
16
- type: String,
17
- default: 'main',
18
- },
19
- active: {
20
- type: Boolean,
21
- default: false,
22
- },
23
- is: {
24
- required: true,
25
- },
26
- route: {
27
- type: Object as PropType<SlideRoute>,
28
- required: true,
29
- },
30
- },
31
- setup(props) {
32
- provideLocal(injectionRoute, props.route)
33
- provideLocal(injectionCurrentPage, ref(props.route.no))
34
- provideLocal(injectionRenderContext, ref(props.renderContext as RenderContext))
35
- provideLocal(injectionActive, toRef(props, 'active'))
36
- provideLocal(injectionClicksContext, toRef(props, 'clicksContext'))
37
-
38
- const style = computed(() => {
39
- const zoom = props.route.meta?.slide?.frontmatter.zoom ?? 1
40
- return zoom === 1
41
- ? undefined
42
- : {
43
- width: `${100 / zoom}%`,
44
- height: `${100 / zoom}%`,
45
- transformOrigin: 'top left',
46
- transform: `scale(${zoom})`,
47
- }
48
- })
49
-
50
- const SlideComponent = defineAsyncComponent({
51
- loader: (props.is as any),
52
- delay: 300,
53
- loadingComponent: SlideLoading,
54
- })
55
-
56
- return () => h(SlideComponent, { style: style.value })
57
- },
58
- })
package/styles/monaco.css DELETED
@@ -1,27 +0,0 @@
1
- div[widgetid='messageoverlay'] {
2
- transform: translateY(calc(100% * (var(--slidev-slide-scale) - 1)));
3
- }
4
-
5
- .slidev-monaco-container {
6
- position: relative;
7
- margin: var(--slidev-code-margin);
8
- padding: var(--slidev-code-padding);
9
- line-height: var(--slidev-code-line-height);
10
- border-radius: var(--slidev-code-radius);
11
- background: var(--slidev-code-background);
12
- }
13
-
14
- .slidev-monaco-container .monaco-editor {
15
- --monaco-monospace-font: var(--slidev-code-font-family);
16
- --vscode-editor-background: var(--slidev-code-background);
17
- --vscode-editorGutter-background: var(--slidev-code-background);
18
- }
19
-
20
- /** Revert styles */
21
- .slidev-monaco-container .monaco-editor a {
22
- border-bottom: none;
23
- }
24
-
25
- .slidev-monaco-container .monaco-editor a:hover {
26
- border-bottom: none;
27
- }