diff --git a/packages/quill/src/modules/clipboard.ts b/packages/quill/src/modules/clipboard.ts index e4c3f755b5..8e35efd5c4 100644 --- a/packages/quill/src/modules/clipboard.ts +++ b/packages/quill/src/modules/clipboard.ts @@ -624,6 +624,9 @@ function matchTable( return delta; } +const NBSP = '\u00a0'; +const SPACE_EXCLUDE_NBSP = `[^\\S${NBSP}]`; + function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) { // @ts-expect-error let text = node.data; @@ -639,12 +642,8 @@ function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) { ) { return delta; } - const replacer = (collapse: unknown, match: string) => { - const replaced = match.replace(/[^\u00a0]/g, ''); // \u00a0 is nbsp; - return replaced.length < 1 && collapse ? ' ' : replaced; - }; text = text.replace(/\r\n/g, ' ').replace(/\n/g, ' '); - text = text.replace(/\s\s+/g, replacer.bind(replacer, true)); // collapse whitespace + text = text.replace(new RegExp(`${SPACE_EXCLUDE_NBSP}{2,}`, 'g'), ' '); // collapse whitespace if ( (node.previousSibling == null && node.parentElement != null && @@ -652,7 +651,7 @@ function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) { (node.previousSibling instanceof Element && isLine(node.previousSibling, scroll)) ) { - text = text.replace(/^\s+/, replacer.bind(replacer, false)); + text = text.replace(new RegExp(`^${SPACE_EXCLUDE_NBSP}+`), ''); } if ( (node.nextSibling == null && @@ -660,8 +659,9 @@ function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) { isLine(node.parentElement, scroll)) || (node.nextSibling instanceof Element && isLine(node.nextSibling, scroll)) ) { - text = text.replace(/\s+$/, replacer.bind(replacer, false)); + text = text.replace(new RegExp(`${SPACE_EXCLUDE_NBSP}+$`), ''); } + text = text.replaceAll(NBSP, ' '); } return delta.insert(text); } diff --git a/packages/quill/test/unit/modules/clipboard.spec.ts b/packages/quill/test/unit/modules/clipboard.spec.ts index 0ba7c159ed..69142690a2 100644 --- a/packages/quill/test/unit/modules/clipboard.spec.ts +++ b/packages/quill/test/unit/modules/clipboard.spec.ts @@ -244,6 +244,12 @@ describe('Clipboard', () => { expect(delta).toEqual(new Delta().insert('0\n1 2 3 4\n5 6 7 8')); }); + test('multiple whitespaces', () => { + const html = '
1 2 3
'; + const delta = createClipboard().convert({ html }); + expect(delta).toEqual(new Delta().insert('1 2 3')); + }); + test('inline whitespace', () => { const html = '

0 1 2

'; const delta = createClipboard().convert({ html }); @@ -256,19 +262,23 @@ describe('Clipboard', () => { const html = '1 2'; const delta = createClipboard().convert({ html }); expect(delta).toEqual( - new Delta() - .insert('0\u00a0') - .insert('1', { bold: true }) - .insert('\u00a02'), + new Delta().insert('0 ').insert('1', { bold: true }).insert(' 2'), ); }); test('consecutive intentional whitespace', () => { const html = '  1  '; const delta = createClipboard().convert({ html }); - expect(delta).toEqual( - new Delta().insert('\u00a0\u00a01\u00a0\u00a0', { bold: true }), - ); + expect(delta).toEqual(new Delta().insert(' 1 ', { bold: true })); + }); + + test('intentional whitespace at line start/end', () => { + expect( + createClipboard().convert({ html: '

0  

  2

' }), + ).toEqual(new Delta().insert('0 \n 2')); + expect( + createClipboard().convert({ html: '

 2

' }), + ).toEqual(new Delta().insert('0 \n 2')); }); test('newlines between inline elements', () => {