diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index b8d9c17fe1..721c19ba74 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -339,6 +339,38 @@ let jsonValue = ''; let mdValue = ''; + const serializeContent = (richText: boolean) => { + if (!editor) return; + + htmlValue = editor.getHTML(); + jsonValue = editor.getJSON(); + + if (richText) { + mdValue = turndownService + .turndown( + htmlValue + .replace(/

<\/p>/g, '
') + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) + ) + .replace(/\u00a0/g, ' '); + } else { + mdValue = turndownService + .turndown( + htmlValue + // Replace empty paragraphs with line breaks + .replace(/

<\/p>/g, '
') + // Replace multiple spaces with non-breaking spaces + .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) + // Replace tabs with non-breaking spaces (preserve indentation) + .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces + ) + // Convert non-breaking spaces back to regular spaces for markdown + .replace(/\u00a0/g, ' '); + } + }; + + $: serializeContent(richText); + let provider: SocketIOCollaborationProvider | null = null; let floatingMenuElement: Element | null = null; @@ -936,7 +968,7 @@ ], content: provider ? undefined : content, autofocus: messageInput && !$showCallOverlay, - onTransaction: () => { + onTransaction: ({ transaction }) => { if (!editor) return; // Defer Svelte reactivity trigger to rAF so we don't interleave @@ -950,30 +982,9 @@ }); } - htmlValue = editor.getHTML(); - jsonValue = editor.getJSON(); - - if (richText) { - mdValue = turndownService - .turndown( - htmlValue - .replace(/

<\/p>/g, '
') - .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) - ) - .replace(/\u00a0/g, ' '); - } else { - mdValue = turndownService - .turndown( - htmlValue - // Replace empty paragraphs with line breaks - .replace(/

<\/p>/g, '
') - // Replace multiple spaces with non-breaking spaces - .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) - // Replace tabs with non-breaking spaces (preserve indentation) - .replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces - ) - // Convert non-breaking spaces back to regular spaces for markdown - .replace(/\u00a0/g, ' '); + // Compare the final document to include changes from appended transactions. + if (jsonValue === '' || transaction.before !== editor.state.doc) { + serializeContent(richText); } onChange({ @@ -988,12 +999,8 @@ if (raw) { value = htmlValue; } else { - if (!preserveBreaks) { - mdValue = mdValue.replace(//g, ''); - } - - if (value !== mdValue) { - value = mdValue; + if (value !== (preserveBreaks ? mdValue : mdValue.replace(//g, ''))) { + value = preserveBreaks ? mdValue : mdValue.replace(//g, ''); // check if the node is paragraph as well if (editor.isActive('paragraph')) { @@ -1346,7 +1353,9 @@ const onValueChange = () => { if (!editor) return; - const jsonValue = editor.getJSON(); + if (value !== '' && value === jsonValue) return; + + const editorJsonValue = editor.getJSON(); const htmlValue = editor.getHTML(); let mdValue = turndownService .turndown( @@ -1365,7 +1374,7 @@ } if (json) { - if (!equalEditorJSON(value, jsonValue)) { + if (!equalEditorJSON(value, editorJsonValue)) { editor.commands.setContent(value); selectTemplate(); }