@slidev/client 0.48.0-beta.21 → 0.48.0-beta.22
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/CodeBlockWrapper.vue +23 -25
- package/builtin/ShikiMagicMove.vue +63 -13
- package/context.ts +2 -5
- package/env.ts +4 -4
- package/internals/PrintContainer.vue +4 -4
- package/internals/PrintSlideClick.vue +2 -2
- package/internals/SlideContainer.vue +6 -6
- package/logic/nav.ts +1 -1
- package/logic/utils.ts +24 -0
- package/modules/context.ts +4 -2
- package/package.json +6 -6
- package/state/index.ts +1 -1
- package/styles/code.css +2 -2
|
@@ -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
|
|
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({
|
|
@@ -87,28 +86,27 @@ onMounted(() => {
|
|
|
87
86
|
if (hide)
|
|
88
87
|
rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
|
|
89
88
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
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' })
|
|
112
110
|
}
|
|
113
111
|
}
|
|
114
112
|
})
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ShikiMagicMovePrecompiled } from 'shiki-magic-move/vue'
|
|
3
3
|
import type { KeyedTokensInfo } from 'shiki-magic-move/types'
|
|
4
|
-
import { onMounted, onUnmounted, ref,
|
|
4
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
5
5
|
import lz from 'lz-string'
|
|
6
6
|
import { useSlideContext } from '../context'
|
|
7
|
-
import { makeId } from '../logic/utils'
|
|
7
|
+
import { makeId, updateCodeHighlightRange } from '../logic/utils'
|
|
8
8
|
|
|
9
9
|
import 'shiki-magic-move/style.css'
|
|
10
10
|
|
|
11
11
|
const props = defineProps<{
|
|
12
|
-
stepsLz: string
|
|
13
12
|
at?: string | number
|
|
13
|
+
stepsLz: string
|
|
14
|
+
stepRanges: string[][]
|
|
14
15
|
}>()
|
|
15
16
|
|
|
16
17
|
const steps = JSON.parse(lz.decompressFromBase64(props.stepsLz)) as KeyedTokensInfo[]
|
|
17
18
|
const { $clicksContext: clicks, $scale: scale } = useSlideContext()
|
|
18
19
|
const id = makeId()
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
const stepIndex = ref(0)
|
|
22
|
+
const container = ref<HTMLElement>()
|
|
23
|
+
|
|
24
|
+
// Normalized the ranges, to at least have one range
|
|
25
|
+
const ranges = computed(() => props.stepRanges.map(i => i.length ? i : ['all']))
|
|
20
26
|
|
|
21
27
|
onUnmounted(() => {
|
|
22
28
|
clicks!.unregister(id)
|
|
@@ -26,24 +32,68 @@ onMounted(() => {
|
|
|
26
32
|
if (!clicks || clicks.disabled)
|
|
27
33
|
return
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
if (ranges.value.length !== steps.length)
|
|
36
|
+
throw new Error('[slidev] The length of stepRanges does not match the length of steps, this is an internal error.')
|
|
37
|
+
|
|
38
|
+
const clickCounts = ranges.value.map(s => s.length).reduce((a, b) => a + b, 0)
|
|
39
|
+
const { start, end, delta } = clicks.resolve(props.at ?? '+1', clickCounts - 1)
|
|
30
40
|
clicks.register(id, { max: end, delta })
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
watch(
|
|
43
|
+
() => clicks.current,
|
|
44
|
+
() => {
|
|
45
|
+
// Calculate the step and rangeStr based on the current click count
|
|
46
|
+
const clickCount = clicks.current - start
|
|
47
|
+
let step = steps.length - 1
|
|
48
|
+
let _currentClickSum = 0
|
|
49
|
+
let rangeStr = 'all'
|
|
50
|
+
for (let i = 0; i < ranges.value.length; i++) {
|
|
51
|
+
const current = ranges.value[i]
|
|
52
|
+
if (clickCount < _currentClickSum + current.length - 1) {
|
|
53
|
+
step = i
|
|
54
|
+
rangeStr = current[clickCount - _currentClickSum + 1]
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
_currentClickSum += current.length || 1
|
|
58
|
+
}
|
|
59
|
+
stepIndex.value = step
|
|
60
|
+
|
|
61
|
+
const pre = container.value?.querySelector('.shiki') as HTMLElement
|
|
62
|
+
if (!pre)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
const children = (Array.from(pre.children) as HTMLElement[])
|
|
66
|
+
.slice(1) // Remove the first anchor
|
|
67
|
+
.filter(i => !i.className.includes('shiki-magic-move-leave')) // Filter the leaving elements
|
|
68
|
+
|
|
69
|
+
// Group to lines between `<br>`
|
|
70
|
+
const lines = children.reduce((acc, el) => {
|
|
71
|
+
if (el.tagName === 'BR')
|
|
72
|
+
acc.push([])
|
|
73
|
+
else
|
|
74
|
+
acc[acc.length - 1].push(el)
|
|
75
|
+
return acc
|
|
76
|
+
}, [[]] as HTMLElement[][])
|
|
77
|
+
|
|
78
|
+
// Update highlight range
|
|
79
|
+
updateCodeHighlightRange(
|
|
80
|
+
rangeStr,
|
|
81
|
+
lines.length,
|
|
82
|
+
1,
|
|
83
|
+
no => lines[no],
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
{ immediate: true },
|
|
87
|
+
)
|
|
38
88
|
})
|
|
39
89
|
</script>
|
|
40
90
|
|
|
41
91
|
<template>
|
|
42
|
-
<div class="slidev-code-wrapper slidev-code-magic-move">
|
|
92
|
+
<div ref="container" class="slidev-code-wrapper slidev-code-magic-move relative">
|
|
43
93
|
<ShikiMagicMovePrecompiled
|
|
44
94
|
class="slidev-code relative shiki overflow-visible"
|
|
45
95
|
:steps="steps"
|
|
46
|
-
:step="
|
|
96
|
+
:step="stepIndex"
|
|
47
97
|
:options="{ globalScale: scale }"
|
|
48
98
|
/>
|
|
49
99
|
</div>
|
package/context.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ref,
|
|
1
|
+
import { ref, toRef } from 'vue'
|
|
2
2
|
import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
|
|
3
|
-
import { useFixedClicks } from './composables/useClicks'
|
|
4
3
|
import {
|
|
5
4
|
FRONTMATTER_FIELDS,
|
|
6
5
|
HEADMATTER_FIELDS,
|
|
@@ -13,15 +12,13 @@ import {
|
|
|
13
12
|
injectionSlidevContext,
|
|
14
13
|
} from './constants'
|
|
15
14
|
|
|
16
|
-
const clicksContextFallback = shallowRef(useFixedClicks())
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
16
|
* Get the current slide context, should be called inside the setup function of a component inside slide
|
|
20
17
|
*/
|
|
21
18
|
export function useSlideContext() {
|
|
22
19
|
const $slidev = injectLocal(injectionSlidevContext)!
|
|
23
20
|
const $nav = toRef($slidev, 'nav')
|
|
24
|
-
const $clicksContext = injectLocal(injectionClicksContext
|
|
21
|
+
const $clicksContext = injectLocal(injectionClicksContext)!.value
|
|
25
22
|
const $clicks = toRef($clicksContext, 'current')
|
|
26
23
|
const $page = injectLocal(injectionCurrentPage)!
|
|
27
24
|
const $renderContext = injectLocal(injectionRenderContext)!
|
package/env.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { computed } from 'vue'
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
2
|
import { objectMap } from '@antfu/utils'
|
|
3
3
|
import configs from '#slidev/configs'
|
|
4
4
|
|
|
5
5
|
export { configs }
|
|
6
6
|
|
|
7
|
-
export const slideAspect = configs.aspectRatio ?? (16 / 9)
|
|
8
|
-
export const slideWidth = configs.canvasWidth ?? 980
|
|
7
|
+
export const slideAspect = ref(configs.aspectRatio ?? (16 / 9))
|
|
8
|
+
export const slideWidth = ref(configs.canvasWidth ?? 980)
|
|
9
9
|
|
|
10
10
|
// To honor the aspect ratio more as possible, we need to approximate the height to the next integer.
|
|
11
11
|
// Doing this, we will prevent on print, to create an additional empty white page after each page.
|
|
12
|
-
export const slideHeight = Math.ceil(slideWidth / slideAspect)
|
|
12
|
+
export const slideHeight = computed(() => Math.ceil(slideWidth.value / slideAspect.value))
|
|
13
13
|
|
|
14
14
|
export const themeVars = computed(() => {
|
|
15
15
|
return objectMap(configs.themeConfig || {}, (k, v) => [`--slidev-theme-${k}`, v])
|
|
@@ -12,14 +12,14 @@ const props = defineProps<{
|
|
|
12
12
|
}>()
|
|
13
13
|
|
|
14
14
|
const width = computed(() => props.width)
|
|
15
|
-
const height = computed(() => props.width / slideAspect)
|
|
15
|
+
const height = computed(() => props.width / slideAspect.value)
|
|
16
16
|
|
|
17
17
|
const screenAspect = computed(() => width.value / height.value)
|
|
18
18
|
|
|
19
19
|
const scale = computed(() => {
|
|
20
|
-
if (screenAspect.value < slideAspect)
|
|
21
|
-
return width.value / slideWidth
|
|
22
|
-
return (height.value * slideAspect) / slideWidth
|
|
20
|
+
if (screenAspect.value < slideAspect.value)
|
|
21
|
+
return width.value / slideWidth.value
|
|
22
|
+
return (height.value * slideAspect.value) / slideWidth.value
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
// In print mode, the routes will never change. So we don't need reactivity here.
|
|
@@ -17,8 +17,8 @@ const { nav } = defineProps<{
|
|
|
17
17
|
const route = computed(() => nav.currentSlideRoute.value)
|
|
18
18
|
|
|
19
19
|
const style = computed(() => ({
|
|
20
|
-
height: `${slideHeight}px`,
|
|
21
|
-
width: `${slideWidth}px`,
|
|
20
|
+
height: `${slideHeight.value}px`,
|
|
21
|
+
width: `${slideWidth.value}px`,
|
|
22
22
|
}))
|
|
23
23
|
|
|
24
24
|
const DrawingPreview = shallowRef<any>()
|
|
@@ -25,7 +25,7 @@ const root = ref<HTMLDivElement>()
|
|
|
25
25
|
const element = useElementSize(root)
|
|
26
26
|
|
|
27
27
|
const width = computed(() => props.width ? props.width : element.width.value)
|
|
28
|
-
const height = computed(() => props.width ? props.width / slideAspect : element.height.value)
|
|
28
|
+
const height = computed(() => props.width ? props.width / slideAspect.value : element.height.value)
|
|
29
29
|
|
|
30
30
|
if (props.width) {
|
|
31
31
|
watchEffect(() => {
|
|
@@ -41,14 +41,14 @@ const screenAspect = computed(() => width.value / height.value)
|
|
|
41
41
|
const scale = computed(() => {
|
|
42
42
|
if (props.scale && !isPrintMode.value)
|
|
43
43
|
return props.scale
|
|
44
|
-
if (screenAspect.value < slideAspect)
|
|
45
|
-
return width.value / slideWidth
|
|
46
|
-
return height.value * slideAspect / slideWidth
|
|
44
|
+
if (screenAspect.value < slideAspect.value)
|
|
45
|
+
return width.value / slideWidth.value
|
|
46
|
+
return height.value * slideAspect.value / slideWidth.value
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
const style = computed(() => ({
|
|
50
|
-
'height': `${slideHeight}px`,
|
|
51
|
-
'width': `${slideWidth}px`,
|
|
50
|
+
'height': `${slideHeight.value}px`,
|
|
51
|
+
'width': `${slideWidth.value}px`,
|
|
52
52
|
'transform': `translate(-50%, -50%) scale(${scale.value})`,
|
|
53
53
|
'--slidev-slide-scale': scale.value,
|
|
54
54
|
}))
|
package/logic/nav.ts
CHANGED
package/logic/utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { parseRangeString } from '@slidev/parser/core'
|
|
1
2
|
import { useTimestamp } from '@vueuse/core'
|
|
2
3
|
import { computed, ref } from 'vue'
|
|
4
|
+
import { CLASS_VCLICK_TARGET } from '../constants'
|
|
3
5
|
|
|
4
6
|
export function useTimer() {
|
|
5
7
|
const tsStart = ref(Date.now())
|
|
@@ -48,3 +50,25 @@ export function normalizeAtProp(at: string | number = '+1'): [isRelative: boolea
|
|
|
48
50
|
n,
|
|
49
51
|
]
|
|
50
52
|
}
|
|
53
|
+
|
|
54
|
+
export function updateCodeHighlightRange(
|
|
55
|
+
rangeStr: string,
|
|
56
|
+
linesCount: number,
|
|
57
|
+
startLine: number,
|
|
58
|
+
getTokenOfLine: (line: number) => Element[],
|
|
59
|
+
) {
|
|
60
|
+
const highlights: number[] = parseRangeString(linesCount + startLine - 1, rangeStr)
|
|
61
|
+
for (let line = 0; line < linesCount; line++) {
|
|
62
|
+
const tokens = getTokenOfLine(line)
|
|
63
|
+
const isHighlighted = highlights.includes(line + startLine)
|
|
64
|
+
for (const token of tokens) {
|
|
65
|
+
token.classList.toggle(CLASS_VCLICK_TARGET, true)
|
|
66
|
+
token.classList.toggle('slidev-code-highlighted', isHighlighted)
|
|
67
|
+
token.classList.toggle('slidev-code-dishonored', !isHighlighted)
|
|
68
|
+
|
|
69
|
+
// for backward compatibility
|
|
70
|
+
token.classList.toggle('highlighted', isHighlighted)
|
|
71
|
+
token.classList.toggle('dishonored', !isHighlighted)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
package/modules/context.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { App } from 'vue'
|
|
2
|
-
import { computed, reactive, ref } from 'vue'
|
|
2
|
+
import { computed, reactive, ref, shallowRef } from 'vue'
|
|
3
3
|
import type { ComputedRef } from '@vue/reactivity'
|
|
4
4
|
import type { configs } from '../env'
|
|
5
5
|
import * as nav from '../logic/nav'
|
|
6
6
|
import { isDark } from '../logic/dark'
|
|
7
|
-
import { injectionCurrentPage, injectionRenderContext, injectionSlidevContext } from '../constants'
|
|
7
|
+
import { injectionClicksContext, injectionCurrentPage, injectionRenderContext, injectionSlidevContext } from '../constants'
|
|
8
8
|
import { useContext } from '../composables/useContext'
|
|
9
9
|
import type { SlidevContextNav } from '../composables/useNav'
|
|
10
|
+
import { useFixedClicks } from '../composables/useClicks'
|
|
10
11
|
|
|
11
12
|
export interface SlidevContext {
|
|
12
13
|
nav: SlidevContextNav
|
|
@@ -21,6 +22,7 @@ export function createSlidevContext() {
|
|
|
21
22
|
app.provide(injectionRenderContext, ref('none'))
|
|
22
23
|
app.provide(injectionSlidevContext, context)
|
|
23
24
|
app.provide(injectionCurrentPage, computed(() => context.nav.currentSlideNo))
|
|
25
|
+
app.provide(injectionClicksContext, shallowRef(useFixedClicks()))
|
|
24
26
|
|
|
25
27
|
// allows controls from postMessages
|
|
26
28
|
if (__DEV__) {
|
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.22",
|
|
5
5
|
"description": "Presentation slides for developers",
|
|
6
6
|
"author": "antfu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@antfu/utils": "^0.7.7",
|
|
30
|
-
"@iconify-json/carbon": "^1.1.
|
|
30
|
+
"@iconify-json/carbon": "^1.1.31",
|
|
31
31
|
"@iconify-json/ph": "^1.1.11",
|
|
32
32
|
"@iconify-json/svg-spinners": "^1.1.2",
|
|
33
33
|
"@shikijs/monaco": "^1.1.7",
|
|
34
34
|
"@shikijs/vitepress-twoslash": "^1.1.7",
|
|
35
35
|
"@slidev/rough-notation": "^0.1.0",
|
|
36
36
|
"@typescript/ata": "^0.9.4",
|
|
37
|
-
"@unhead/vue": "^1.8.
|
|
37
|
+
"@unhead/vue": "^1.8.11",
|
|
38
38
|
"@unocss/reset": "^0.58.5",
|
|
39
39
|
"@vueuse/core": "^10.9.0",
|
|
40
40
|
"@vueuse/math": "^10.9.0",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
"prettier": "^3.2.5",
|
|
53
53
|
"recordrtc": "^5.6.2",
|
|
54
54
|
"shiki": "^1.1.7",
|
|
55
|
-
"shiki-magic-move": "^0.
|
|
55
|
+
"shiki-magic-move": "^0.3.0",
|
|
56
56
|
"typescript": "^5.3.3",
|
|
57
57
|
"unocss": "^0.58.5",
|
|
58
58
|
"vue": "^3.4.21",
|
|
59
59
|
"vue-router": "^4.3.0",
|
|
60
|
-
"@slidev/
|
|
61
|
-
"@slidev/
|
|
60
|
+
"@slidev/parser": "0.48.0-beta.22",
|
|
61
|
+
"@slidev/types": "0.48.0-beta.22"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"vite": "^5.1.4"
|
package/state/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const breakpoints = useBreakpoints({
|
|
|
14
14
|
})
|
|
15
15
|
export const windowSize = useWindowSize()
|
|
16
16
|
export const magicKeys = useMagicKeys()
|
|
17
|
-
export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect > 120)
|
|
17
|
+
export const isScreenVertical = computed(() => windowSize.height.value - windowSize.width.value / slideAspect.value > 120)
|
|
18
18
|
export const fullscreen = useFullscreen(isClient ? document.body : null)
|
|
19
19
|
|
|
20
20
|
export const activeElement = useActiveElement()
|
package/styles/code.css
CHANGED
|
@@ -44,9 +44,9 @@ html:not(.dark) .shiki span {
|
|
|
44
44
|
overflow: auto;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
.slidev-code .
|
|
47
|
+
.slidev-code .slidev-code-highlighted {
|
|
48
48
|
}
|
|
49
|
-
.slidev-code .
|
|
49
|
+
.slidev-code .slidev-code-dishonored {
|
|
50
50
|
opacity: 0.3;
|
|
51
51
|
pointer-events: none;
|
|
52
52
|
}
|