11<script setup lang="ts">
2- import katex from ' katex'
3- import { onBeforeUnmount , onMounted , ref , watch } from ' vue'
4- import { renderKaTeXInWorker , setKaTeXCache } from ' ../../workers/katexWorkerClient'
2+ import { computed , onBeforeUnmount , onMounted , ref , useAttrs , watch } from ' vue'
3+ import { renderKaTeXInWorker } from ' ../../workers/katexWorkerClient'
54
65const props = defineProps <{
76 node: {
@@ -10,13 +9,28 @@ const props = defineProps<{
109 raw: string
1110 loading? : boolean
1211 }
12+ /** link text / underline color (CSS color string) */
13+ color? : string
14+ /** underline height in px */
15+ underlineHeight? : number
16+ /** underline bottom offset (px). Can be negative. */
17+ underlineBottom? : number | string
18+ /** total animation duration in seconds */
19+ animationDuration? : number
20+ /** underline opacity */
21+ animationOpacity? : number
22+ /** animation timing function */
23+ animationTiming? : string
24+ /** animation iteration (e.g. 'infinite' or a number) */
25+ animationIteration? : string | number
1326}>()
1427
1528const mathElement = ref <HTMLElement | null >(null )
1629let hasRenderedOnce = false
1730let currentRenderId = 0
1831let isUnmounted = false
1932let currentAbortController: AbortController | null = null
33+ const renderingLoading = ref (true )
2034
2135function renderMath() {
2236 if (! props .node .content || ! mathElement .value || isUnmounted )
@@ -37,6 +51,7 @@ function renderMath() {
3751 return
3852 if (! mathElement .value )
3953 return
54+ renderingLoading .value = false
4055 mathElement .value .innerHTML = html
4156 hasRenderedOnce = true
4257 })
@@ -45,28 +60,21 @@ function renderMath() {
4560 return
4661 if (! mathElement .value )
4762 return
48- // Try synchronous render as a fallback
49- try {
50- const html = katex .renderToString (props .node .content , {
51- throwOnError: true ,
52- displayMode: false ,
53- })
54- mathElement .value .innerHTML = html
55- hasRenderedOnce = true
56- try {
57- setKaTeXCache (props .node .content , false , html )
58- }
59- catch {
60- // ignore cache set errors
61- }
62- }
63- catch {
64- if (! hasRenderedOnce || ! props .node .loading )
65- mathElement .value .textContent = props .node .raw
63+ if (! hasRenderedOnce || ! props .node .loading ) {
64+ renderingLoading .value = true
65+ // mathElement.value.textContent = props.node.raw
6666 }
6767 })
6868}
6969
70+ // watch(
71+ // () => props.node.loading,
72+ // (newVal) => {
73+ // nextTick(() => {
74+
75+ // })
76+ // },
77+ // )
7078watch (
7179 () => props .node .content ,
7280 () => {
@@ -85,15 +93,73 @@ onBeforeUnmount(() => {
8593 currentAbortController = null
8694 }
8795})
96+ const cssVars = computed (() => {
97+ const bottom = props .underlineBottom !== undefined
98+ ? (typeof props .underlineBottom === ' number' ? ` ${props .underlineBottom }px ` : String (props .underlineBottom ))
99+ : ' -3px'
100+
101+ return {
102+ ' --link-color' : props .color ?? ' #0366d6' ,
103+ ' --underline-height' : ` ${props .underlineHeight ?? 2 }px ` ,
104+ ' --underline-bottom' : bottom ,
105+ ' --underline-opacity' : String (props .animationOpacity ?? 0.9 ),
106+ ' --underline-duration' : ` ${props .animationDuration ?? 0.8 }s ` ,
107+ ' --underline-timing' : props .animationTiming ?? ' linear' ,
108+ ' --underline-iteration' : typeof props .animationIteration === ' number' ? String (props .animationIteration ) : (props .animationIteration ?? ' infinite' ),
109+ } as Record <string , string >
110+ })
111+ const attrs = useAttrs ()
88112 </script >
89113
90114<template >
91- <span ref =" mathElement" class =" math-inline" />
115+ <!-- <span v-if="renderingLoading"
116+ :style="cssVars"
117+ >Loading...</span> -->
118+ <span v-show =" renderingLoading" class =" math-loading inline-flex items-baseline gap-1.5" :aria-hidden =" !node.loading ? 'true' : 'false'" v-bind =" attrs" :style =" cssVars" >
119+ <span class =" math-text-wrapper relative inline-flex" >
120+ <span class =" leading-[normal] math-text" >Loading...</span >
121+ <span class =" underline-anim" aria-hidden =" true" />
122+ </span >
123+ </span >
124+ <span v-show =" !renderingLoading" ref =" mathElement" class =" math-inline" />
92125</template >
93126
94127<style >
95128.math-inline {
96129 display : inline-block ;
97130 vertical-align : middle ;
98131}
132+ .math-loading .math-text-wrapper {
133+ position : relative ;
134+ }
135+
136+ .math-loading .math-text {
137+ position : relative ;
138+ z-index : 2 ;
139+ }
140+
141+ .underline-anim {
142+ position : absolute ;
143+ left : 0 ;
144+ right : 0 ;
145+ height : var (--underline-height , 2px );
146+ bottom : var (--underline-bottom , -3px ); /* a little below text */
147+ background : currentColor ;
148+ /* grow symmetrically from the center */
149+ transform-origin : center center ;
150+ will-change : transform, opacity;
151+ opacity : var (--underline-opacity , 0.9 );
152+ transform : scaleX (0 );
153+ animation : underlineLoop var (--underline-duration , 0.8s ) var (--underline-timing , linear ) var (--underline-iteration , infinite );
154+ }
155+
156+ @keyframes underlineLoop {
157+ 0% { transform : scaleX (0 ); opacity : var (--underline-opacity , 0.9 ); }
158+ /* draw to full width by 75% (0.6s) */
159+ 75% { transform : scaleX (1 ); opacity : var (--underline-opacity , 0.9 ); }
160+ /* hold at full width until ~99% (~0.2s pause) */
161+ 99% { transform : scaleX (1 ); opacity : var (--underline-opacity , 0.9 ); }
162+ /* collapse quickly back to center right at the end */
163+ 100% { transform : scaleX (0 ); opacity : 0 ; }
164+ }
99165 </style >
0 commit comments