-
-
Notifications
You must be signed in to change notification settings - Fork 347
fix: wrap code viewer lines by computing width #2070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
da6f15a
6955710
9e08913
a6e9ca5
f62fefe
81c79a9
a201a36
3bafb67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,25 +11,59 @@ const emit = defineEmits<{ | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const codeRef = useTemplateRef('codeRef') | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Generate line numbers array | ||||||||||||||||||||||||||||||||||||||||||||||
| const lineNumbers = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return Array.from({ length: props.lines }, (_, i) => i + 1) | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| const LINE_HEIGHT_PX = 24 | ||||||||||||||||||||||||||||||||||||||||||||||
| const lineMultipliers = ref<number[]>([]) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Used for CSS calculation of line number column width | ||||||||||||||||||||||||||||||||||||||||||||||
| const lineDigits = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return String(props.lines).length | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| function updateLineMultipliers() { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!codeRef.value) return | ||||||||||||||||||||||||||||||||||||||||||||||
| const lines = codeRef.value.querySelectorAll<HTMLElement>('code > .line') | ||||||||||||||||||||||||||||||||||||||||||||||
| const result: number[] = Array.from({ length: lines.length }) | ||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < lines.length; i++) | ||||||||||||||||||||||||||||||||||||||||||||||
| result[i] = Math.max(1, Math.round(lines[i]!.offsetHeight / LINE_HEIGHT_PX)) | ||||||||||||||||||||||||||||||||||||||||||||||
| lineMultipliers.value = result | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| watch( | ||||||||||||||||||||||||||||||||||||||||||||||
| () => props.html, | ||||||||||||||||||||||||||||||||||||||||||||||
| () => nextTick(updateLineMultipliers), | ||||||||||||||||||||||||||||||||||||||||||||||
| { immediate: true }, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
| useResizeObserver(codeRef, updateLineMultipliers) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const lineDigits = computed(() => String(props.lines).length) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Check if a line is selected | ||||||||||||||||||||||||||||||||||||||||||||||
| function isLineSelected(lineNum: number): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!props.selectedLines) return false | ||||||||||||||||||||||||||||||||||||||||||||||
| return lineNum >= props.selectedLines.start && lineNum <= props.selectedLines.end | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Handle line number click | ||||||||||||||||||||||||||||||||||||||||||||||
| function onLineClick(lineNum: number, event: MouseEvent) { | ||||||||||||||||||||||||||||||||||||||||||||||
| emit('lineClick', lineNum, event) | ||||||||||||||||||||||||||||||||||||||||||||||
| const lineNumbersHtml = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const multipliers = lineMultipliers.value | ||||||||||||||||||||||||||||||||||||||||||||||
| const total = props.lines | ||||||||||||||||||||||||||||||||||||||||||||||
| const parts: string[] = [] | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < total; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const num = i + 1 | ||||||||||||||||||||||||||||||||||||||||||||||
| const cls = isLineSelected(num) | ||||||||||||||||||||||||||||||||||||||||||||||
| ? 'bg-yellow-500/20 text-fg' | ||||||||||||||||||||||||||||||||||||||||||||||
| : 'text-fg-subtle hover:text-fg-muted' | ||||||||||||||||||||||||||||||||||||||||||||||
| parts.push( | ||||||||||||||||||||||||||||||||||||||||||||||
| `<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>`, | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const extra = (multipliers[i] ?? 1) - 1 | ||||||||||||||||||||||||||||||||||||||||||||||
| for (let j = 0; j < extra; j++) parts.push('<span class="block px-3 leading-6">\u00a0</span>') | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return parts.join('') | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| function onLineNumberClick(event: MouseEvent) { | ||||||||||||||||||||||||||||||||||||||||||||||
| const target = (event.target as HTMLElement).closest<HTMLAnchorElement>('a[data-line]') | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!target) return | ||||||||||||||||||||||||||||||||||||||||||||||
| event.preventDefault() | ||||||||||||||||||||||||||||||||||||||||||||||
| const lineNum = Number(target.dataset.line) | ||||||||||||||||||||||||||||||||||||||||||||||
| if (lineNum) emit('lineClick', lineNum, event) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Apply highlighting to code lines when selection changes | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -86,36 +120,21 @@ watch( | |||||||||||||||||||||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <template> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div class="code-viewer flex min-h-full max-w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- Line numbers column --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div class="code-viewer flex min-h-full max-w-full" :style="{ '--line-digits': lineDigits }"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- Line numbers column — raw HTML + event delegation to avoid v-for overhead on large files --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- eslint-disable vue/no-v-html --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||
| class="line-numbers shrink-0 bg-bg-subtle border-ie border-solid border-border text-end select-none relative" | ||||||||||||||||||||||||||||||||||||||||||||||
| :style="{ '--line-digits': lineDigits }" | ||||||||||||||||||||||||||||||||||||||||||||||
| aria-hidden="true" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- 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) --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||||||||||||||||||
| v-for="lineNum in lineNumbers" | ||||||||||||||||||||||||||||||||||||||||||||||
| :id="`L${lineNum}`" | ||||||||||||||||||||||||||||||||||||||||||||||
| :key="lineNum" | ||||||||||||||||||||||||||||||||||||||||||||||
| :href="`#L${lineNum}`" | ||||||||||||||||||||||||||||||||||||||||||||||
| tabindex="-1" | ||||||||||||||||||||||||||||||||||||||||||||||
| class="line-number block px-3 py-0 font-mono text-sm leading-6 cursor-pointer transition-colors no-underline" | ||||||||||||||||||||||||||||||||||||||||||||||
| :class="[ | ||||||||||||||||||||||||||||||||||||||||||||||
| isLineSelected(lineNum) | ||||||||||||||||||||||||||||||||||||||||||||||
| ? 'bg-yellow-500/20 text-fg' | ||||||||||||||||||||||||||||||||||||||||||||||
| : 'text-fg-subtle hover:text-fg-muted', | ||||||||||||||||||||||||||||||||||||||||||||||
| ]" | ||||||||||||||||||||||||||||||||||||||||||||||
| @click.prevent="onLineClick(lineNum, $event)" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| {{ lineNum }} | ||||||||||||||||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| v-html="lineNumbersHtml" | ||||||||||||||||||||||||||||||||||||||||||||||
| @click="onLineNumberClick" | ||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- eslint-enable vue/no-v-html --> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <!-- Code content --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div class="code-content flex-1 overflow-x-auto min-w-0"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div class="code-content"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- eslint-disable vue/no-v-html -- HTML is generated server-side by Shiki --> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div ref="codeRef" class="code-lines min-w-full w-fit" v-html="html" /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div ref="codeRef" class="code-lines" v-html="html" /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <!-- eslint-enable vue/no-v-html --> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -124,46 +143,56 @@ watch( | |||||||||||||||||||||||||||||||||||||||||||||
| <style scoped> | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-viewer { | ||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 14px; | ||||||||||||||||||||||||||||||||||||||||||||||
| /* 1ch per digit + 1.5rem (px-3 * 2) padding */ | ||||||||||||||||||||||||||||||||||||||||||||||
| --line-numbers-width: calc(var(--line-digits) * 1ch + 1.5rem); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .line-numbers { | ||||||||||||||||||||||||||||||||||||||||||||||
| /* 1ch per digit + 1.5rem (px-3 * 2) padding */ | ||||||||||||||||||||||||||||||||||||||||||||||
| min-width: calc(var(--line-digits) * 1ch + 1.5rem); | ||||||||||||||||||||||||||||||||||||||||||||||
| min-width: var(--line-numbers-width); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .code-content { | ||||||||||||||||||||||||||||||||||||||||||||||
| flex: 1; | ||||||||||||||||||||||||||||||||||||||||||||||
| min-width: 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| max-width: calc(100% - var(--line-numbers-width)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(pre) { | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(pre) { | ||||||||||||||||||||||||||||||||||||||||||||||
| margin: 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| padding: 0; | ||||||||||||||||||||||||||||||||||||||||||||||
| background: transparent !important; | ||||||||||||||||||||||||||||||||||||||||||||||
| overflow: visible; | ||||||||||||||||||||||||||||||||||||||||||||||
| max-width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(code) { | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(code) { | ||||||||||||||||||||||||||||||||||||||||||||||
| display: block; | ||||||||||||||||||||||||||||||||||||||||||||||
| padding: 0 1rem; | ||||||||||||||||||||||||||||||||||||||||||||||
| background: transparent !important; | ||||||||||||||||||||||||||||||||||||||||||||||
| max-width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(.line) { | ||||||||||||||||||||||||||||||||||||||||||||||
| display: block; | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(.line) { | ||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||
| flex-wrap: wrap; | ||||||||||||||||||||||||||||||||||||||||||||||
| /* Ensure consistent height matching line numbers */ | ||||||||||||||||||||||||||||||||||||||||||||||
| line-height: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||
| min-height: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||
| max-height: 24px; | ||||||||||||||||||||||||||||||||||||||||||||||
| white-space: pre; | ||||||||||||||||||||||||||||||||||||||||||||||
| line-height: calc(v-bind(LINE_HEIGHT_PX) * 1px); | ||||||||||||||||||||||||||||||||||||||||||||||
| min-height: calc(v-bind(LINE_HEIGHT_PX) * 1px); | ||||||||||||||||||||||||||||||||||||||||||||||
| white-space: pre-wrap; | ||||||||||||||||||||||||||||||||||||||||||||||
| overflow: hidden; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+175
to
182
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Long unbroken tokens can still overflow in wrap mode. Line 185 uses 🩹 Proposed fix📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| transition: background-color 0.1s; | ||||||||||||||||||||||||||||||||||||||||||||||
| max-width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /* Highlighted lines in code content - extend full width with negative margin */ | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(.line.highlighted) { | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(.line.highlighted) { | ||||||||||||||||||||||||||||||||||||||||||||||
| @apply bg-yellow-500/20; | ||||||||||||||||||||||||||||||||||||||||||||||
| margin: 0 -1rem; | ||||||||||||||||||||||||||||||||||||||||||||||
| padding: 0 1rem; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /* Clickable import links */ | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(.import-link) { | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(.import-link) { | ||||||||||||||||||||||||||||||||||||||||||||||
| color: inherit; | ||||||||||||||||||||||||||||||||||||||||||||||
| text-decoration: underline; | ||||||||||||||||||||||||||||||||||||||||||||||
| text-decoration-style: dotted; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -175,7 +204,7 @@ watch( | |||||||||||||||||||||||||||||||||||||||||||||
| cursor: pointer; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| .code-content :deep(.import-link:hover) { | ||||||||||||||||||||||||||||||||||||||||||||||
| .code-content:deep(.import-link:hover) { | ||||||||||||||||||||||||||||||||||||||||||||||
| text-decoration-style: solid; | ||||||||||||||||||||||||||||||||||||||||||||||
| text-decoration-color: #9ecbff; /* syntax.str - light blue */ | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,17 +34,13 @@ export function buildWeeklyEvolution( | |
| if (sorted.length === 0) return [] | ||
|
|
||
| const rangeStartDate = parseIsoDate(rangeStartIso) | ||
| const rangeEndDate = parseIsoDate(rangeEndIso) | ||
|
|
||
| // Align from last day with actual data (npm has 1-2 day delay, today is incomplete) | ||
| const lastNonZero = sorted.findLast(d => d.value > 0) | ||
| const pickerEnd = parseIsoDate(rangeEndIso) | ||
| const effectiveEnd = lastNonZero ? parseIsoDate(lastNonZero.day) : pickerEnd | ||
| const rangeEndDate = effectiveEnd.getTime() < pickerEnd.getTime() ? effectiveEnd : pickerEnd | ||
|
|
||
| // Group into 7-day buckets from END backwards | ||
| const buckets = new Map<number, number>() | ||
|
|
||
| for (const item of sorted) { | ||
| const offset = Math.floor((rangeEndDate.getTime() - parseIsoDate(item.day).getTime()) / DAY_MS) | ||
| const itemDate = parseIsoDate(item.day) | ||
| const offset = Math.floor((rangeEndDate.getTime() - itemDate.getTime()) / DAY_MS) | ||
|
Comment on lines
+37
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these changes intended in this PR?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are not, a mistake on my part for merging latest instead of rebasing :) I will have to remove them. Don't think this PR will end up being merged to be honest, given the above conversation |
||
| if (offset < 0) continue | ||
| const idx = Math.floor(offset / 7) | ||
| buckets.set(idx, (buckets.get(idx) ?? 0) + item.value) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.