From a6b3e1e769079d4049c21ace9cf152f85fd5fa74 Mon Sep 17 00:00:00 2001 From: Alec Gibson <12036746+alecgibson@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:35:53 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Run=20clipboard=20matchers=20aga?= =?UTF-8?q?inst=20plain=20text=20pastes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a reimplementation of https://github.com/quilljs/quill/pull/3530 At the moment, if the clipboard pastes `text/plain` content and no `text/html` content, the `Clipboard.convert()` function will completely skip the matching logic. This is surprising when registering text node clipboard matchers. This change updates the `convert()` function to match the plain text against the plain text matchers, just like we do with HTML. Note that these matchers will run _before_ applying the formats for ["paste and match style"][1], so they won't match an element matchers for the target formatting (which I think should be expected anyway). [1]: https://github.com/quilljs/quill/pull/3927 --- packages/quill/src/modules/clipboard.ts | 14 ++++++++++- .../quill/test/unit/modules/clipboard.spec.ts | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/quill/src/modules/clipboard.ts b/packages/quill/src/modules/clipboard.ts index 4f7243e298..509a22fd33 100644 --- a/packages/quill/src/modules/clipboard.ts +++ b/packages/quill/src/modules/clipboard.ts @@ -106,7 +106,8 @@ class Clipboard extends Module { }); } if (!html) { - return new Delta().insert(text || '', formats); + const converted = this.convertText(text || ''); + return converted.compose(new Delta().retain(converted.length(), formats)); } const delta = this.convertHTML(html); // Remove trailing newline @@ -123,8 +124,19 @@ class Clipboard extends Module { normalizeExternalHTML(doc); } + protected convertText(text: string) { + const doc = new DOMParser().parseFromString('', 'text/html'); + const node = doc.createTextNode(text); + doc.body.appendChild(node); + return this.convertDoc(doc); + } + protected convertHTML(html: string) { const doc = new DOMParser().parseFromString(html, 'text/html'); + return this.convertDoc(doc); + } + + private convertDoc(doc: Document) { this.normalizeHTML(doc); const container = doc.body; const nodeMatches = new WeakMap(); diff --git a/packages/quill/test/unit/modules/clipboard.spec.ts b/packages/quill/test/unit/modules/clipboard.spec.ts index 35c5ec8b67..fbd8b09d91 100644 --- a/packages/quill/test/unit/modules/clipboard.spec.ts +++ b/packages/quill/test/unit/modules/clipboard.spec.ts @@ -522,6 +522,31 @@ describe('Clipboard', () => { expect(delta).toEqual(expected); }); + test('runs a text node matcher on a plain text paste', function () { + const clipboard = createClipboard(); + clipboard.addMatcher(Node.TEXT_NODE, (node, delta) => { + let index = 0; + const regex = /https?:\/\/[^\s]+/g; + let match = null; + const composer = new Delta(); + // eslint-disable-next-line no-cond-assign + while ((match = regex.exec((node as Text).data))) { + composer.retain(match.index - index); + index = regex.lastIndex; + composer.retain(match[0].length, { link: match[0] }); + } + return delta.compose(composer); + }); + const delta = clipboard.convert({ + text: 'http://github.com https://quilljs.com', + }); + const expected = new Delta() + .insert('http://github.com', { link: 'http://github.com' }) + .insert(' ') + .insert('https://quilljs.com', { link: 'https://quilljs.com' }); + expect(delta).toEqual(expected); + }); + test('does not execute javascript', () => { // @ts-expect-error window.unsafeFunction = vitest.fn();