From ca13ccad9c1abbe9d043678dea4f5cc43455a3a6 Mon Sep 17 00:00:00 2001 From: stephansama Date: Tue, 10 Mar 2026 14:11:02 -0400 Subject: [PATCH] feat(word-wrap): added word wrap to code viewer --- app/components/Code/Viewer.vue | 53 ++++++++++++++++++- .../v/[version]/[...filePath].vue | 12 +++++ i18n/locales/en.json | 3 +- i18n/schema.json | 3 ++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/app/components/Code/Viewer.vue b/app/components/Code/Viewer.vue index 90bcf0c221..ec544b6e83 100644 --- a/app/components/Code/Viewer.vue +++ b/app/components/Code/Viewer.vue @@ -3,6 +3,7 @@ const props = defineProps<{ html: string lines: number selectedLines: { start: number; end: number } | null + wordWrap?: boolean }>() const emit = defineEmits<{ @@ -10,6 +11,7 @@ const emit = defineEmits<{ }>() 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('.line-number') + nums.forEach(num => (num.style.height = '')) + } + return + } + + const lines = codeRef.value.querySelectorAll('code > .line') + const nums = lineNumbersRef.value.querySelectorAll('.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` + } + }) +} + // 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( diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 00ba413a5b..f35de882f1 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -792,7 +792,8 @@ "code": "code" }, "file_path": "File path", - "scroll_to_top": "Scroll to top" + "scroll_to_top": "Scroll to top", + "word_wrap": "Word wrap" }, "badges": { "provenance": { diff --git a/i18n/schema.json b/i18n/schema.json index e3c3c0a4b4..9a7a0b1b9c 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -2382,6 +2382,9 @@ }, "scroll_to_top": { "type": "string" + }, + "word_wrap": { + "type": "string" } }, "additionalProperties": false