diff --git a/packages/app-mobile/components/NoteEditor/RichTextEditor.test.tsx b/packages/app-mobile/components/NoteEditor/RichTextEditor.test.tsx index 818b41458e..4a179b8b29 100644 --- a/packages/app-mobile/components/NoteEditor/RichTextEditor.test.tsx +++ b/packages/app-mobile/components/NoteEditor/RichTextEditor.test.tsx @@ -173,6 +173,21 @@ describe('RichTextEditor', () => { }); }); + it('should save repeated spaces using nonbreaking spaces', async () => { + let body = 'Test'; + render( { body = newBody; }} + />); + + const window = await getEditorWindow(); + mockTyping(window, ' test'); + + await waitFor(async () => { + expect(body.trim()).toBe('Test \u00A0test'); + }); + }); + it('should render clickable checkboxes', async () => { let body = '- [ ] Test\n- [x] Another test'; render( imp resource = await Resource.save(resource, { isNew: true }); - const resourceTag = Resource.markupTag(resource); + const resourceTag = Resource.markupTag(resource, this.state.note.markup_language); const newNote = await this.insertText(resourceTag, { newLine: true }); void this.refreshResource(resource, newNote.body); diff --git a/packages/editor/ProseMirror/plugins/originalMarkupPlugin.ts b/packages/editor/ProseMirror/plugins/originalMarkupPlugin.ts index 2f82e0e225..fff7a6b8d3 100644 --- a/packages/editor/ProseMirror/plugins/originalMarkupPlugin.ts +++ b/packages/editor/ProseMirror/plugins/originalMarkupPlugin.ts @@ -4,6 +4,8 @@ import { Decoration, DecorationSet } from 'prosemirror-view'; import schema from '../schema'; import changedDescendants from '../vendor/changedDescendants'; +const nonbreakingSpace = '\u00A0'; + // Creates a custom serializer that can preserve empty paragraphs. // See https://discuss.prosemirror.net/t/how-to-preserve-br-tags-in-empty-paragraphs/2051/8. const createSerializer = () => { @@ -21,10 +23,10 @@ const createSerializer = () => { return result; }, - // Prevent empty paragraphs from being removed by padding them with  s: + // Prevent empty paragraphs from being removed by padding them with nonbreaking spaces: paragraph: (node) => { if (node.content.size === 0) { - return ['p', ' ']; + return ['p', nonbreakingSpace]; } else { return ['p', 0]; } @@ -39,7 +41,8 @@ const createSerializer = () => { } // Replace repeated spaces with a space followed by a nonbreaking space: - return node.text.replace(/ {2}/g, '  '); + // Use \u00A0 as the nonbreaking space character, since   will be escaped. + return node.text.replace(/ {2}/g, ` ${nonbreakingSpace}`); }, // However,  s don't render as nonbreaking spaces in code blocks. @@ -76,7 +79,8 @@ const originalMarkupPlugin = (htmlToMarkup: (html: Node)=> string) => { }); if (matchingDecorations.length === 0) { - const markup = htmlToMarkup(proseMirrorSerializer.serializeNode(node)); + const serialized = proseMirrorSerializer.serializeNode(node); + const markup = htmlToMarkup(serialized); decorations = decorations.add(doc, [makeDecoration(position, node, markup)]); } diff --git a/packages/editor/ProseMirror/styles/prosemirror-editor.css b/packages/editor/ProseMirror/styles/prosemirror-editor.css index 8fd9bdb216..e370c949b1 100644 --- a/packages/editor/ProseMirror/styles/prosemirror-editor.css +++ b/packages/editor/ProseMirror/styles/prosemirror-editor.css @@ -3,4 +3,6 @@ outline: none; padding-left: 15px; padding-right: 15px; + + color: var(--joplin-color); }