From 16790e19f93f1c8ce204849d139f5730fec05c5a Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 27 Nov 2025 20:10:22 +0100 Subject: [PATCH] fix(editor): preserve consecutive whitespace in comments in TipTap (#1893) * fix(editor): preserve consecutive whitespace in comments in TipTap Ensure multiple spaces in comment content are no longer collapsed when editing/saving by: - Adding SetContentOptions with parseOptions.preserveWhitespace = 'full' - Applying those options to all setContent calls (initial load, exit edit mode, post-upload cleanup) - Enabling preserveWhitespace in editor parseOptions Previously, repeated spaces were normalized away after setContent(false), causing comments with deliberate spacing to be altered unexpectedly. No behavioral changes beyond whitespace retention; renders identical except space fidelity. * fix(editor): use preserveWhitespace true instead of full to avoid duplicate checklist items The 'full' mode preserves all whitespace including between block elements, which caused the HTML parser to create extra empty list items from newlines between
  • tags. Using 'true' preserves whitespace in inline content only, which still achieves the goal of preserving consecutive spaces in text while not creating spurious nodes from formatting whitespace. --------- Co-authored-by: maggch --- .../src/components/input/editor/TipTap.vue | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/input/editor/TipTap.vue b/frontend/src/components/input/editor/TipTap.vue index 1c9a5f06c..1041a30f9 100644 --- a/frontend/src/components/input/editor/TipTap.vue +++ b/frontend/src/components/input/editor/TipTap.vue @@ -145,7 +145,7 @@ import {eventToHotkeyString} from '@github/hotkey' import EditorToolbar from './EditorToolbar.vue' import StarterKit from '@tiptap/starter-kit' -import {Extension, mergeAttributes} from '@tiptap/core' +import {Extension, mergeAttributes, type SetContentOptions} from '@tiptap/core' import {EditorContent, type Extensions, useEditor, VueNodeViewRenderer} from '@tiptap/vue-3' import {Plugin, PluginKey} from '@tiptap/pm/state' import {marked} from 'marked' @@ -216,6 +216,12 @@ const tiptapInstanceRef = ref(null) const {t} = useI18n() +const defaultSetContentOptions: SetContentOptions = { + parseOptions: { + preserveWhitespace: true, + }, +} + const CustomTableCell = TableCell.extend({ addAttributes() { return { @@ -549,6 +555,9 @@ const editor = useEditor({ onUpdate: () => { bubbleNow() }, + parseOptions: { + preserveWhitespace: true, + }, }) watch( @@ -606,7 +615,10 @@ function bubbleSave() { } function exitEditMode() { - editor.value?.commands.setContent(lastSavedState, {emitUpdate: false}) + editor.value?.commands.setContent(lastSavedState, { + ...defaultSetContentOptions, + emitUpdate: false, + }) // Clear draft from localStorage when discarding changes if (props.storageKey) { @@ -659,7 +671,10 @@ function uploadAndInsertFiles(files: File[] | FileList) { const html = editor.value?.getHTML().replace(UPLOAD_PLACEHOLDER_ELEMENT, '') ?? '' - editor.value?.commands.setContent(html, {emitUpdate: false}) + editor.value?.commands.setContent(html, { + ...defaultSetContentOptions, + emitUpdate: false, + }) bubbleNow() }) @@ -733,7 +748,10 @@ onBeforeUnmount(() => { function setModeAndValue(value: string) { internalMode.value = isEditorContentEmpty(value) ? 'edit' : 'preview' - editor.value?.commands.setContent(value, {emitUpdate: false}) + editor.value?.commands.setContent(value, { + ...defaultSetContentOptions, + emitUpdate: false, + }) }