@slidev/client 0.49.11 → 0.49.12
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/assets/logo.svg +60 -0
- package/builtin/PoweredBySlidev.vue +8 -0
- package/composables/useClicks.ts +41 -11
- package/composables/useNav.ts +1 -7
- package/index.ts +1 -0
- package/internals/ClicksSlider.vue +1 -1
- package/internals/CodeRunner.vue +6 -6
- package/internals/NoteDisplay.vue +88 -78
- package/internals/ShikiEditor.vue +60 -0
- package/internals/SideEditor.vue +22 -77
- package/internals/SlideWrapper.vue +4 -5
- package/logic/slides.ts +19 -0
- package/package.json +15 -16
- package/pages/404.vue +11 -11
- package/pages/overview.vue +1 -1
- package/pages/presenter.vue +3 -4
- package/setup/code-runners.ts +23 -32
- package/state/index.ts +1 -1
- package/styles/code.css +1 -5
- package/uno.config.ts +9 -0
- package/modules/codemirror.ts +0 -56
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<svg width="115" height="115" viewBox="0 0 115 115" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0)">
|
|
3
|
+
<g filter="url(#filter0_d)">
|
|
4
|
+
<rect x="14" y="14" width="90" height="90" rx="15" fill="url(#paint0_linear)"/>
|
|
5
|
+
</g>
|
|
6
|
+
<g filter="url(#filter1_d)">
|
|
7
|
+
<rect width="90" height="90" rx="45" fill="url(#paint1_linear)"/>
|
|
8
|
+
</g>
|
|
9
|
+
<g filter="url(#filter2_d)">
|
|
10
|
+
<path d="M56.127 63.925C54.9501 59.5327 54.3617 57.3366 54.9439 55.8199C55.4517 54.497 56.497 53.4517 57.8199 52.9439C59.3366 52.3617 61.5327 52.9501 65.925 54.127L87.7563 59.9767C92.1485 61.1536 94.3446 61.742 95.367 63.0046C96.2588 64.1058 96.6414 65.5338 96.4197 66.9334C96.1656 68.5379 94.5579 70.1456 91.3426 73.3609L75.3609 89.3426C72.1456 92.5579 70.5379 94.1656 68.9333 94.4197C67.5337 94.6414 66.1058 94.2588 65.0046 93.367C63.742 92.3446 63.1536 90.1485 61.9767 85.7563L56.127 63.925Z" fill="url(#paint2_linear)"/>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
<defs>
|
|
14
|
+
<filter id="filter0_d" x="8" y="8" width="110" height="110" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
15
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
16
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
|
17
|
+
<feOffset dx="4" dy="4"/>
|
|
18
|
+
<feGaussianBlur stdDeviation="5"/>
|
|
19
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
|
|
20
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
|
21
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
|
22
|
+
</filter>
|
|
23
|
+
<filter id="filter1_d" x="-6" y="-6" width="110" height="110" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
24
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
25
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
|
26
|
+
<feOffset dx="4" dy="4"/>
|
|
27
|
+
<feGaussianBlur stdDeviation="5"/>
|
|
28
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
|
|
29
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
|
30
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
|
31
|
+
</filter>
|
|
32
|
+
<filter id="filter2_d" x="34.8833" y="32.8833" width="77.6614" height="77.6614" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
33
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
34
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
|
35
|
+
<feOffset dx="4" dy="4"/>
|
|
36
|
+
<feGaussianBlur stdDeviation="5"/>
|
|
37
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
|
|
38
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
|
39
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
|
40
|
+
</filter>
|
|
41
|
+
<linearGradient id="paint0_linear" x1="14" y1="6" x2="104" y2="104" gradientUnits="userSpaceOnUse">
|
|
42
|
+
<stop stop-color="#3ACBD4"/>
|
|
43
|
+
<stop offset="1" stop-color="#2988B1"/>
|
|
44
|
+
</linearGradient>
|
|
45
|
+
<linearGradient id="paint1_linear" x1="-9.5" y1="-11" x2="76.0825" y2="90" gradientUnits="userSpaceOnUse">
|
|
46
|
+
<stop stop-color="#95F0CF"/>
|
|
47
|
+
<stop offset="1" stop-color="#3AB9D5"/>
|
|
48
|
+
</linearGradient>
|
|
49
|
+
<linearGradient id="paint2_linear" x1="54.6616" y1="49.3453" x2="59.8793" y2="96.3585" gradientUnits="userSpaceOnUse">
|
|
50
|
+
<stop stop-color="#FFEB83"/>
|
|
51
|
+
<stop offset="0.0001" stop-color="#FFEB83"/>
|
|
52
|
+
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
|
53
|
+
<stop offset="0.601773" stop-color="#FFBB13"/>
|
|
54
|
+
<stop offset="1" stop-color="#FFA800"/>
|
|
55
|
+
</linearGradient>
|
|
56
|
+
<clipPath id="clip0">
|
|
57
|
+
<rect width="115" height="115" fill="white"/>
|
|
58
|
+
</clipPath>
|
|
59
|
+
</defs>
|
|
60
|
+
</svg>
|
package/composables/useClicks.ts
CHANGED
|
@@ -30,18 +30,42 @@ export function createClicksContextBase(
|
|
|
30
30
|
clicksStart = 0,
|
|
31
31
|
clicksTotalOverrides?: number,
|
|
32
32
|
): ClicksContext {
|
|
33
|
+
const isMounted = ref(false)
|
|
34
|
+
let relativeSizeMap: ClicksContext['relativeSizeMap'] = new Map()
|
|
35
|
+
let maxMap: ClicksContext['maxMap'] = new Map()
|
|
33
36
|
const context: ClicksContext = {
|
|
34
37
|
get current() {
|
|
35
|
-
// Here we haven't know clicksTotal yet.
|
|
36
38
|
return clamp(+current.value, clicksStart, context.total)
|
|
37
39
|
},
|
|
38
40
|
set current(value) {
|
|
39
|
-
current.value =
|
|
41
|
+
current.value = isMounted.value
|
|
42
|
+
? clamp(value, clicksStart, context.total)
|
|
43
|
+
: value /* context.total is not available yet */
|
|
40
44
|
},
|
|
41
45
|
clicksStart,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
get relativeSizeMap() {
|
|
47
|
+
if (__DEV__ && isMounted.value)
|
|
48
|
+
console.warn('[slidev] ClicksContext: Unexpected access to relativeSizeMap after mounted')
|
|
49
|
+
return relativeSizeMap
|
|
50
|
+
},
|
|
51
|
+
get maxMap() {
|
|
52
|
+
return maxMap
|
|
53
|
+
},
|
|
54
|
+
get isMounted() {
|
|
55
|
+
return isMounted.value
|
|
56
|
+
},
|
|
57
|
+
onMounted: () => {
|
|
58
|
+
isMounted.value = true
|
|
59
|
+
// Convert maxMap to reactive
|
|
60
|
+
maxMap = shallowReactive(maxMap)
|
|
61
|
+
// Make sure the query is not greater than the total
|
|
62
|
+
context.current = current.value
|
|
63
|
+
},
|
|
64
|
+
onUnmounted: () => {
|
|
65
|
+
isMounted.value = false
|
|
66
|
+
relativeSizeMap = new Map()
|
|
67
|
+
maxMap = new Map()
|
|
68
|
+
},
|
|
45
69
|
calculateSince(rawAt, size = 1) {
|
|
46
70
|
const at = normalizeSingleAtValue(rawAt)
|
|
47
71
|
if (at == null)
|
|
@@ -109,23 +133,29 @@ export function createClicksContextBase(
|
|
|
109
133
|
register(el, info) {
|
|
110
134
|
if (!info)
|
|
111
135
|
return
|
|
136
|
+
if (__DEV__ && isMounted.value)
|
|
137
|
+
console.warn('[slidev] ClicksContext: Unexpected register after mounted')
|
|
112
138
|
const { delta, max } = info
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
relativeSizeMap.set(el, delta)
|
|
140
|
+
maxMap.set(el, max)
|
|
115
141
|
},
|
|
116
142
|
unregister(el) {
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
relativeSizeMap.delete(el)
|
|
144
|
+
maxMap.delete(el)
|
|
119
145
|
},
|
|
120
146
|
get currentOffset() {
|
|
121
147
|
// eslint-disable-next-line no-unused-expressions
|
|
122
148
|
routeForceRefresh.value
|
|
123
|
-
return sum(...
|
|
149
|
+
return sum(...relativeSizeMap.values())
|
|
124
150
|
},
|
|
125
151
|
get total() {
|
|
126
152
|
// eslint-disable-next-line no-unused-expressions
|
|
127
153
|
routeForceRefresh.value
|
|
128
|
-
return clicksTotalOverrides
|
|
154
|
+
return clicksTotalOverrides
|
|
155
|
+
?? (isMounted.value
|
|
156
|
+
? Math.max(0, ...maxMap.values())
|
|
157
|
+
: 0 /* fallback value */
|
|
158
|
+
)
|
|
129
159
|
},
|
|
130
160
|
}
|
|
131
161
|
return context
|
package/composables/useNav.ts
CHANGED
|
@@ -327,19 +327,13 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
|
|
|
327
327
|
},
|
|
328
328
|
set(v) {
|
|
329
329
|
if (currentSlideNo.value === thisNo)
|
|
330
|
-
queryClicksRaw.value =
|
|
330
|
+
queryClicksRaw.value = v.toString()
|
|
331
331
|
},
|
|
332
332
|
}),
|
|
333
333
|
route?.meta.slide?.frontmatter.clicksStart ?? 0,
|
|
334
334
|
route?.meta.clicks,
|
|
335
335
|
)
|
|
336
336
|
|
|
337
|
-
// On slide mounted, make sure the query is not greater than the total
|
|
338
|
-
context.onMounted = () => {
|
|
339
|
-
if (currentSlideNo.value === thisNo)
|
|
340
|
-
queryClicksRaw.value = clamp(+queryClicksRaw.value, context.clicksStart, context.total).toString()
|
|
341
|
-
}
|
|
342
|
-
|
|
343
337
|
if (route?.meta)
|
|
344
338
|
route.meta.__clicksContext = context
|
|
345
339
|
|
package/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { useSlideContext } from './context'
|
|
|
7
7
|
export { useNav } from './composables/useNav'
|
|
8
8
|
export { useDrawings } from './composables/useDrawings'
|
|
9
9
|
export { useDarkMode } from './composables/useDarkMode'
|
|
10
|
+
export { useIsSlideActive, onSlideEnter, onSlideLeave } from './logic/slides'
|
|
10
11
|
|
|
11
12
|
export * from './layoutHelper'
|
|
12
13
|
export * from './env'
|
|
@@ -39,7 +39,7 @@ function onMousedown() {
|
|
|
39
39
|
<div
|
|
40
40
|
class="flex gap-1 items-center select-none"
|
|
41
41
|
:title="`Clicks in this slide: ${length}`"
|
|
42
|
-
:class="length ? '' : 'op50'"
|
|
42
|
+
:class="length && props.clicksContext.isMounted ? '' : 'op50'"
|
|
43
43
|
>
|
|
44
44
|
<div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
|
|
45
45
|
<carbon:cursor-1 text-sm op50 />
|
package/internals/CodeRunner.vue
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { debounce, toArray } from '@antfu/utils'
|
|
3
3
|
import { useVModel } from '@vueuse/core'
|
|
4
|
-
import type {
|
|
5
|
-
import { computed, onMounted, onUnmounted, ref, shallowRef, watch, watchSyncEffect } from 'vue'
|
|
4
|
+
import type { CodeRunnerOutputs, RawAtValue } from '@slidev/types'
|
|
5
|
+
import { computed, onMounted, onUnmounted, ref, shallowRef, toValue, watch, watchSyncEffect } from 'vue'
|
|
6
6
|
import { useSlideContext } from '../context'
|
|
7
7
|
import setupCodeRunners from '../setup/code-runners'
|
|
8
8
|
import { useNav } from '../composables/useNav'
|
|
@@ -31,7 +31,7 @@ const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.
|
|
|
31
31
|
|
|
32
32
|
const autorun = isPrintMode.value ? 'once' : props.autorun
|
|
33
33
|
const isRunning = ref(autorun)
|
|
34
|
-
const outputs = shallowRef<
|
|
34
|
+
const outputs = shallowRef<CodeRunnerOutputs>()
|
|
35
35
|
const runCount = ref(0)
|
|
36
36
|
const highlightFn = ref<(code: string, lang: string) => string>()
|
|
37
37
|
|
|
@@ -64,7 +64,7 @@ const triggerRun = debounce(200, async () => {
|
|
|
64
64
|
isRunning.value = true
|
|
65
65
|
}, 500)
|
|
66
66
|
|
|
67
|
-
outputs.value =
|
|
67
|
+
outputs.value = await run(code.value, props.lang, props.runnerOptions ?? {})
|
|
68
68
|
runCount.value += 1
|
|
69
69
|
isRunning.value = false
|
|
70
70
|
|
|
@@ -90,11 +90,11 @@ else if (autorun)
|
|
|
90
90
|
<div v-else-if="isRunning" class="text-sm text-center opacity-50">
|
|
91
91
|
Running...
|
|
92
92
|
</div>
|
|
93
|
-
<div v-else-if="!outputs
|
|
93
|
+
<div v-else-if="!outputs" class="text-sm text-center opacity-50">
|
|
94
94
|
Click the play button to run the code
|
|
95
95
|
</div>
|
|
96
96
|
<div v-else :key="`run-${runCount}`" class="slidev-runner-output">
|
|
97
|
-
<template v-for="output, _idx1 of outputs" :key="_idx1">
|
|
97
|
+
<template v-for="output, _idx1 of toArray(toValue(outputs))" :key="_idx1">
|
|
98
98
|
<div v-if="'html' in output" v-html="output.html" />
|
|
99
99
|
<div v-else-if="'error' in output" class="text-red-500">
|
|
100
100
|
{{ output.error }}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
2
|
+
import { computed, nextTick, onMounted, ref, watch, watchEffect } from 'vue'
|
|
3
3
|
import type { ClicksContext } from '@slidev/types'
|
|
4
4
|
import { CLICKS_MAX } from '../constants'
|
|
5
5
|
|
|
@@ -29,108 +29,118 @@ const noteDisplay = ref<HTMLElement | null>(null)
|
|
|
29
29
|
const CLASS_FADE = 'slidev-note-fade'
|
|
30
30
|
const CLASS_MARKER = 'slidev-note-click-mark'
|
|
31
31
|
|
|
32
|
-
function
|
|
32
|
+
function processNote() {
|
|
33
33
|
if (!noteDisplay.value || !withClicks.value)
|
|
34
34
|
return
|
|
35
35
|
|
|
36
36
|
const markers = Array.from(noteDisplay.value.querySelectorAll(`.${CLASS_MARKER}`)) as HTMLElement[]
|
|
37
37
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const nodeToIgnores = new Set<Element>()
|
|
42
|
-
function ignoreParent(node: Element) {
|
|
43
|
-
if (!node || node === noteDisplay.value)
|
|
44
|
-
return
|
|
45
|
-
nodeToIgnores.add(node)
|
|
46
|
-
if (node.parentElement)
|
|
47
|
-
ignoreParent(node.parentElement)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const markersMap = new Map<number, HTMLElement>()
|
|
51
|
-
|
|
52
|
-
// Convert all sibling text nodes to spans, so we attach classes to them
|
|
38
|
+
const markersMap = new Map<HTMLElement, number>()
|
|
39
|
+
const parentsMap = new Map<HTMLElement, [divider: HTMLElement | null, dividerClicks: number][]>()
|
|
40
|
+
let lastClicks = 0
|
|
53
41
|
for (const marker of markers) {
|
|
54
|
-
const parent = marker.parentElement!
|
|
55
42
|
const clicks = Number(marker.dataset!.clicks)
|
|
56
|
-
markersMap.set(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let count = 0
|
|
72
|
-
|
|
73
|
-
// Segmenting notes by clicks
|
|
74
|
-
const segments = new Map<number, Element[]>()
|
|
75
|
-
for (const child of children) {
|
|
76
|
-
if (!segments.has(count))
|
|
77
|
-
segments.set(count, [])
|
|
78
|
-
segments.get(count)!.push(child)
|
|
79
|
-
// Update count when reach marker
|
|
80
|
-
if (child.classList.contains(CLASS_MARKER))
|
|
81
|
-
count = Number((child as HTMLElement).dataset.clicks) || (count + 1)
|
|
43
|
+
markersMap.set(marker, clicks)
|
|
44
|
+
|
|
45
|
+
// Set parent clicks map
|
|
46
|
+
let n = marker
|
|
47
|
+
let p = marker.parentElement
|
|
48
|
+
while (p && n !== noteDisplay.value) {
|
|
49
|
+
if (!parentsMap.has(p))
|
|
50
|
+
parentsMap.set(p, [[null, lastClicks]])
|
|
51
|
+
parentsMap.get(p)!.push([n, clicks])
|
|
52
|
+
n = p
|
|
53
|
+
p = p.parentElement
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
lastClicks = clicks
|
|
82
57
|
}
|
|
83
58
|
|
|
84
|
-
|
|
85
|
-
for (const [
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
59
|
+
const siblingsMap = new Map<HTMLElement, number>()
|
|
60
|
+
for (const [parent, dividers] of parentsMap) {
|
|
61
|
+
let hasPrefix = false
|
|
62
|
+
let dividerIdx = 0
|
|
63
|
+
for (const sibling of Array.from(parent.childNodes)) {
|
|
64
|
+
let skip = false
|
|
65
|
+
while (sibling === dividers[dividerIdx + 1]?.[0]) {
|
|
66
|
+
skip = true
|
|
67
|
+
dividerIdx++
|
|
68
|
+
}
|
|
69
|
+
if (skip)
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
// Convert sibling text nodes to spans
|
|
73
|
+
let siblingEl = sibling as HTMLElement
|
|
74
|
+
if (sibling.nodeType === 3 /* text node */) {
|
|
75
|
+
if (!sibling.textContent?.trim())
|
|
76
|
+
continue
|
|
77
|
+
siblingEl = document.createElement('span')
|
|
78
|
+
siblingEl.textContent = sibling.textContent
|
|
79
|
+
parent.insertBefore(siblingEl, sibling)
|
|
80
|
+
sibling.remove()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
hasPrefix ||= dividerIdx === 0
|
|
84
|
+
siblingsMap.set(siblingEl, dividers[dividerIdx][1])
|
|
96
85
|
}
|
|
86
|
+
if (!hasPrefix)
|
|
87
|
+
dividers[0][1] = -1
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
// Apply
|
|
91
|
+
return (current: number) => {
|
|
92
|
+
const enabled = props.highlight
|
|
93
|
+
for (const [parent, clicks] of parentsMap)
|
|
94
|
+
parent.classList.toggle(CLASS_FADE, enabled && !clicks.some(([_, c]) => c === current))
|
|
95
|
+
for (const [parent, clicks] of siblingsMap)
|
|
96
|
+
parent.classList.toggle(CLASS_FADE, enabled && clicks !== current)
|
|
97
|
+
for (const [marker, clicks] of markersMap) {
|
|
98
|
+
marker.classList.remove(CLASS_FADE)
|
|
99
|
+
marker.classList.toggle(`${CLASS_MARKER}-past`, enabled && clicks < current)
|
|
100
|
+
marker.classList.toggle(`${CLASS_MARKER}-active`, enabled && clicks === current)
|
|
101
|
+
marker.classList.toggle(`${CLASS_MARKER}-next`, enabled && clicks === current + 1)
|
|
102
|
+
marker.classList.toggle(`${CLASS_MARKER}-future`, enabled && clicks > current + 1)
|
|
103
|
+
marker.ondblclick = enabled
|
|
104
|
+
? (e) => {
|
|
105
|
+
emit('markerDblclick', e, clicks)
|
|
106
|
+
if (e.defaultPrevented)
|
|
107
|
+
return
|
|
108
|
+
props.clicksContext!.current = clicks
|
|
109
|
+
e.stopPropagation()
|
|
110
|
+
e.stopImmediatePropagation()
|
|
111
|
+
}
|
|
112
|
+
: null
|
|
113
|
+
marker.onclick = enabled
|
|
114
|
+
? (e) => {
|
|
115
|
+
emit('markerClick', e, clicks)
|
|
116
|
+
}
|
|
117
|
+
: null
|
|
118
|
+
|
|
119
|
+
if (!enabled && props.autoScroll && clicks === current)
|
|
120
|
+
marker.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
if (props.autoScroll && clicks === current)
|
|
118
|
-
marker.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
const applyHighlight = ref<ReturnType<typeof processNote>>()
|
|
126
|
+
|
|
122
127
|
watch(
|
|
123
|
-
() => [props.noteHtml, props.
|
|
128
|
+
() => [props.noteHtml, props.highlight],
|
|
124
129
|
() => {
|
|
125
130
|
nextTick(() => {
|
|
126
|
-
|
|
131
|
+
applyHighlight.value = processNote()
|
|
127
132
|
})
|
|
128
133
|
},
|
|
129
134
|
{ immediate: true },
|
|
130
135
|
)
|
|
131
136
|
|
|
132
137
|
onMounted(() => {
|
|
133
|
-
|
|
138
|
+
processNote()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
watchEffect(() => {
|
|
142
|
+
const current = props.clicksContext?.current ?? CLICKS_MAX
|
|
143
|
+
applyHighlight.value?.(current)
|
|
134
144
|
})
|
|
135
145
|
</script>
|
|
136
146
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watchEffect } from 'vue'
|
|
3
|
+
import { getHighlighter } from '#slidev/shiki'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
placeholder?: string
|
|
7
|
+
}>()
|
|
8
|
+
const content = defineModel<string>({ required: true })
|
|
9
|
+
|
|
10
|
+
const textareaEl = ref<HTMLTextAreaElement | null>(null)
|
|
11
|
+
|
|
12
|
+
const html = ref('')
|
|
13
|
+
|
|
14
|
+
watchEffect((onCleanup) => {
|
|
15
|
+
let canceled = false
|
|
16
|
+
onCleanup(() => canceled = true)
|
|
17
|
+
|
|
18
|
+
const c = content.value
|
|
19
|
+
async function updateHtml() {
|
|
20
|
+
const highlight = await getHighlighter()
|
|
21
|
+
if (canceled)
|
|
22
|
+
return
|
|
23
|
+
const h = await highlight(c, 'markdown')
|
|
24
|
+
if (canceled)
|
|
25
|
+
return
|
|
26
|
+
html.value = h
|
|
27
|
+
}
|
|
28
|
+
updateHtml()
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="absolute inset-x-3 inset-y-2 font-mono overflow-x-hidden overflow-y-auto">
|
|
34
|
+
<div class="relative w-full h-max">
|
|
35
|
+
<div class="relative w-full h-max" v-html="html" />
|
|
36
|
+
<textarea
|
|
37
|
+
ref="textareaEl" v-model="content" :placeholder="props.placeholder"
|
|
38
|
+
class="absolute inset-0 resize-none text-transparent bg-transparent focus:outline-none caret-white overflow-y-hidden"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
:deep(code),
|
|
46
|
+
textarea {
|
|
47
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
|
48
|
+
'Liberation Mono', 'Courier New', monospace;
|
|
49
|
+
font-feature-settings: normal;
|
|
50
|
+
font-variation-settings: normal;
|
|
51
|
+
font-size: 1em;
|
|
52
|
+
text-wrap: wrap;
|
|
53
|
+
word-break: break-all;
|
|
54
|
+
display: block;
|
|
55
|
+
width: 100%;
|
|
56
|
+
}
|
|
57
|
+
:deep(pre.shiki) {
|
|
58
|
+
background-color: transparent;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
package/internals/SideEditor.vue
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { throttledWatch, useEventListener
|
|
3
|
-
import { computed,
|
|
4
|
-
import { activeElement, editorHeight, editorWidth,
|
|
5
|
-
import { useCodeMirror } from '../modules/codemirror'
|
|
2
|
+
import { throttledWatch, useEventListener } from '@vueuse/core'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
import { activeElement, editorHeight, editorWidth, isInputting, showEditor, isEditorVertical as vertical } from '../state'
|
|
6
5
|
import { useNav } from '../composables/useNav'
|
|
7
6
|
import { useDynamicSlideInfo } from '../composables/useSlideInfo'
|
|
8
7
|
import IconButton from './IconButton.vue'
|
|
8
|
+
import ShikiEditor from './ShikiEditor.vue'
|
|
9
9
|
|
|
10
10
|
const props = defineProps<{
|
|
11
11
|
resize?: boolean
|
|
@@ -18,8 +18,6 @@ const content = ref('')
|
|
|
18
18
|
const note = ref('')
|
|
19
19
|
const dirty = ref(false)
|
|
20
20
|
const frontmatter = ref<any>({})
|
|
21
|
-
const contentInput = ref<HTMLTextAreaElement>()
|
|
22
|
-
const noteInput = ref<HTMLTextAreaElement>()
|
|
23
21
|
|
|
24
22
|
const { info, update } = useDynamicSlideInfo(currentSlideNo)
|
|
25
23
|
|
|
@@ -57,65 +55,22 @@ useEventListener('keydown', (e) => {
|
|
|
57
55
|
}
|
|
58
56
|
})
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
72
|
-
{
|
|
73
|
-
mode: 'markdown',
|
|
74
|
-
lineWrapping: true,
|
|
75
|
-
// @ts-expect-error missing types
|
|
76
|
-
highlightFormatting: true,
|
|
77
|
-
fencedCodeBlockDefaultMode: 'javascript',
|
|
78
|
-
},
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
const noteEditor = await useCodeMirror(
|
|
82
|
-
noteInput,
|
|
83
|
-
computed({
|
|
84
|
-
get() { return note.value },
|
|
85
|
-
set(v) {
|
|
86
|
-
note.value = v
|
|
87
|
-
dirty.value = true
|
|
88
|
-
},
|
|
89
|
-
}),
|
|
90
|
-
{
|
|
91
|
-
mode: 'markdown',
|
|
92
|
-
lineWrapping: true,
|
|
93
|
-
// @ts-expect-error missing types
|
|
94
|
-
highlightFormatting: true,
|
|
95
|
-
fencedCodeBlockDefaultMode: 'javascript',
|
|
96
|
-
},
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
watchThrottled(
|
|
100
|
-
[tab, vertical, isEditorVertical, editorWidth, editorHeight],
|
|
101
|
-
() => {
|
|
102
|
-
nextTick(() => {
|
|
103
|
-
if (tab.value === 'content')
|
|
104
|
-
contentEditor.refresh()
|
|
105
|
-
else
|
|
106
|
-
noteEditor.refresh()
|
|
107
|
-
})
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
throttle: 100,
|
|
111
|
-
flush: 'post',
|
|
112
|
-
},
|
|
113
|
-
)
|
|
58
|
+
const contentRef = computed({
|
|
59
|
+
get() { return content.value },
|
|
60
|
+
set(v) {
|
|
61
|
+
if (content.value.trim() !== v.trim()) {
|
|
62
|
+
content.value = v
|
|
63
|
+
dirty.value = true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
})
|
|
114
67
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
68
|
+
const noteRef = computed({
|
|
69
|
+
get() { return note.value },
|
|
70
|
+
set(v) {
|
|
71
|
+
note.value = v
|
|
72
|
+
dirty.value = true
|
|
73
|
+
},
|
|
119
74
|
})
|
|
120
75
|
|
|
121
76
|
const handlerDown = ref(false)
|
|
@@ -212,19 +167,9 @@ throttledWatch(
|
|
|
212
167
|
<carbon:close />
|
|
213
168
|
</IconButton>
|
|
214
169
|
</div>
|
|
215
|
-
<div class="overflow-hidden">
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
</div>
|
|
219
|
-
<div v-show="tab === 'note'" class="w-full h-full">
|
|
220
|
-
<textarea ref="noteInput" placeholder="Write some notes..." />
|
|
221
|
-
</div>
|
|
170
|
+
<div class="relative overflow-hidden rounded" style="background-color: var(--slidev-code-background)">
|
|
171
|
+
<ShikiEditor v-show="tab === 'content'" v-model="contentRef" placeholder="Create slide content..." />
|
|
172
|
+
<ShikiEditor v-show="tab === 'note'" v-model="noteRef" placeholder="Write some notes..." />
|
|
222
173
|
</div>
|
|
223
174
|
</div>
|
|
224
175
|
</template>
|
|
225
|
-
|
|
226
|
-
<style lang="postcss">
|
|
227
|
-
.CodeMirror {
|
|
228
|
-
@apply px-3 py-2 h-full overflow-hidden bg-transparent font-mono text-sm z-0;
|
|
229
|
-
}
|
|
230
|
-
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, defineAsyncComponent, defineComponent, h,
|
|
2
|
+
import { computed, defineAsyncComponent, defineComponent, h, ref, toRef } from 'vue'
|
|
3
3
|
import type { CSSProperties, PropType } from 'vue'
|
|
4
4
|
import { provideLocal } from '@vueuse/core'
|
|
5
5
|
import type { ClicksContext, RenderContext, SlideRoute } from '@slidev/types'
|
|
@@ -51,10 +51,9 @@ const SlideComponent = computed(() => props.route && defineAsyncComponent({
|
|
|
51
51
|
loader: async () => {
|
|
52
52
|
const component = await props.route.component()
|
|
53
53
|
return defineComponent({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
},
|
|
54
|
+
mounted: props.clicksContext?.onMounted,
|
|
55
|
+
unmounted: props.clicksContext?.onUnmounted,
|
|
56
|
+
render: () => h(component.default),
|
|
58
57
|
})
|
|
59
58
|
},
|
|
60
59
|
delay: 300,
|
package/logic/slides.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { SlideRoute } from '@slidev/types'
|
|
2
|
+
import { computed, watch, watchEffect } from 'vue'
|
|
3
|
+
import { useSlideContext } from '../context'
|
|
4
|
+
import { useNav } from '../composables/useNav'
|
|
2
5
|
import { slides } from '#slidev/slides'
|
|
3
6
|
|
|
4
7
|
export { slides }
|
|
@@ -18,3 +21,19 @@ export function getSlidePath(
|
|
|
18
21
|
const no = route.meta.slide?.frontmatter.routeAlias ?? route.no
|
|
19
22
|
return presenter ? `/presenter/${no}` : `/${no}`
|
|
20
23
|
}
|
|
24
|
+
|
|
25
|
+
export function useIsSlideActive() {
|
|
26
|
+
const { $page } = useSlideContext()
|
|
27
|
+
const { currentSlideNo } = useNav()
|
|
28
|
+
return computed(() => $page.value === currentSlideNo.value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function onSlideEnter(cb: () => void) {
|
|
32
|
+
const isActive = useIsSlideActive()
|
|
33
|
+
watchEffect(() => isActive.value && cb())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function onSlideLeave(cb: () => void) {
|
|
37
|
+
const isActive = useIsSlideActive()
|
|
38
|
+
watch(isActive, () => !isActive.value && cb())
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slidev/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.49.
|
|
4
|
+
"version": "0.49.12",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,19 +29,18 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@antfu/utils": "^0.7.8",
|
|
32
|
-
"@iconify-json/carbon": "^1.1.
|
|
32
|
+
"@iconify-json/carbon": "^1.1.36",
|
|
33
33
|
"@iconify-json/ph": "^1.1.13",
|
|
34
34
|
"@iconify-json/svg-spinners": "^1.1.2",
|
|
35
|
-
"@shikijs/monaco": "^1.
|
|
36
|
-
"@shikijs/vitepress-twoslash": "^1.
|
|
35
|
+
"@shikijs/monaco": "^1.9.1",
|
|
36
|
+
"@shikijs/vitepress-twoslash": "^1.9.1",
|
|
37
37
|
"@slidev/rough-notation": "^0.1.0",
|
|
38
|
-
"@typescript/ata": "^0.9.
|
|
39
|
-
"@unhead/vue": "^1.9.
|
|
40
|
-
"@unocss/reset": "^0.
|
|
38
|
+
"@typescript/ata": "^0.9.6",
|
|
39
|
+
"@unhead/vue": "^1.9.14",
|
|
40
|
+
"@unocss/reset": "^0.61.0",
|
|
41
41
|
"@vueuse/core": "^10.10.0",
|
|
42
42
|
"@vueuse/math": "^10.10.0",
|
|
43
43
|
"@vueuse/motion": "^2.2.3",
|
|
44
|
-
"codemirror": "^5.65.16",
|
|
45
44
|
"drauu": "^0.4.0",
|
|
46
45
|
"file-saver": "^2.0.5",
|
|
47
46
|
"floating-vue": "^5.2.2",
|
|
@@ -52,17 +51,17 @@
|
|
|
52
51
|
"monaco-editor": "^0.49.0",
|
|
53
52
|
"prettier": "^3.3.0",
|
|
54
53
|
"recordrtc": "^5.6.2",
|
|
55
|
-
"shiki": "^1.
|
|
54
|
+
"shiki": "^1.9.1",
|
|
56
55
|
"shiki-magic-move": "^0.4.2",
|
|
57
|
-
"typescript": "^5.
|
|
58
|
-
"unocss": "^0.
|
|
59
|
-
"vue": "^3.4.
|
|
60
|
-
"vue-router": "^4.
|
|
56
|
+
"typescript": "^5.5.2",
|
|
57
|
+
"unocss": "^0.61.0",
|
|
58
|
+
"vue": "^3.4.30",
|
|
59
|
+
"vue-router": "^4.4.0",
|
|
61
60
|
"yaml": "^2.4.2",
|
|
62
|
-
"@slidev/
|
|
63
|
-
"@slidev/
|
|
61
|
+
"@slidev/types": "0.49.12",
|
|
62
|
+
"@slidev/parser": "0.49.12"
|
|
64
63
|
},
|
|
65
64
|
"devDependencies": {
|
|
66
|
-
"vite": "^5.
|
|
65
|
+
"vite": "^5.3.1"
|
|
67
66
|
}
|
|
68
67
|
}
|
package/pages/404.vue
CHANGED
|
@@ -19,22 +19,22 @@ const guessedSlide = computed(() => {
|
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
|
-
<div class="grid justify-center pt-15%">
|
|
22
|
+
<div class="grid justify-center text-center pt-15% gap-5">
|
|
23
23
|
<div>
|
|
24
|
-
<h1 class="text-9xl font-
|
|
24
|
+
<h1 class="text-9xl font-light">
|
|
25
25
|
404
|
|
26
26
|
</h1>
|
|
27
27
|
<p class="text-2xl">
|
|
28
|
-
Page
|
|
28
|
+
Page <code class="op-60">{{ currentRoute.path }}</code> not found
|
|
29
29
|
</p>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</
|
|
30
|
+
</div>
|
|
31
|
+
<div class="mt-3 flex flex-col gap-2 max-w-xs mx-auto w-full">
|
|
32
|
+
<RouterLink v-if="guessedSlide !== 1" to="/" class="page-link">
|
|
33
|
+
Go Home
|
|
34
|
+
</RouterLink>
|
|
35
|
+
<RouterLink v-if="guessedSlide" :to="`/${guessedSlide}`" class="page-link">
|
|
36
|
+
Go to Slide {{ guessedSlide }}
|
|
37
|
+
</RouterLink>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
40
40
|
</template>
|
package/pages/overview.vue
CHANGED
|
@@ -165,7 +165,7 @@ onMounted(() => {
|
|
|
165
165
|
<carbon:presentation-file />
|
|
166
166
|
</IconButton>
|
|
167
167
|
<IconButton
|
|
168
|
-
v-if="route.meta?.slide"
|
|
168
|
+
v-if="__DEV__ && route.meta?.slide"
|
|
169
169
|
class="mr--3 op0 group-hover:op80"
|
|
170
170
|
title="Open in editor"
|
|
171
171
|
@click="openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
|
package/pages/presenter.vue
CHANGED
|
@@ -40,7 +40,6 @@ const {
|
|
|
40
40
|
hasNext,
|
|
41
41
|
nextRoute,
|
|
42
42
|
slides,
|
|
43
|
-
queryClicks,
|
|
44
43
|
getPrimaryClicks,
|
|
45
44
|
total,
|
|
46
45
|
} = useNav()
|
|
@@ -67,10 +66,10 @@ const nextFrameClicksCtx = computed(() => {
|
|
|
67
66
|
})
|
|
68
67
|
|
|
69
68
|
watch(
|
|
70
|
-
|
|
69
|
+
nextFrame,
|
|
71
70
|
() => {
|
|
72
|
-
if (nextFrameClicksCtx.value)
|
|
73
|
-
nextFrameClicksCtx.value.current = nextFrame.value
|
|
71
|
+
if (nextFrameClicksCtx.value && nextFrame.value)
|
|
72
|
+
nextFrameClicksCtx.value.current = nextFrame.value[1]
|
|
74
73
|
},
|
|
75
74
|
{ immediate: true },
|
|
76
75
|
)
|
package/setup/code-runners.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createSingletonPromise } from '@antfu/utils'
|
|
2
2
|
import type { CodeRunner, CodeRunnerOutput, CodeRunnerOutputText, CodeRunnerOutputs } from '@slidev/types'
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import type ts from 'typescript'
|
|
5
|
-
import {
|
|
5
|
+
import { ref } from 'vue'
|
|
6
6
|
import deps from '#slidev/monaco-run-deps'
|
|
7
7
|
import setups from '#slidev/setups/code-runners'
|
|
8
8
|
|
|
@@ -14,17 +14,8 @@ export default createSingletonPromise(async () => {
|
|
|
14
14
|
ts: runTypeScript,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const {
|
|
18
|
-
const
|
|
19
|
-
const highlight = (code: string, lang: string, options: Partial<CodeToHastOptions> = {}) => highlighter.codeToHtml(code, {
|
|
20
|
-
lang,
|
|
21
|
-
theme: typeof themes === 'string'
|
|
22
|
-
? themes
|
|
23
|
-
: isDark.value
|
|
24
|
-
? themes.dark || 'vitesse-dark'
|
|
25
|
-
: themes.light || 'vitesse-light',
|
|
26
|
-
...options,
|
|
27
|
-
})
|
|
17
|
+
const { getHighlighter } = await import('#slidev/shiki')
|
|
18
|
+
const highlight = await getHighlighter()
|
|
28
19
|
|
|
29
20
|
const run = async (code: string, lang: string, options: Record<string, unknown>): Promise<CodeRunnerOutputs> => {
|
|
30
21
|
try {
|
|
@@ -62,32 +53,32 @@ export default createSingletonPromise(async () => {
|
|
|
62
53
|
})
|
|
63
54
|
|
|
64
55
|
// Ported from https://github.com/microsoft/TypeScript-Website/blob/v2/packages/playground/src/sidebar/runtime.ts
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const logger =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
replace.clear = () => allLogs.length = 0
|
|
74
|
-
const vmConsole = Object.assign({}, console, replace)
|
|
56
|
+
function runJavaScript(code: string): CodeRunnerOutputs {
|
|
57
|
+
const result = ref<CodeRunnerOutput[]>([])
|
|
58
|
+
|
|
59
|
+
const onError = (error: any) => result.value.push({ error: String(error) })
|
|
60
|
+
const logger = (...objs: any[]) => result.value.push(objs.map(printObject))
|
|
61
|
+
const vmConsole = Object.assign({}, console)
|
|
62
|
+
vmConsole.info = vmConsole.log = vmConsole.debug = vmConsole.warn = vmConsole.error = logger
|
|
63
|
+
vmConsole.clear = () => result.value.length = 0
|
|
75
64
|
try {
|
|
76
|
-
const safeJS = `return async (console, __slidev_import) => {
|
|
77
|
-
|
|
65
|
+
const safeJS = `return async (console, __slidev_import, __slidev_on_error) => {
|
|
66
|
+
try {
|
|
67
|
+
${sanitizeJS(code)}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
__slidev_on_error(e)
|
|
70
|
+
}
|
|
78
71
|
}`
|
|
79
72
|
// eslint-disable-next-line no-new-func
|
|
80
|
-
|
|
73
|
+
;(new Function(safeJS)())(vmConsole, (specifier: string) => {
|
|
81
74
|
const mod = deps[specifier]
|
|
82
75
|
if (!mod)
|
|
83
76
|
throw new Error(`Module not found: ${specifier}.\nAvailable modules: ${Object.keys(deps).join(', ')}. Please refer to https://sli.dev/custom/config-code-runners#additional-runner-dependencies`)
|
|
84
77
|
return mod
|
|
85
|
-
})
|
|
78
|
+
}, onError)
|
|
86
79
|
}
|
|
87
80
|
catch (error) {
|
|
88
|
-
|
|
89
|
-
error: String(error),
|
|
90
|
-
}
|
|
81
|
+
onError(error)
|
|
91
82
|
}
|
|
92
83
|
|
|
93
84
|
function printObject(arg: any): CodeRunnerOutputText {
|
|
@@ -162,7 +153,7 @@ async function runJavaScript(code: string): Promise<CodeRunnerOutputs> {
|
|
|
162
153
|
return code
|
|
163
154
|
}
|
|
164
155
|
|
|
165
|
-
return
|
|
156
|
+
return result
|
|
166
157
|
}
|
|
167
158
|
|
|
168
159
|
let tsModule: typeof import('typescript') | undefined
|
|
@@ -183,7 +174,7 @@ export async function runTypeScript(code: string) {
|
|
|
183
174
|
const importRegex = /import\s*\((.+)\)/g
|
|
184
175
|
code = code.replace(importRegex, (_full, specifier) => `__slidev_import(${specifier})`)
|
|
185
176
|
|
|
186
|
-
return
|
|
177
|
+
return runJavaScript(code)
|
|
187
178
|
}
|
|
188
179
|
|
|
189
180
|
/**
|
package/state/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ export const isScreenVertical = computed(() => windowSize.height.value - windowS
|
|
|
19
19
|
export const fullscreen = useFullscreen(isClient ? document.body : null)
|
|
20
20
|
|
|
21
21
|
export const activeElement = useActiveElement()
|
|
22
|
-
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || '')
|
|
22
|
+
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || ''))
|
|
23
23
|
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))
|
|
24
24
|
|
|
25
25
|
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default', { listenToStorageChanges: false })
|
package/styles/code.css
CHANGED
|
@@ -5,6 +5,7 @@ html.dark:root {
|
|
|
5
5
|
/* Shiki */
|
|
6
6
|
html.dark .shiki {
|
|
7
7
|
color: var(--shiki-dark, inherit);
|
|
8
|
+
background: var(--shiki-dark-bg, inherit);
|
|
8
9
|
--twoslash-popup-bg: var(--shiki-dark-bg, inherit);
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -82,8 +83,3 @@ html:not(.dark) .shiki span {
|
|
|
82
83
|
.katex :before {
|
|
83
84
|
border-color: currentColor;
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
-
/* CodeMirror */
|
|
87
|
-
.CodeMirror pre.CodeMirror-placeholder {
|
|
88
|
-
opacity: 0.4;
|
|
89
|
-
}
|
package/uno.config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
2
3
|
import {
|
|
3
4
|
defineConfig,
|
|
4
5
|
presetAttributify,
|
|
@@ -42,6 +43,14 @@ export default defineConfig({
|
|
|
42
43
|
presetAttributify(),
|
|
43
44
|
presetIcons({
|
|
44
45
|
collectionsNodeResolvePath: fileURLToPath(import.meta.url),
|
|
46
|
+
collections: {
|
|
47
|
+
slidev: {
|
|
48
|
+
logo: async () => {
|
|
49
|
+
const content = readFileSync(fileURLToPath(new URL('assets/logo.svg', import.meta.url)), 'utf-8')
|
|
50
|
+
return content
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
45
54
|
}),
|
|
46
55
|
presetTypography(),
|
|
47
56
|
],
|
package/modules/codemirror.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { Ref, WritableComputedRef } from 'vue'
|
|
2
|
-
import { onClickOutside } from '@vueuse/core'
|
|
3
|
-
import { watch } from 'vue'
|
|
4
|
-
import CodeMirror from 'codemirror'
|
|
5
|
-
import 'codemirror/mode/javascript/javascript'
|
|
6
|
-
import 'codemirror/mode/css/css'
|
|
7
|
-
import 'codemirror/mode/markdown/markdown'
|
|
8
|
-
import 'codemirror/mode/xml/xml'
|
|
9
|
-
import 'codemirror/mode/htmlmixed/htmlmixed'
|
|
10
|
-
import 'codemirror/addon/display/placeholder'
|
|
11
|
-
import 'codemirror/lib/codemirror.css'
|
|
12
|
-
|
|
13
|
-
export async function useCodeMirror(
|
|
14
|
-
textarea: Ref<HTMLTextAreaElement | null | undefined>,
|
|
15
|
-
input: Ref<string> | WritableComputedRef<string>,
|
|
16
|
-
options: CodeMirror.EditorConfiguration = {},
|
|
17
|
-
) {
|
|
18
|
-
const cm = CodeMirror.fromTextArea(
|
|
19
|
-
textarea.value!,
|
|
20
|
-
{
|
|
21
|
-
theme: 'vars',
|
|
22
|
-
...options,
|
|
23
|
-
},
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
let skip = false
|
|
27
|
-
|
|
28
|
-
cm.on('change', () => {
|
|
29
|
-
if (skip) {
|
|
30
|
-
skip = false
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
input.value = cm.getValue()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
watch(
|
|
37
|
-
input,
|
|
38
|
-
(v) => {
|
|
39
|
-
if (v !== cm.getValue()) {
|
|
40
|
-
skip = true
|
|
41
|
-
const selections = cm.listSelections()
|
|
42
|
-
cm.replaceRange(v, cm.posFromIndex(0), cm.posFromIndex(Number.POSITIVE_INFINITY))
|
|
43
|
-
cm.setSelections(selections)
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
{ immediate: true },
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
onClickOutside(cm.getWrapperElement(), () => {
|
|
50
|
-
const el = cm.getInputField()
|
|
51
|
-
if (document.activeElement === el)
|
|
52
|
-
el.blur()
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
return cm
|
|
56
|
-
}
|