@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.
- package/builtin/Monaco.vue +64 -8
- package/builtin/ShikiMagicMove.vue +6 -3
- package/builtin/Tweet.vue +12 -1
- package/composables/useClicks.ts +10 -2
- package/composables/useNav.ts +12 -8
- package/{logic/note.ts → composables/useSlideInfo.ts} +11 -14
- package/composables/useViewTransition.ts +7 -4
- package/context.ts +2 -0
- package/index.html +1 -0
- package/internals/CodeRunner.vue +139 -0
- package/internals/DomElement.vue +18 -0
- package/internals/IconButton.vue +1 -1
- package/internals/NoteEditable.vue +4 -3
- package/internals/NoteStatic.vue +2 -2
- package/internals/PrintSlideClick.vue +1 -1
- package/internals/QuickOverview.vue +2 -1
- package/internals/SideEditor.vue +1 -1
- package/internals/SlideLoading.vue +1 -1
- package/internals/SlideWrapper.vue +79 -0
- package/internals/SlidesShow.vue +1 -1
- package/logic/route.ts +6 -1
- package/logic/utils.ts +2 -3
- package/main.ts +1 -0
- package/modules/mermaid.ts +3 -1
- package/package.json +8 -7
- package/pages/overview.vue +7 -6
- package/pages/presenter.vue +1 -1
- package/setup/code-runners.ts +169 -0
- package/setup/monaco.ts +11 -10
- package/shim-vue.d.ts +4 -8
- package/internals/SlideWrapper.ts +0 -58
- package/styles/monaco.css +0 -27
package/builtin/Monaco.vue
CHANGED
|
@@ -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
|
|
148
|
-
<div ref="
|
|
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="{
|
|
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
|
-
|
|
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
|
|
package/composables/useClicks.ts
CHANGED
|
@@ -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
|
package/composables/useNav.ts
CHANGED
|
@@ -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(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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: {
|
|
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(
|
|
13
|
-
if (
|
|
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/${
|
|
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.
|
|
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.
|
|
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<
|
|
55
|
+
const map: Record<number, UseSlideInfo> = {}
|
|
56
56
|
|
|
57
|
-
export function useDynamicSlideInfo(
|
|
58
|
-
function get(
|
|
59
|
-
|
|
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(
|
|
63
|
+
info: computed(() => get(unref(no)).info.value),
|
|
67
64
|
update: async (data: SlidePatch, newId?: number) => {
|
|
68
|
-
const info = get(newId ?? unref(
|
|
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
|
|
15
|
-
const
|
|
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
|
-
(
|
|
20
|
-
|| (
|
|
22
|
+
(fromMeta?.transition === 'view-transition' && fromNo < toNo)
|
|
23
|
+
|| (toMeta?.transition === 'view-transition' && toNo < fromNo)
|
|
21
24
|
)
|
|
22
25
|
)
|
|
23
26
|
) {
|
package/context.ts
CHANGED
package/index.html
CHANGED
|
@@ -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>
|
package/internals/IconButton.vue
CHANGED
|
@@ -7,7 +7,7 @@ defineProps<{
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
|
-
<component :is="as || 'button'" class="slidev-icon-btn" :title="title"
|
|
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 '../
|
|
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
|
|
42
|
+
const { info, update } = useDynamicSlideInfo(toRef(props, 'no'))
|
|
42
43
|
|
|
43
44
|
const note = ref('')
|
|
44
45
|
let timer: any
|
package/internals/NoteStatic.vue
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ClicksContext } from '@slidev/types'
|
|
3
|
-
import { useSlideInfo } from '../
|
|
3
|
+
import { useSlideInfo } from '../composables/useSlideInfo'
|
|
4
4
|
import NoteDisplay from './NoteDisplay.vue'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
|
-
no
|
|
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"
|
package/internals/SideEditor.vue
CHANGED
|
@@ -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 '../
|
|
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>
|
package/internals/SlidesShow.vue
CHANGED
|
@@ -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']({
|
|
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 = '
|
|
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
package/modules/mermaid.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
56
|
-
"typescript": "^5.
|
|
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.
|
|
61
|
-
"@slidev/types": "0.48.0-beta.
|
|
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.
|
|
65
|
+
"vite": "^5.1.5"
|
|
65
66
|
}
|
|
66
67
|
}
|
package/pages/overview.vue
CHANGED
|
@@ -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 ===
|
|
196
|
-
@click="edittingNote =
|
|
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="
|
|
203
|
+
:no="route.no"
|
|
203
204
|
class="max-w-250 w-250 text-lg rounded p3"
|
|
204
205
|
:auto-height="true"
|
|
205
|
-
:editing="edittingNote ===
|
|
206
|
+
:editing="edittingNote === route.no"
|
|
206
207
|
:clicks-context="getClicksContext(route)"
|
|
207
|
-
@dblclick="edittingNote !==
|
|
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
|
/>
|
package/pages/presenter.vue
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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:
|
|
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
|
-
}
|