+
diff --git a/packages/client/logic/utils.ts b/packages/client/logic/utils.ts
index 66627dbc95..bba23419d9 100644
--- a/packages/client/logic/utils.ts
+++ b/packages/client/logic/utils.ts
@@ -1,5 +1,7 @@
+import { parseRangeString } from '@slidev/parser/core'
import { useTimestamp } from '@vueuse/core'
import { computed, ref } from 'vue'
+import { CLASS_VCLICK_TARGET } from '../constants'
export function useTimer() {
const tsStart = ref(Date.now())
@@ -48,3 +50,25 @@ export function normalizeAtProp(at: string | number = '+1'): [isRelative: boolea
n,
]
}
+
+export function updateCodeHighlightRange(
+ rangeStr: string,
+ linesCount: number,
+ startLine: number,
+ getTokenOfLine: (line: number) => Element[],
+) {
+ const highlights: number[] = parseRangeString(linesCount + startLine - 1, rangeStr)
+ for (let line = 0; line < linesCount; line++) {
+ const tokens = getTokenOfLine(line)
+ const isHighlighted = highlights.includes(line + startLine)
+ for (const token of tokens) {
+ token.classList.toggle(CLASS_VCLICK_TARGET, true)
+ token.classList.toggle('slidev-code-highlighted', isHighlighted)
+ token.classList.toggle('slidev-code-dishonored', !isHighlighted)
+
+ // for backward compatibility
+ token.classList.toggle('highlighted', isHighlighted)
+ token.classList.toggle('dishonored', !isHighlighted)
+ }
+ }
+}
diff --git a/packages/client/styles/code.css b/packages/client/styles/code.css
index f256ef37b0..e0e9ae5aa1 100644
--- a/packages/client/styles/code.css
+++ b/packages/client/styles/code.css
@@ -44,9 +44,9 @@ html:not(.dark) .shiki span {
overflow: auto;
}
-.slidev-code .line.highlighted {
+.slidev-code .slidev-code-highlighted {
}
-.slidev-code .line.dishonored {
+.slidev-code .slidev-code-dishonored {
opacity: 0.3;
pointer-events: none;
}
diff --git a/packages/slidev/node/plugins/markdown.ts b/packages/slidev/node/plugins/markdown.ts
index f3e391b006..4b3ca34209 100644
--- a/packages/slidev/node/plugins/markdown.ts
+++ b/packages/slidev/node/plugins/markdown.ts
@@ -260,6 +260,7 @@ export function transformMagicMove(
if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')
+ const ranges = matches.map(i => normalizeRangeStr(i[2]))
const steps = matches.map(i =>
codeToKeyedTokens(shiki, i[5].trimEnd(), {
...shikiOptions,
@@ -267,7 +268,7 @@ export function transformMagicMove(
}),
)
const compressed = lz.compressToBase64(JSON.stringify(steps))
- return `
`
+ return `
`
},
)
}
@@ -279,7 +280,7 @@ export function transformHighlighter(md: string) {
return md.replace(
reCodeBlock,
(full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string) => {
- const ranges = !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
+ const ranges = normalizeRangeStr(rangeStr)
code = code.trimEnd()
options = options.trim() || '{}'
return `\n
\n\n\`\`\`${lang}${attrs}\n${code}\n\`\`\`\n\n`
@@ -287,6 +288,10 @@ export function transformHighlighter(md: string) {
)
}
+function normalizeRangeStr(rangeStr = '') {
+ return !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
+}
+
export function getCodeBlocks(md: string) {
const codeblocks = Array
.from(md.matchAll(/^```[\s\S]*?^```/mg))