@@ -11,16 +11,16 @@ const emit = defineEmits<{
1111
1212const codeRef = useTemplateRef (' codeRef' )
1313
14- // Using this so we can track the height of each line, and therefore compute digit sidebar
14+ const LINE_HEIGHT_PX = 24
1515const lineMultipliers = ref <number []>([])
16- const LINE_HEIGHT_PX = 24 // also used in css
1716
1817function updateLineMultipliers() {
1918 if (! codeRef .value ) return
20- const lines = Array .from (codeRef .value .querySelectorAll (' code > .line' ))
21- lineMultipliers .value = lines .map (line =>
22- Math .max (1 , Math .round (parseFloat (getComputedStyle (line ).height ) / LINE_HEIGHT_PX )),
23- )
19+ const lines = codeRef .value .querySelectorAll <HTMLElement >(' code > .line' )
20+ const result: number [] = Array .from ({ length: lines .length })
21+ for (let i = 0 ; i < lines .length ; i ++ )
22+ result [i ] = Math .max (1 , Math .round (lines [i ]! .offsetHeight / LINE_HEIGHT_PX ))
23+ lineMultipliers .value = result
2424}
2525
2626watch (
@@ -30,28 +30,40 @@ watch(
3030)
3131useResizeObserver (codeRef , updateLineMultipliers )
3232
33- // Line numbers ++ blank rows for the wrapped lines
34- const displayLines = computed (() => {
35- const result: (number | null )[] = []
36- for (let i = 0 ; i < props .lines ; i ++ ) {
37- result .push (i + 1 )
38- const extra = (lineMultipliers .value [i ] ?? 1 ) - 1
39- for (let j = 0 ; j < extra ; j ++ ) result .push (null )
40- }
41- return result
42- })
43-
4433const lineDigits = computed (() => String (props .lines ).length )
4534
46- // Check if a line is selected
4735function isLineSelected(lineNum : number ): boolean {
4836 if (! props .selectedLines ) return false
4937 return lineNum >= props .selectedLines .start && lineNum <= props .selectedLines .end
5038}
5139
52- // Handle line number click
53- function onLineClick(lineNum : number , event : MouseEvent ) {
54- emit (' lineClick' , lineNum , event )
40+ const lineNumbersHtml = computed (() => {
41+ const multipliers = lineMultipliers .value
42+ const total = props .lines
43+ const parts: string [] = []
44+
45+ for (let i = 0 ; i < total ; i ++ ) {
46+ const num = i + 1
47+ const cls = isLineSelected (num )
48+ ? ' bg-yellow-500/20 text-fg'
49+ : ' text-fg-subtle hover:text-fg-muted'
50+ parts .push (
51+ ` <a id="L${num }" href="#L${num }" tabindex="-1" class="line-number block px-3 py-0 font-mono text-sm leading-6 cursor-pointer transition-colors no-underline ${cls }" data-line="${num }">${num }</a> ` ,
52+ )
53+
54+ const extra = (multipliers [i ] ?? 1 ) - 1
55+ for (let j = 0 ; j < extra ; j ++ ) parts .push (' <span class="block px-3 leading-6">\u00a0 </span>' )
56+ }
57+
58+ return parts .join (' ' )
59+ })
60+
61+ function onLineNumberClick(event : MouseEvent ) {
62+ const target = (event .target as HTMLElement ).closest <HTMLAnchorElement >(' a[data-line]' )
63+ if (! target ) return
64+ event .preventDefault ()
65+ const lineNum = Number (target .dataset .line )
66+ if (lineNum ) emit (' lineClick' , lineNum , event )
5567}
5668
5769// Apply highlighting to code lines when selection changes
@@ -109,31 +121,15 @@ watch(
109121
110122<template >
111123 <div class =" code-viewer flex min-h-full max-w-full" :style =" { '--line-digits': lineDigits }" >
112- <!-- Line numbers column -->
124+ <!-- Line numbers column — raw HTML + event delegation to avoid v-for overhead on large files -->
125+ <!-- eslint-disable vue/no-v-html -->
113126 <div
114127 class =" line-numbers shrink-0 bg-bg-subtle border-ie border-solid border-border text-end select-none relative"
115128 aria-hidden =" true"
116- >
117- <!-- This needs to be a native <a> element, because `LinkBase` (or specifically `NuxtLink`) does not seem to work when trying to prevent default behavior (jumping to the anchor) -->
118- <template v-for =" (lineNum , idx ) in displayLines " :key =" idx " >
119- <a
120- v-if =" lineNum !== null"
121- :id =" `L${lineNum}`"
122- :href =" `#L${lineNum}`"
123- tabindex =" -1"
124- class =" line-number block px-3 py-0 font-mono text-sm leading-6 cursor-pointer transition-colors no-underline"
125- :class =" [
126- isLineSelected(lineNum)
127- ? 'bg-yellow-500/20 text-fg'
128- : 'text-fg-subtle hover:text-fg-muted',
129- ]"
130- @click.prevent =" onLineClick(lineNum, $event)"
131- >
132- {{ lineNum }}
133- </a >
134- <span v-else class =" block px-3 leading-6" >  ; </span >
135- </template >
136- </div >
129+ v-html =" lineNumbersHtml"
130+ @click =" onLineNumberClick"
131+ />
132+ <!-- eslint-enable vue/no-v-html -->
137133
138134 <!-- Code content -->
139135 <div class =" code-content" >
0 commit comments