@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
package/App.vue CHANGED
@@ -1,7 +1,14 @@
1
1
  <script setup lang="ts">
2
+ import { watchEffect } from 'vue'
3
+ import { themeVars } from './env'
2
4
  import setupRoot from './setup/root'
3
5
 
4
6
  setupRoot()
7
+
8
+ watchEffect(() => {
9
+ for (const [key, value] of Object.entries(themeVars.value))
10
+ document.body.style.setProperty(key, value.toString())
11
+ })
5
12
  </script>
6
13
 
7
14
  <template>
package/builtin/Arrow.vue CHANGED
@@ -9,7 +9,7 @@ Simple Arrow
9
9
  -->
10
10
 
11
11
  <script setup lang="ts">
12
- import { customAlphabet } from 'nanoid'
12
+ import { makeId } from '../logic/utils'
13
13
 
14
14
  defineProps<{
15
15
  x1: number | string
@@ -20,9 +20,7 @@ defineProps<{
20
20
  color?: string
21
21
  }>()
22
22
 
23
- const nanoid = customAlphabet('abcedfghijklmn', 10)
24
-
25
- const id = nanoid()
23
+ const id = makeId()
26
24
  </script>
27
25
 
28
26
  <template>
@@ -12,13 +12,12 @@ Learn more: https://sli.dev/guide/syntax.html#line-highlighting
12
12
  -->
13
13
 
14
14
  <script setup lang="ts">
15
- import { parseRangeString } from '@slidev/parser/core'
16
15
  import { useClipboard } from '@vueuse/core'
17
16
  import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
18
17
  import type { PropType } from 'vue'
19
18
  import { configs } from '../env'
20
- import { makeId } from '../logic/utils'
21
- import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET } from '../constants'
19
+ import { makeId, updateCodeHighlightRange } from '../logic/utils'
20
+ import { CLASS_VCLICK_HIDDEN } from '../constants'
22
21
  import { useSlideContext } from '../context'
23
22
 
24
23
  const props = defineProps({
@@ -56,8 +55,12 @@ onUnmounted(() => {
56
55
  clicks!.unregister(id)
57
56
  })
58
57
 
58
+ watchEffect(() => {
59
+ el.value?.classList.toggle('slidev-code-line-numbers', props.lines)
60
+ })
61
+
59
62
  onMounted(() => {
60
- if (!clicks || clicks.disabled)
63
+ if (!clicks || clicks.disabled || !props.ranges?.length)
61
64
  return
62
65
 
63
66
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
@@ -83,28 +86,27 @@ onMounted(() => {
83
86
  if (hide)
84
87
  rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
85
88
 
86
- const isDuoTone = el.value.querySelector('.shiki-dark')
87
- const targets = isDuoTone ? Array.from(el.value.querySelectorAll('.shiki')) : [el.value]
88
- const startLine = props.startLine
89
- for (const target of targets) {
90
- const lines = Array.from(target.querySelectorAll('code > .line'))
91
- const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr)
92
- lines.forEach((line, idx) => {
93
- const highlighted = highlights.includes(idx + startLine)
94
- line.classList.toggle(CLASS_VCLICK_TARGET, true)
95
- line.classList.toggle('highlighted', highlighted)
96
- line.classList.toggle('dishonored', !highlighted)
97
- })
98
- if (props.maxHeight) {
99
- const highlightedEls = Array.from(target.querySelectorAll('.line.highlighted')) as HTMLElement[]
100
- const height = highlightedEls.reduce((acc, el) => el.offsetHeight + acc, 0)
101
- if (height > el.value.offsetHeight) {
102
- highlightedEls[0].scrollIntoView({ behavior: 'smooth', block: 'start' })
103
- }
104
- else if (highlightedEls.length > 0) {
105
- const middleEl = highlightedEls[Math.round((highlightedEls.length - 1) / 2)]
106
- middleEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
107
- }
89
+ const pre = el.value.querySelector('.shiki')!
90
+ const lines = Array.from(pre.querySelectorAll('code > .line'))
91
+ const linesCount = lines.length
92
+
93
+ updateCodeHighlightRange(
94
+ rangeStr,
95
+ linesCount,
96
+ props.startLine,
97
+ no => [lines[no]],
98
+ )
99
+
100
+ // Scroll to the highlighted line if `maxHeight` is set
101
+ if (props.maxHeight) {
102
+ const highlightedEls = Array.from(pre.querySelectorAll('.line.highlighted')) as HTMLElement[]
103
+ const height = highlightedEls.reduce((acc, el) => el.offsetHeight + acc, 0)
104
+ if (height > el.value.offsetHeight) {
105
+ highlightedEls[0].scrollIntoView({ behavior: 'smooth', block: 'start' })
106
+ }
107
+ else if (highlightedEls.length > 0) {
108
+ const middleEl = highlightedEls[Math.round((highlightedEls.length - 1) / 2)]
109
+ middleEl.scrollIntoView({ behavior: 'smooth', block: 'center' })
108
110
  }
109
111
  }
110
112
  })
@@ -121,9 +123,12 @@ function copyCode() {
121
123
 
122
124
  <template>
123
125
  <div
124
- ref="el" class="slidev-code-wrapper relative group" :class="{
126
+ ref="el"
127
+ class="slidev-code-wrapper relative group"
128
+ :class="{
125
129
  'slidev-code-line-numbers': props.lines,
126
- }" :style="{
130
+ }"
131
+ :style="{
127
132
  'max-height': props.maxHeight,
128
133
  'overflow-y': props.maxHeight ? 'scroll' : undefined,
129
134
  '--start': props.startLine,
@@ -55,7 +55,7 @@ onUnmounted(() => {
55
55
  })
56
56
 
57
57
  onMounted(() => {
58
- if (!clicks || clicks.disabled)
58
+ if (!clicks || clicks.disabled || !props.ranges?.length)
59
59
  return
60
60
 
61
61
  const { start, end, delta } = clicks.resolve(props.at, props.ranges.length - 1)
package/builtin/Link.vue CHANGED
@@ -8,12 +8,14 @@ Usage:
8
8
  <Link :to="5" title="Go to slide 5" />
9
9
  -->
10
10
  <script setup lang="ts">
11
- import { isPrintMode } from '../logic/nav'
11
+ import { useNav } from '../composables/useNav'
12
12
 
13
13
  defineProps<{
14
14
  to: number | string
15
15
  title?: string
16
16
  }>()
17
+
18
+ const { isPrintMode } = useNav()
17
19
  </script>
18
20
 
19
21
  <template>
@@ -19,7 +19,7 @@ import ShadowRoot from '../internals/ShadowRoot.vue'
19
19
  import { isDark } from '../logic/dark'
20
20
 
21
21
  const props = defineProps<{
22
- code: string
22
+ codeLz: string
23
23
  scale?: number
24
24
  theme?: string
25
25
  }>()
@@ -37,7 +37,7 @@ watchEffect(async (onCleanup) => {
37
37
  error.value = null
38
38
  try {
39
39
  const svg = await renderMermaid(
40
- props.code || '',
40
+ props.codeLz || '',
41
41
  {
42
42
  theme: props.theme || (isDark.value ? 'dark' : undefined),
43
43
  ...vm!.attrs,
@@ -48,6 +48,7 @@ watchEffect(async (onCleanup) => {
48
48
  }
49
49
  catch (e) {
50
50
  error.value = `${e}`
51
+ console.warn(e)
51
52
  }
52
53
  })
53
54
 
@@ -76,6 +77,6 @@ watchEffect(() => {
76
77
  </script>
77
78
 
78
79
  <template>
79
- <pre v-if="error" border="1 red rounded" class="pa-3">{{ error }}</pre>
80
+ <pre v-if="error" border="1 red rounded" class="pa-3 text-wrap">{{ error }}</pre>
80
81
  <ShadowRoot v-else class="mermaid" :inner-html="html" @shadow="el = $event" />
81
82
  </template>
@@ -12,122 +12,195 @@ Learn more: https://sli.dev/guide/syntax.html#monaco-editor
12
12
  -->
13
13
 
14
14
  <script setup lang="ts">
15
- import { computed, onMounted, ref } from 'vue'
16
- import { useEventListener } from '@vueuse/core'
17
- import { decode } from 'js-base64'
18
- import { nanoid } from 'nanoid'
15
+ import { debounce } from '@antfu/utils'
16
+ import lz from 'lz-string'
19
17
  import type * as monaco from 'monaco-editor'
20
- import { isDark } from '../logic/dark'
18
+ import { computed, nextTick, onMounted, ref } from 'vue'
19
+ import { makeId } from '../logic/utils'
20
+ import CodeRunner from '../internals/CodeRunner.vue'
21
21
 
22
22
  const props = withDefaults(defineProps<{
23
- code: string
24
- diff?: string
23
+ codeLz: string
24
+ diffLz?: string
25
25
  lang?: string
26
26
  readonly?: boolean
27
27
  lineNumbers?: 'on' | 'off' | 'relative' | 'interval'
28
- height?: number | string
28
+ height?: number | string // Posible values: 'initial', 'auto', '100%', '200px', etc.
29
29
  editorOptions?: monaco.editor.IEditorOptions
30
+ ata?: boolean
31
+ runnable?: boolean
32
+ autorun?: boolean | 'once'
33
+ outputHeight?: string
34
+ highlightOutput?: boolean
35
+ runnerOptions?: Record<string, unknown>
30
36
  }>(), {
31
- code: '',
37
+ codeLz: '',
32
38
  lang: 'typescript',
33
39
  readonly: false,
34
40
  lineNumbers: 'off',
35
- height: 'auto',
41
+ height: 'initial',
42
+ ata: true,
43
+ runnable: false,
44
+ autorun: true,
45
+ highlightOutput: true,
36
46
  })
37
47
 
38
- const id = nanoid()
39
- const code = ref(decode(props.code).trimEnd())
40
- const diff = ref(props.diff ? decode(props.diff).trimEnd() : null)
41
- const lineHeight = +(getComputedStyle(document.body).getPropertyValue('--slidev-code-line-height') || '18').replace('px', '') || 18
42
- const editorHeight = ref(0)
43
- const calculatedHeight = computed(() => code.value.split(/\r?\n/g).length * lineHeight)
44
- const height = computed(() => {
45
- return props.height === 'auto' ? `${Math.max(calculatedHeight.value, editorHeight.value) + 20}px` : props.height
46
- })
48
+ const code = ref(lz.decompressFromBase64(props.codeLz).trimEnd())
49
+ const diff = props.diffLz && ref(lz.decompressFromBase64(props.diffLz).trimEnd())
47
50
 
48
- const iframe = ref<HTMLIFrameElement>()
49
-
50
- const cssVars = [
51
- '--slidev-code-font-size',
52
- '--slidev-code-font-family',
53
- '--slidev-code-background',
54
- '--slidev-code-line-height',
55
- '--slidev-code-padding',
56
- '--slidev-code-margin',
57
- '--slidev-code-radius',
58
- ]
59
-
60
- function getStyleObject(el: Element) {
61
- const object: Record<string, string> = {}
62
- const style = getComputedStyle(el)
63
- for (const v of cssVars)
64
- object[v] = style.getPropertyValue(v)
65
- return object
51
+ const langMap: Record<string, string> = {
52
+ ts: 'typescript',
53
+ js: 'javascript',
54
+ }
55
+ const lang = langMap[props.lang] ?? props.lang
56
+ const extMap: Record<string, string> = {
57
+ typescript: 'mts',
58
+ javascript: 'mjs',
59
+ ts: 'mts',
60
+ js: 'mjs',
66
61
  }
62
+ const ext = extMap[props.lang] ?? props.lang
67
63
 
68
- onMounted(() => {
69
- const frame = iframe.value!
70
- frame.setAttribute('sandbox', [
71
- 'allow-forms',
72
- 'allow-modals',
73
- 'allow-pointer-lock',
74
- 'allow-popups',
75
- 'allow-same-origin',
76
- 'allow-scripts',
77
- 'allow-top-navigation-by-user-activation',
78
- ].join(' '))
79
-
80
- let src = __DEV__
81
- ? `${location.origin}${__SLIDEV_CLIENT_ROOT__}/`
82
- : import.meta.env.BASE_URL
83
- src += `iframes/monaco/index.html?id=${id}&lineNumbers=${props.lineNumbers}&lang=${props.lang}`
84
- if (diff.value)
85
- src += '&diff=1'
86
- frame.src = src
87
-
88
- frame.style.backgroundColor = 'transparent'
64
+ const outer = ref<HTMLDivElement>()
65
+ const container = ref<HTMLDivElement>()
66
+
67
+ const contentHeight = ref(0)
68
+ const initialHeight = ref<number>()
69
+ const height = computed(() => {
70
+ if (props.height === 'auto')
71
+ return `${contentHeight.value}px`
72
+ if (props.height === 'initial')
73
+ return `${initialHeight.value}px`
74
+ return props.height
89
75
  })
90
76
 
91
- function post(payload: any) {
92
- iframe.value?.contentWindow?.postMessage(
93
- JSON.stringify({
94
- type: 'slidev-monaco',
95
- data: payload,
96
- id,
97
- }),
98
- location.origin,
99
- )
100
- }
77
+ onMounted(async () => {
78
+ // Lazy load monaco, so it will be bundled in async chunk
79
+ const { default: setup } = await import('../setup/monaco')
80
+ const { ata, monaco } = await setup()
81
+ const model = monaco.editor.createModel(code.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
82
+ model.onDidChangeContent(() => code.value = model.getValue())
83
+ const commonOptions = {
84
+ automaticLayout: true,
85
+ readOnly: props.readonly,
86
+ lineNumbers: props.lineNumbers,
87
+ minimap: { enabled: false },
88
+ overviewRulerBorder: false,
89
+ overviewRulerLanes: 0,
90
+ padding: { top: 10, bottom: 10 },
91
+ lineNumbersMinChars: 3,
92
+ bracketPairColorization: { enabled: false },
93
+ tabSize: 2,
94
+ fontSize: 11.5,
95
+ fontFamily: 'var(--slidev-code-font-family)',
96
+ scrollBeyondLastLine: false,
97
+ ...props.editorOptions,
98
+ } satisfies monaco.editor.IStandaloneEditorConstructionOptions & monaco.editor.IDiffEditorConstructionOptions
101
99
 
102
- useEventListener(window, 'message', ({ data: payload }) => {
103
- if (payload.id !== id)
104
- return
105
- if (payload.type === 'slidev-monaco-loaded') {
106
- if (iframe.value) {
107
- post({
108
- code: code.value,
109
- diff: diff.value,
110
- lang: props.lang,
111
- readonly: props.readonly,
112
- lineNumbers: props.lineNumbers,
113
- editorOptions: props.editorOptions,
114
- dark: isDark.value,
115
- style: Object.entries(getStyleObject(iframe.value)).map(([k, v]) => `${k}: ${v};`).join(''),
116
- })
100
+ let editableEditor: monaco.editor.IStandaloneCodeEditor
101
+ if (diff) {
102
+ const diffModel = monaco.editor.createModel(diff.value, lang, monaco.Uri.parse(`file:///${makeId()}.${ext}`))
103
+ diffModel.onDidChangeContent(() => code.value = model.getValue())
104
+ const editor = monaco.editor.createDiffEditor(container.value!, {
105
+ renderOverviewRuler: false,
106
+ ...commonOptions,
107
+ })
108
+ editor.setModel({
109
+ original: model,
110
+ modified: diffModel,
111
+ })
112
+ const originalEditor = editor.getOriginalEditor()
113
+ const modifiedEditor = editor.getModifiedEditor()
114
+ const onContentSizeChange = () => {
115
+ const newHeight = Math.max(originalEditor.getContentHeight(), modifiedEditor.getContentHeight()) + 4
116
+ initialHeight.value ??= newHeight
117
+ contentHeight.value = newHeight
118
+ nextTick(() => editor.layout())
117
119
  }
118
- return
120
+ originalEditor.onDidContentSizeChange(onContentSizeChange)
121
+ modifiedEditor.onDidContentSizeChange(onContentSizeChange)
122
+ editableEditor = modifiedEditor
119
123
  }
120
- if (payload.type !== 'slidev-monaco')
121
- return
122
- if (payload.data?.height)
123
- editorHeight.value = payload.data?.height
124
- if (payload?.data?.code && code.value !== payload.data.code)
125
- code.value = payload.data.code
126
- if (payload?.data?.diff && diff.value !== payload.data.diff)
127
- diff.value = payload.data.diff
124
+ else {
125
+ const editor = monaco.editor.create(container.value!, {
126
+ model,
127
+ lineDecorationsWidth: 0,
128
+ ...commonOptions,
129
+ })
130
+ editor.onDidContentSizeChange((e) => {
131
+ const newHeight = e.contentHeight + 4
132
+ initialHeight.value ??= newHeight
133
+ contentHeight.value = newHeight
134
+ nextTick(() => editableEditor.layout())
135
+ })
136
+ editableEditor = editor
137
+ }
138
+ if (props.ata) {
139
+ ata(editableEditor.getValue())
140
+ editableEditor.onDidChangeModelContent(debounce(1000, () => {
141
+ ata(editableEditor.getValue())
142
+ }))
143
+ }
144
+ const originalLayoutContentWidget = editableEditor.layoutContentWidget.bind(editableEditor)
145
+ editableEditor.layoutContentWidget = (widget: any) => {
146
+ originalLayoutContentWidget(widget)
147
+ const id = widget.getId()
148
+ if (id === 'editor.contrib.resizableContentHoverWidget') {
149
+ widget._resizableNode.domNode.style.transform = widget._positionPreference === 1
150
+ ? /* ABOVE */ `translateY(calc(100% * (var(--slidev-slide-scale) - 1)))`
151
+ : /* BELOW */ `` // reset
152
+ }
153
+ }
154
+ nextTick(() => monaco.editor.remeasureFonts())
128
155
  })
129
156
  </script>
130
157
 
131
158
  <template>
132
- <iframe ref="iframe" class="text-base w-full rounded" :style="{ height }" />
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
+ />
172
+ </div>
133
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>
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
3
+ import type { KeyedTokensInfo } from 'shiki-magic-move/types'
4
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
5
+ import lz from 'lz-string'
6
+ import { useSlideContext } from '../context'
7
+ import { makeId, updateCodeHighlightRange } from '../logic/utils'
8
+
9
+ const props = defineProps<{
10
+ at?: string | number
11
+ stepsLz: string
12
+ stepRanges: string[][]
13
+ }>()
14
+
15
+ const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
16
+ const { $clicksContext: clicks, $scale: scale } = useSlideContext()
17
+ const id = makeId()
18
+
19
+ const stepIndex = ref(0)
20
+ const container = ref<HTMLElement>()
21
+
22
+ // Normalized the ranges, to at least have one range
23
+ const ranges = computed(() => props.stepRanges.map(i => i.length ? i : ['all']))
24
+
25
+ onUnmounted(() => {
26
+ clicks!.unregister(id)
27
+ })
28
+
29
+ onMounted(() => {
30
+ if (!clicks || clicks.disabled)
31
+ return
32
+
33
+ if (ranges.value.length !== steps.length)
34
+ throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')
35
+
36
+ const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)
37
+ const { start, end, delta } = clicks.resolve(props.at ?? '+1', clickCounts - 1)
38
+ clicks.register(id, { max: end, delta })
39
+
40
+ watch(
41
+ () => clicks.current,
42
+ () => {
43
+ // Calculate the step and rangeStr based on the current click count
44
+ const clickCount = clicks.current - start
45
+ let step = steps.length - 1
46
+ let _currentClickSum = 0
47
+ let rangeStr = 'all'
48
+ for (let i = 0; i < ranges.value.length; i++) {
49
+ const current = ranges.value[i]
50
+ if (clickCount < _currentClickSum + current.length - 1) {
51
+ step = i
52
+ rangeStr = current[clickCount - _currentClickSum + 1]
53
+ break
54
+ }
55
+ _currentClickSum += current.length || 1
56
+ }
57
+ stepIndex.value = step
58
+
59
+ const pre = container.value?.querySelector('.shiki') as HTMLElement
60
+ if (!pre)
61
+ return
62
+
63
+ const children = (Array.from(pre.children) as HTMLElement[])
64
+ .slice(1) // Remove the first anchor
65
+ .filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
66
+
67
+ // Group to lines between `<br>`
68
+ const lines = children.reduce((acc, el) => {
69
+ if (el.tagName === 'BR')
70
+ acc.push([])
71
+ else
72
+ acc[acc.length - 1].push(el)
73
+ return acc
74
+ }, [[]] as HTMLElement[][])
75
+
76
+ // Update highlight range
77
+ updateCodeHighlightRange(
78
+ rangeStr,
79
+ lines.length,
80
+ 1,
81
+ no => lines[no],
82
+ )
83
+ },
84
+ { immediate: true },
85
+ )
86
+ })
87
+ </script>
88
+
89
+ <template>
90
+ <div ref="container" class="slidev-code-wrapper slidev-code-magic-move relative">
91
+ <ShikiMagicMovePrecompiled
92
+ class="slidev-code relative shiki overflow-visible"
93
+ :steps="steps"
94
+ :step="stepIndex"
95
+ :options="{
96
+ globalScale: scale,
97
+ // TODO: make this configurable later
98
+ duration: 800,
99
+ stagger: 1,
100
+ }"
101
+ />
102
+ </div>
103
+ </template>
@@ -22,7 +22,7 @@ const ended = ref(false)
22
22
  const matchRoute = computed(() => {
23
23
  if (!video.value || currentContext?.value !== 'slide')
24
24
  return false
25
- return route === $slidev?.nav.currentRoute
25
+ return route && route.no === $slidev?.nav.currentSlideNo
26
26
  })
27
27
 
28
28
  const matchClick = computed(() => {
package/builtin/Toc.vue CHANGED
@@ -74,7 +74,7 @@ function filterOnlySiblings(tree: TocItem[]): TocItem[] {
74
74
  }
75
75
 
76
76
  const toc = computed(() => {
77
- const tree = $slidev?.nav.tree
77
+ const tree = $slidev?.nav.tocTree
78
78
  if (!tree)
79
79
  return []
80
80
  let tocTree = filterTreeDepth(tree)
@@ -10,7 +10,7 @@ Usage:
10
10
  import { computed } from 'vue'
11
11
  import { toArray } from '@antfu/utils'
12
12
  import type { TocItem } from '@slidev/types'
13
- import Titles from '/@slidev/titles.md'
13
+ import TitleRenderer from '#slidev/title-renderer'
14
14
 
15
15
  const props = withDefaults(defineProps<{
16
16
  level: number
@@ -48,7 +48,7 @@ const styles = computed(() => {
48
48
  :class="[{ 'slidev-toc-item-active': item.active }, { 'slidev-toc-item-parent-active': item.activeParent }]"
49
49
  >
50
50
  <Link :to="item.path">
51
- <Titles :no="item.path" />
51
+ <TitleRenderer :no="item.no" />
52
52
  </Link>
53
53
  <TocList
54
54
  v-if="item.children.length > 0"
@@ -65,7 +65,8 @@ const styles = computed(() => {
65
65
  .slidev-layout .slidev-toc-item p {
66
66
  margin: 0;
67
67
  }
68
- .slidev-layout .slidev-toc-item div, .slidev-layout .slidev-toc-item div p {
68
+ .slidev-layout .slidev-toc-item div,
69
+ .slidev-layout .slidev-toc-item div p {
69
70
  display: initial;
70
71
  }
71
72
  </style>
package/builtin/Tweet.vue CHANGED
@@ -7,9 +7,8 @@ Usage:
7
7
  -->
8
8
 
9
9
  <script setup lang="ts">
10
- import { getCurrentInstance, onMounted, ref } from 'vue'
10
+ import { onMounted, ref } from 'vue'
11
11
  import { isDark } from '../logic/dark'
12
- import { useTweetScript } from '../composables/useTweetScript'
13
12
 
14
13
  const props = defineProps<{
15
14
  id: string | number
@@ -20,11 +19,17 @@ const props = defineProps<{
20
19
 
21
20
  const tweet = ref<HTMLElement | null>()
22
21
 
23
- const vm = getCurrentInstance()!
24
22
  const loaded = ref(false)
25
23
  const tweetNotFound = ref(false)
26
24
 
27
- async function create() {
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
+ }
28
33
  // @ts-expect-error global
29
34
  const element = await window.twttr.widgets.createTweet(
30
35
  props.id.toString(),
@@ -40,11 +45,9 @@ async function create() {
40
45
  tweetNotFound.value = true
41
46
  }
42
47
 
43
- // @ts-expect-error global
44
- if (window?.twttr?.widgets)
45
- onMounted(create)
46
- else
47
- useTweetScript(vm, create)
48
+ onMounted(() => {
49
+ create()
50
+ })
48
51
  </script>
49
52
 
50
53
  <template>