-
-
Notifications
You must be signed in to change notification settings - Fork 328
feat(ui): added word wrap to code viewer #2028
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
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 |
|---|---|---|
|
|
@@ -3,13 +3,15 @@ const props = defineProps<{ | |
| html: string | ||
| lines: number | ||
| selectedLines: { start: number; end: number } | null | ||
| wordWrap?: boolean | ||
| }>() | ||
|
|
||
| const emit = defineEmits<{ | ||
| lineClick: [lineNum: number, event: MouseEvent] | ||
| }>() | ||
|
|
||
| const codeRef = useTemplateRef('codeRef') | ||
| const lineNumbersRef = useTemplateRef('lineNumbersRef') | ||
|
|
||
| // Generate line numbers array | ||
| const lineNumbers = computed(() => { | ||
|
|
@@ -32,6 +34,30 @@ function onLineClick(lineNum: number, event: MouseEvent) { | |
| emit('lineClick', lineNum, event) | ||
| } | ||
|
|
||
| // Synchronize line number heights with code line heights (needed for word wrap) | ||
| function syncLineHeights() { | ||
| if (!props.wordWrap || !codeRef.value || !lineNumbersRef.value) { | ||
| // Reset heights if word wrap is disabled | ||
| if (lineNumbersRef.value) { | ||
| const nums = lineNumbersRef.value.querySelectorAll<HTMLElement>('.line-number') | ||
| nums.forEach(num => (num.style.height = '')) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| const lines = codeRef.value.querySelectorAll<HTMLElement>('code > .line') | ||
| const nums = lineNumbersRef.value.querySelectorAll<HTMLElement>('.line-number') | ||
|
|
||
| lines.forEach((line, index) => { | ||
| const num = nums[index] | ||
| if (num) { | ||
| // Use getBoundingClientRect for more precision if needed, but offsetHeight is usually enough | ||
| const height = line.offsetHeight | ||
| num.style.height = `${height}px` | ||
| } | ||
| }) | ||
| } | ||
|
Comment on lines
+37
to
+59
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify that markdown files default to preview mode, that CodeViewer is mounted with v-show,
# and that syncLineHeights currently only reruns on html/wordWrap/resize.
rg -n -C3 "const markdownViewMode|<CodeViewer|v-show=\"!fileContent.markdownHtml \\|\\| markdownViewMode === 'code'\"" "app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue"
rg -n -C3 "function syncLineHeights|offsetHeight|watch\\(|props.wordWrap|useEventListener\\(window, 'resize'" "app/components/Code/Viewer.vue"Repository: npmx-dev/npmx.dev Length of output: 2129 Re-sync wrapped line heights after the viewer becomes visible. The parent component mounts Add visibility detection to skip measurement while hidden, or use a 🧰 Tools🪛 Biome (2.4.6)[error] 43-43: This callback passed to forEach() iterable method should not return a value. (lint/suspicious/useIterableCallbackReturn) |
||
|
|
||
| // Apply highlighting to code lines when selection changes | ||
| function updateLineHighlighting() { | ||
| if (!codeRef.value) return | ||
|
|
@@ -53,11 +79,27 @@ function updateLineHighlighting() { | |
| watch( | ||
| () => [props.selectedLines, props.html] as const, | ||
| () => { | ||
| nextTick(updateLineHighlighting) | ||
| nextTick(() => { | ||
| updateLineHighlighting() | ||
| syncLineHeights() | ||
| }) | ||
| }, | ||
| { immediate: true }, | ||
| ) | ||
|
|
||
| // Also watch wordWrap specifically | ||
| watch( | ||
| () => props.wordWrap, | ||
| () => { | ||
| nextTick(syncLineHeights) | ||
| }, | ||
| ) | ||
|
|
||
| // Sync on resize | ||
| if (import.meta.client) { | ||
| useEventListener(window, 'resize', syncLineHeights) | ||
| } | ||
|
|
||
| // Use Nuxt's `navigateTo` for the rendered import links | ||
| function handleImportLinkNavigate() { | ||
| if (!codeRef.value) return | ||
|
|
@@ -86,9 +128,10 @@ watch( | |
| </script> | ||
|
|
||
| <template> | ||
| <div class="code-viewer flex min-h-full max-w-full"> | ||
| <div class="code-viewer flex min-h-full max-w-full" :class="{ 'is-wrapped': wordWrap }"> | ||
| <!-- Line numbers column --> | ||
| <div | ||
| ref="lineNumbersRef" | ||
| 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" | ||
|
|
@@ -155,6 +198,12 @@ watch( | |
| transition: background-color 0.1s; | ||
| } | ||
|
|
||
| .is-wrapped .code-content :deep(.line) { | ||
| white-space: pre-wrap; | ||
| word-break: break-all; | ||
| max-height: none; | ||
| } | ||
|
|
||
| /* Highlighted lines in code content - extend full width with negative margin */ | ||
| .code-content :deep(.line.highlighted) { | ||
| @apply bg-yellow-500/20; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,6 +265,8 @@ function scrollToTop() { | |
| // Canonical URL for this code page | ||
| const canonicalUrl = computed(() => `https://npmx.dev${getCodeUrl(route.params)}`) | ||
|
|
||
| const wordWrap = useLocalStorage('npmx-code-word-wrap', false) | ||
|
|
||
| // Toggle markdown view mode | ||
| const markdownViewModes = [ | ||
| { | ||
|
|
@@ -471,6 +473,15 @@ defineOgImageComponent('Default', { | |
| <span class="i-lucide:arrow-up w-3 h-3" /> | ||
| {{ $t('code.scroll_to_top') }} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| class="px-2 py-1 font-mono text-xs text-fg-muted bg-bg-subtle border border-border rounded hover:text-fg hover:border-border-hover transition-colors items-center inline-flex gap-1" | ||
| :class="{ 'bg-accent/10 text-accent border-accent/20': wordWrap }" | ||
| @click="wordWrap = !wordWrap" | ||
| > | ||
| <span class="i-lucide:wrap-text w-3 h-3" /> | ||
| {{ $t('code.word_wrap') }} | ||
| </button> | ||
|
Comment on lines
+476
to
+484
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. Expose the wrap state on the toggle button. This behaves as a toggle, but without |
||
| <button | ||
| v-if="selectedLines" | ||
| type="button" | ||
|
|
@@ -515,6 +526,7 @@ defineOgImageComponent('Default', { | |
| :html="fileContent.html" | ||
| :lines="fileContent.lines" | ||
| :selected-lines="selectedLines" | ||
| :word-wrap="wordWrap" | ||
| @line-click="handleLineClick" | ||
| /> | ||
| </template> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find app/components/Code -name "Viewer.vue" -type fRepository: npmx-dev/npmx.dev
Length of output: 91
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 749
🏁 Script executed:
cat -n app/components/Code/Viewer.vue | head -50Repository: npmx-dev/npmx.dev
Length of output: 1935
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 73
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 1835
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 406
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 88
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 88
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 2777
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 456
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 144
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 750
Use a block body when clearing the line-number heights.
Line 43 returns the assignment value from the
forEachcallback, which Biome'slint/suspicious/useIterableCallbackReturnrule flags. A block body keeps the behaviour the same and resolves the lint error:🧰 Tools
🪛 Biome (2.4.6)
[error] 43-43: This callback passed to forEach() iterable method should not return a value.
(lint/suspicious/useIterableCallbackReturn)