返回 slidev
KaTexBlockWrapper.vue
根目录 / packages / client / builtin / KaTexBlockWrapper.vue
1 <!--
2 Line highlighting for KaTex blocks/
3 (auto transformed, you don't need to use this component directly)
4
5 Usage:
6 $$ {1|3|all}
7 \begin{array}{c}
8
9 \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} &
10 = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
11
12 \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
13
14 \nabla \cdot \vec{\mathbf{B}} & = 0
15
16 \end{array}
17 $$
18
19 Learn more: https://sli.dev/guide/syntax.html#latex-line-highlighting
20 -->
21
22 <script setup lang="ts">
23 import type { PropType } from 'vue'
24 import { parseRangeString } from '@slidev/parser/utils'
25 import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
26 import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, CLICKS_MAX } from '../constants'
27 import { useSlideContext } from '../context'
28 import { makeId } from '../logic/utils'
29 import { collectKatexEquationLines } from './katex-lines'
30
31 const props = defineProps({
32 ranges: {
33 type: Array as PropType<string[]>,
34 default: () => [],
35 },
36 finally: {
37 type: [String, Number],
38 default: 'last',
39 },
40 startLine: {
41 type: Number,
42 default: 1,
43 },
44 at: {
45 type: [String, Number],
46 default: '+1',
47 },
48 })
49
50 const { $clicksContext: clicks } = useSlideContext()
51 const el = ref<HTMLDivElement>()
52 const id = makeId()
53
54 onUnmounted(() => {
55 clicks!.unregister(id)
56 })
57
58 onMounted(() => {
59 if (!clicks || !props.ranges?.length)
60 return
61
62 const clicksInfo = clicks.calculateSince(props.at, props.ranges.length - 1)
63 clicks.register(id, clicksInfo)
64
65 const index = computed(() => clicksInfo ? Math.max(0, clicks.current - clicksInfo.start + 1) : CLICKS_MAX)
66
67 const finallyRange = computed(() => {
68 return props.finally === 'last' ? props.ranges.at(-1) : props.finally.toString()
69 })
70
71 let hiddenByRange = false
72
73 watchEffect(() => {
74 if (!el.value)
75 return
76
77 let rangeStr = props.ranges[index.value] ?? finallyRange.value
78 const hide = rangeStr === 'hide'
79 // Only toggle the class when a `hide` range set it, so a click directive on the same element keeps control
80 if (hide || hiddenByRange) {
81 el.value.classList.toggle(CLASS_VCLICK_HIDDEN, hide)
82 hiddenByRange = hide
83 }
84 if (hide)
85 rangeStr = props.ranges[index.value + 1] ?? finallyRange.value
86
87 // This list maps rows from different parents to line them up
88 const lines = collectKatexEquationLines(el.value)
89 if (!lines.length)
90 return
91
92 const startLine = props.startLine
93 const highlights: number[] = parseRangeString(lines.length + startLine - 1, rangeStr)
94 lines.forEach((line, idx) => {
95 const highlighted = highlights.includes(idx + startLine)
96 line.forEach((node) => {
97 node.classList.toggle(CLASS_VCLICK_TARGET, true)
98 node.classList.toggle('highlighted', highlighted)
99 node.classList.toggle('dishonored', !highlighted)
100 })
101 })
102 })
103 })
104 </script>
105
106 <template>
107 <div ref="el" class="slidev-katex-wrapper">
108 <slot />
109 </div>
110 </template>
111
111 lines Plain Text