Skip to content

Commit ca13cca

Browse files
committed
feat(word-wrap): added word wrap to code viewer
1 parent 3712560 commit ca13cca

4 files changed

Lines changed: 68 additions & 3 deletions

File tree

app/components/Code/Viewer.vue

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ const props = defineProps<{
33
html: string
44
lines: number
55
selectedLines: { start: number; end: number } | null
6+
wordWrap?: boolean
67
}>()
78
89
const emit = defineEmits<{
910
lineClick: [lineNum: number, event: MouseEvent]
1011
}>()
1112
1213
const codeRef = useTemplateRef('codeRef')
14+
const lineNumbersRef = useTemplateRef('lineNumbersRef')
1315
1416
// Generate line numbers array
1517
const lineNumbers = computed(() => {
@@ -32,6 +34,30 @@ function onLineClick(lineNum: number, event: MouseEvent) {
3234
emit('lineClick', lineNum, event)
3335
}
3436
37+
// Synchronize line number heights with code line heights (needed for word wrap)
38+
function syncLineHeights() {
39+
if (!props.wordWrap || !codeRef.value || !lineNumbersRef.value) {
40+
// Reset heights if word wrap is disabled
41+
if (lineNumbersRef.value) {
42+
const nums = lineNumbersRef.value.querySelectorAll<HTMLElement>('.line-number')
43+
nums.forEach(num => (num.style.height = ''))
44+
}
45+
return
46+
}
47+
48+
const lines = codeRef.value.querySelectorAll<HTMLElement>('code > .line')
49+
const nums = lineNumbersRef.value.querySelectorAll<HTMLElement>('.line-number')
50+
51+
lines.forEach((line, index) => {
52+
const num = nums[index]
53+
if (num) {
54+
// Use getBoundingClientRect for more precision if needed, but offsetHeight is usually enough
55+
const height = line.offsetHeight
56+
num.style.height = `${height}px`
57+
}
58+
})
59+
}
60+
3561
// Apply highlighting to code lines when selection changes
3662
function updateLineHighlighting() {
3763
if (!codeRef.value) return
@@ -53,11 +79,27 @@ function updateLineHighlighting() {
5379
watch(
5480
() => [props.selectedLines, props.html] as const,
5581
() => {
56-
nextTick(updateLineHighlighting)
82+
nextTick(() => {
83+
updateLineHighlighting()
84+
syncLineHeights()
85+
})
5786
},
5887
{ immediate: true },
5988
)
6089
90+
// Also watch wordWrap specifically
91+
watch(
92+
() => props.wordWrap,
93+
() => {
94+
nextTick(syncLineHeights)
95+
},
96+
)
97+
98+
// Sync on resize
99+
if (import.meta.client) {
100+
useEventListener(window, 'resize', syncLineHeights)
101+
}
102+
61103
// Use Nuxt's `navigateTo` for the rendered import links
62104
function handleImportLinkNavigate() {
63105
if (!codeRef.value) return
@@ -86,9 +128,10 @@ watch(
86128
</script>
87129

88130
<template>
89-
<div class="code-viewer flex min-h-full max-w-full">
131+
<div class="code-viewer flex min-h-full max-w-full" :class="{ 'is-wrapped': wordWrap }">
90132
<!-- Line numbers column -->
91133
<div
134+
ref="lineNumbersRef"
92135
class="line-numbers shrink-0 bg-bg-subtle border-ie border-solid border-border text-end select-none relative"
93136
:style="{ '--line-digits': lineDigits }"
94137
aria-hidden="true"
@@ -155,6 +198,12 @@ watch(
155198
transition: background-color 0.1s;
156199
}
157200
201+
.is-wrapped .code-content :deep(.line) {
202+
white-space: pre-wrap;
203+
word-break: break-all;
204+
max-height: none;
205+
}
206+
158207
/* Highlighted lines in code content - extend full width with negative margin */
159208
.code-content :deep(.line.highlighted) {
160209
@apply bg-yellow-500/20;

app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ function scrollToTop() {
265265
// Canonical URL for this code page
266266
const canonicalUrl = computed(() => `https://npmx.dev${getCodeUrl(route.params)}`)
267267
268+
const wordWrap = useLocalStorage('npmx-code-word-wrap', false)
269+
268270
// Toggle markdown view mode
269271
const markdownViewModes = [
270272
{
@@ -471,6 +473,15 @@ defineOgImageComponent('Default', {
471473
<span class="i-lucide:arrow-up w-3 h-3" />
472474
{{ $t('code.scroll_to_top') }}
473475
</button>
476+
<button
477+
type="button"
478+
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"
479+
:class="{ 'bg-accent/10 text-accent border-accent/20': wordWrap }"
480+
@click="wordWrap = !wordWrap"
481+
>
482+
<span class="i-lucide:wrap-text w-3 h-3" />
483+
{{ $t('code.word_wrap') }}
484+
</button>
474485
<button
475486
v-if="selectedLines"
476487
type="button"
@@ -515,6 +526,7 @@ defineOgImageComponent('Default', {
515526
:html="fileContent.html"
516527
:lines="fileContent.lines"
517528
:selected-lines="selectedLines"
529+
:word-wrap="wordWrap"
518530
@line-click="handleLineClick"
519531
/>
520532
</template>

i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,8 @@
792792
"code": "code"
793793
},
794794
"file_path": "File path",
795-
"scroll_to_top": "Scroll to top"
795+
"scroll_to_top": "Scroll to top",
796+
"word_wrap": "Word wrap"
796797
},
797798
"badges": {
798799
"provenance": {

i18n/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,9 @@
23822382
},
23832383
"scroll_to_top": {
23842384
"type": "string"
2385+
},
2386+
"word_wrap": {
2387+
"type": "string"
23852388
}
23862389
},
23872390
"additionalProperties": false

0 commit comments

Comments
 (0)