From a3d3886c5110b7caff7e214b59acbc002f697bb0 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Sat, 26 Aug 2023 19:24:29 +0500 Subject: [PATCH] editor: refactor clipboard extension --- packages/editor/package-lock.json | 11 -- packages/editor/package.json | 1 - .../clipboard-text-serializer/index.ts | 152 ------------------ .../clipboard/clipboard-dom-parser.ts} | 54 +++---- .../clipboard/clipboard-dom-serializer.ts | 57 +++++++ .../clipboard/clipboard-text-parser.ts | 60 +++++++ .../clipboard/clipboard-text-serializer.ts | 69 ++++++++ .../src/extensions/clipboard/clipboard.ts | 52 +++++- .../editor/src/extensions/clipboard/index.ts | 4 +- .../clipboard-dom-parser.test.ts.snap | 69 ++++++++ .../clipboard-text-serializer.test.ts.snap | 0 .../tests/clipboard-dom-parser.test.ts} | 8 +- .../tests/clipboard-text-serializer.test.ts | 16 +- .../__snapshots__/code-block.test.ts.snap | 7 +- .../code-block/tests/code-block.test.ts | 5 +- packages/editor/src/index.ts | 8 +- .../tests/__snapshots__/html.test.ts.snap | 45 ------ 17 files changed, 351 insertions(+), 267 deletions(-) delete mode 100644 packages/editor/src/extensions/clipboard-text-serializer/index.ts rename packages/editor/src/{utils/html.ts => extensions/clipboard/clipboard-dom-parser.ts} (69%) create mode 100644 packages/editor/src/extensions/clipboard/clipboard-dom-serializer.ts create mode 100644 packages/editor/src/extensions/clipboard/clipboard-text-parser.ts create mode 100644 packages/editor/src/extensions/clipboard/clipboard-text-serializer.ts create mode 100644 packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-dom-parser.test.ts.snap rename packages/editor/src/extensions/{clipboard-text-serializer => clipboard}/tests/__snapshots__/clipboard-text-serializer.test.ts.snap (100%) rename packages/editor/src/{utils/tests/html.test.ts => extensions/clipboard/tests/clipboard-dom-parser.test.ts} (90%) rename packages/editor/src/extensions/{clipboard-text-serializer => clipboard}/tests/clipboard-text-serializer.test.ts (96%) delete mode 100644 packages/editor/src/utils/tests/__snapshots__/html.test.ts.snap diff --git a/packages/editor/package-lock.json b/packages/editor/package-lock.json index a679acf86..85f66a2f7 100644 --- a/packages/editor/package-lock.json +++ b/packages/editor/package-lock.json @@ -38,7 +38,6 @@ "@tiptap/extension-underline": "2.0.3", "@tiptap/pm": "2.0.3", "@tiptap/starter-kit": "2.0.3", - "clipboard-polyfill": "4.0.0", "detect-indent": "^7.0.0", "entities": "^4.5.0", "katex": "0.16.4", @@ -1910,11 +1909,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/clipboard-polyfill": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/clipboard-polyfill/-/clipboard-polyfill-4.0.0.tgz", - "integrity": "sha512-U4KPNJqAYuyOtixCZZUyWTcj+wlI66j07g5ggMRE2DR1VFu/3ZWXkjLAslmme8i065gBSCUblHET7DKQ2Xg3RA==" - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5098,11 +5092,6 @@ } } }, - "clipboard-polyfill": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/clipboard-polyfill/-/clipboard-polyfill-4.0.0.tgz", - "integrity": "sha512-U4KPNJqAYuyOtixCZZUyWTcj+wlI66j07g5ggMRE2DR1VFu/3ZWXkjLAslmme8i065gBSCUblHET7DKQ2Xg3RA==" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", diff --git a/packages/editor/package.json b/packages/editor/package.json index f0f1fa42b..991cd259f 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -33,7 +33,6 @@ "@tiptap/extension-underline": "2.0.3", "@tiptap/pm": "2.0.3", "@tiptap/starter-kit": "2.0.3", - "clipboard-polyfill": "4.0.0", "detect-indent": "^7.0.0", "entities": "^4.5.0", "katex": "0.16.4", diff --git a/packages/editor/src/extensions/clipboard-text-serializer/index.ts b/packages/editor/src/extensions/clipboard-text-serializer/index.ts deleted file mode 100644 index 01daa05b9..000000000 --- a/packages/editor/src/extensions/clipboard-text-serializer/index.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* -This file is part of the Notesnook project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -import { Extension, TextSerializer } from "@tiptap/core"; -import { Plugin, PluginKey } from "prosemirror-state"; -import { Fragment, Schema, Slice } from "prosemirror-model"; -import { ListItem } from "../list-item"; -import { LIST_NODE_TYPES } from "../../utils/node-types"; -import { - DOMSerializer, - DOMParser as ProsemirrorDOMParser -} from "@tiptap/pm/model"; -import { convertTextToHTML } from "../../utils/html"; - -export class ClipboardDOMSerializer extends DOMSerializer { - static fromSchema(schema: Schema): ClipboardDOMSerializer { - return ( - schema.cached.domSerializer2 || - (schema.cached.domSerializer2 = new ClipboardDOMSerializer( - this.nodesFromSchema(schema), - this.marksFromSchema(schema) - )) - ); - } - - serializeFragment( - fragment: Fragment, - options?: { document?: Document | undefined } | undefined, - target?: HTMLElement | DocumentFragment | undefined - ): HTMLElement | DocumentFragment { - const dom = super.serializeFragment(fragment, options, target); - for (const p of dom.querySelectorAll("li > p")) { - if (p.parentElement && p.parentElement.childElementCount > 1) continue; - p.parentElement?.append(...p.childNodes); - p.remove(); - } - - for (const p of dom.querySelectorAll('p[data-spacing="single"]')) { - if (!p.previousElementSibling || p.previousElementSibling.tagName !== "P") - continue; - if (p.previousElementSibling.childNodes.length > 0) - p.previousElementSibling.appendChild(document.createElement("br")); - p.previousElementSibling.append(...p.childNodes); - p.remove(); - } - - return dom; - } -} - -export const ClipboardTextSerializer = Extension.create({ - name: "clipboardTextSerializer", - - addProseMirrorPlugins() { - return [ - new Plugin({ - key: new PluginKey("clipboardTextSerializer"), - props: { - transformCopied, - clipboardSerializer: ClipboardDOMSerializer.fromSchema( - this.editor.view.state.schema - ), - clipboardTextParser: (text) => { - const doc = new DOMParser().parseFromString( - convertTextToHTML(text), - "text/html" - ); - return ProsemirrorDOMParser.fromSchema( - this.editor.view.state.schema - ).parseSlice(doc, { preserveWhitespace: "full" }); - }, - clipboardTextSerializer: (content, view) => { - return getTextBetween(content, view.state.schema); - } - } - }) - ]; - } -}); - -export function transformCopied(slice: Slice) { - // when copying a single list item, we shouldn't retain the - // list formatting but copy it as a paragraph. - const maybeList = slice.content.firstChild; - if ( - maybeList && - LIST_NODE_TYPES.includes(maybeList.type.name) && - maybeList.childCount === 1 && - maybeList.firstChild - ) { - return transformCopied(new Slice(maybeList.firstChild.content, 0, 0)); - } - return slice; -} - -export function getTextBetween(slice: Slice, schema: Schema): string { - const range = { from: 0, to: slice.size }; - const separator = "\n"; - let text = ""; - let separated = true; - - slice.content.nodesBetween(0, slice.size, (node, pos, parent, index) => { - const textSerializer = schema.nodes[node.type.name]?.spec - .toText as TextSerializer; - - if (textSerializer) { - if (node.isBlock && !separated) { - text += separator; - separated = true; - } - - if (parent) { - text += textSerializer({ - node, - pos, - parent, - index, - range - }); - } - } else if (node.isText) { - text += node?.text; - separated = false; - } else if (node.isBlock && !!text) { - // we don't want double spaced list items when pasting - if (index === 0 && parent?.type.name === ListItem.name) return; - - text += separator; - if (node.attrs.spacing === "double" && node.childCount > 0) - text += separator; - separated = true; - } - }); - - return text; -} diff --git a/packages/editor/src/utils/html.ts b/packages/editor/src/extensions/clipboard/clipboard-dom-parser.ts similarity index 69% rename from packages/editor/src/utils/html.ts rename to packages/editor/src/extensions/clipboard/clipboard-dom-parser.ts index dd7dbaa11..be03868db 100644 --- a/packages/editor/src/utils/html.ts +++ b/packages/editor/src/extensions/clipboard/clipboard-dom-parser.ts @@ -17,11 +17,32 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { encodeNonAsciiHTML } from "entities"; +import { + DOMParser as ProsemirrorDOMParser, + ParseOptions +} from "@tiptap/pm/model"; +import { Schema, Slice } from "prosemirror-model"; -export function convertBrToParagraph(html: string) { - const doc = new DOMParser().parseFromString(html, "text/html"); - for (const br of doc.querySelectorAll("br")) { +export class ClipboardDOMParser extends ProsemirrorDOMParser { + static fromSchema(schema: Schema): ClipboardDOMParser { + return ( + (schema.cached.clipboardDomParser as ClipboardDOMParser) || + (schema.cached.clipboardDomParser = new ClipboardDOMParser( + schema, + (ProsemirrorDOMParser as any).schemaRules(schema) + )) + ); + } + + parseSlice(dom: Node, options?: ParseOptions | undefined): Slice { + convertBrToSingleSpacedParagraphs(dom); + return super.parseSlice(dom, options); + } +} + +export function convertBrToSingleSpacedParagraphs(dom: Node) { + if (!(dom instanceof HTMLElement)) return; + for (const br of dom.querySelectorAll("br")) { let paragraph = br.closest("p"); // if no paragraph is found over the br, we add one. @@ -37,14 +58,13 @@ export function convertBrToParagraph(html: string) { if (paragraph) { splitOn(paragraph, br); const children = Array.from(paragraph.childNodes.values()); - const newParagraph = doc.createElement("p"); + const newParagraph = document.createElement("p"); newParagraph.dataset.spacing = "single"; newParagraph.append(...children.slice(children.indexOf(br) + 1)); paragraph.insertAdjacentElement("afterend", newParagraph); br.remove(); } } - return doc; } function splitOn(bound: Element, cutElement: Element) { @@ -63,25 +83,3 @@ function splitOn(bound: Element, cutElement: Element) { } } } - -export function convertTextToHTML(src: string) { - return src - .split(/[\r\n]/) - .map((line) => - line - ? `

${encodeLine(line)}

` - : `

` - ) - .join(""); -} - -function encodeLine(line: string) { - line = encodeNonAsciiHTML(line); - line = line.replace(/(^ +)|( {2,})/g, (sub, ...args) => { - const [starting, inline] = args; - if (starting) return " ".repeat(starting.length); - if (inline) return " ".repeat(inline.length); - return sub; - }); - return line; -} diff --git a/packages/editor/src/extensions/clipboard/clipboard-dom-serializer.ts b/packages/editor/src/extensions/clipboard/clipboard-dom-serializer.ts new file mode 100644 index 000000000..45e1438f0 --- /dev/null +++ b/packages/editor/src/extensions/clipboard/clipboard-dom-serializer.ts @@ -0,0 +1,57 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { Fragment, Schema } from "prosemirror-model"; +import { DOMSerializer } from "@tiptap/pm/model"; + +export class ClipboardDOMSerializer extends DOMSerializer { + static fromSchema(schema: Schema): ClipboardDOMSerializer { + return ( + schema.cached.clipboardDomSerializer || + (schema.cached.clipboardDomSerializer = new ClipboardDOMSerializer( + this.nodesFromSchema(schema), + this.marksFromSchema(schema) + )) + ); + } + + serializeFragment( + fragment: Fragment, + options?: { document?: Document | undefined } | undefined, + target?: HTMLElement | DocumentFragment | undefined + ): HTMLElement | DocumentFragment { + const dom = super.serializeFragment(fragment, options, target); + for (const p of dom.querySelectorAll("li > p")) { + if (p.parentElement && p.parentElement.childElementCount > 1) continue; + p.parentElement?.append(...p.childNodes); + p.remove(); + } + + for (const p of dom.querySelectorAll('p[data-spacing="single"]')) { + if (!p.previousElementSibling || p.previousElementSibling.tagName !== "P") + continue; + if (p.previousElementSibling.childNodes.length > 0) + p.previousElementSibling.appendChild(document.createElement("br")); + p.previousElementSibling.append(...p.childNodes); + p.remove(); + } + + return dom; + } +} diff --git a/packages/editor/src/extensions/clipboard/clipboard-text-parser.ts b/packages/editor/src/extensions/clipboard/clipboard-text-parser.ts new file mode 100644 index 000000000..44b76de4a --- /dev/null +++ b/packages/editor/src/extensions/clipboard/clipboard-text-parser.ts @@ -0,0 +1,60 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { ResolvedPos, Slice } from "@tiptap/pm/model"; +import { encodeNonAsciiHTML } from "entities"; +import { ClipboardDOMParser } from "./clipboard-dom-parser"; +import { EditorView } from "@tiptap/pm/view"; + +export function clipboardTextParser( + text: string, + _$context: ResolvedPos, + _plain: boolean, + view: EditorView +): Slice { + const doc = new DOMParser().parseFromString( + convertTextToHTML(text), + "text/html" + ); + return ClipboardDOMParser.fromSchema(view.state.schema).parseSlice(doc, { + preserveWhitespace: "full" + }); +} + +export function convertTextToHTML(src: string) { + return src + .split(/[\r\n]/) + .map((line) => + line + ? `

${encodeLine(line)}

` + : `

` + ) + .join(""); +} + +function encodeLine(line: string) { + line = encodeNonAsciiHTML(line); + line = line.replace(/(^ +)|( {2,})/g, (sub, ...args) => { + const [starting, inline] = args; + if (starting) return " ".repeat(starting.length); + if (inline) return " ".repeat(inline.length); + return sub; + }); + return line; +} diff --git a/packages/editor/src/extensions/clipboard/clipboard-text-serializer.ts b/packages/editor/src/extensions/clipboard/clipboard-text-serializer.ts new file mode 100644 index 000000000..058fc1157 --- /dev/null +++ b/packages/editor/src/extensions/clipboard/clipboard-text-serializer.ts @@ -0,0 +1,69 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { TextSerializer } from "@tiptap/core"; +import { Schema, Slice } from "prosemirror-model"; +import { ListItem } from "../list-item"; +import { EditorView } from "@tiptap/pm/view"; + +export function clipboardTextSerializer(content: Slice, view: EditorView) { + return getTextBetween(content, view.state.schema); +} + +export function getTextBetween(slice: Slice, schema: Schema): string { + const range = { from: 0, to: slice.size }; + const separator = "\n"; + let text = ""; + let separated = true; + + slice.content.nodesBetween(0, slice.size, (node, pos, parent, index) => { + const textSerializer = schema.nodes[node.type.name]?.spec + .toText as TextSerializer; + + if (textSerializer) { + if (node.isBlock && !separated) { + text += separator; + separated = true; + } + + if (parent) { + text += textSerializer({ + node, + pos, + parent, + index, + range + }); + } + } else if (node.isText) { + text += node?.text; + separated = false; + } else if (node.isBlock && !!text) { + // we don't want double spaced list items when pasting + if (index === 0 && parent?.type.name === ListItem.name) return; + + text += separator; + if (node.attrs.spacing === "double" && node.childCount > 0) + text += separator; + separated = true; + } + }); + + return text; +} diff --git a/packages/editor/src/extensions/clipboard/clipboard.ts b/packages/editor/src/extensions/clipboard/clipboard.ts index 9530d1be4..ddc5a5243 100644 --- a/packages/editor/src/extensions/clipboard/clipboard.ts +++ b/packages/editor/src/extensions/clipboard/clipboard.ts @@ -16,8 +16,15 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + import { Extension } from "@tiptap/core"; -import { writeText } from "clipboard-polyfill"; +import { Plugin, PluginKey } from "prosemirror-state"; +import { Slice } from "prosemirror-model"; +import { LIST_NODE_TYPES } from "../../utils/node-types"; +import { ClipboardDOMParser } from "./clipboard-dom-parser"; +import { ClipboardDOMSerializer } from "./clipboard-dom-serializer"; +import { clipboardTextParser } from "./clipboard-text-parser"; +import { clipboardTextSerializer } from "./clipboard-text-serializer"; declare module "@tiptap/core" { interface Commands { @@ -31,14 +38,15 @@ export type ClipboardOptions = { copyToClipboard: (text: string) => void; }; -export const Clipboard = Extension.create({ +export const Clipboard = Extension.create({ + name: "clipboard", + addOptions() { return { - copyToClipboard: (text) => { - writeText(text); - } + copyToClipboard: () => {} }; }, + addCommands() { return { copyToClipboard: (text: string) => (props) => { @@ -46,5 +54,39 @@ export const Clipboard = Extension.create({ return true; } }; + }, + + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey("clipboard"), + props: { + clipboardParser: ClipboardDOMParser.fromSchema( + this.editor.view.state.schema + ), + clipboardSerializer: ClipboardDOMSerializer.fromSchema( + this.editor.view.state.schema + ), + transformCopied, + clipboardTextParser, + clipboardTextSerializer + } + }) + ]; } }); + +export function transformCopied(slice: Slice) { + // when copying a single list item, we shouldn't retain the + // list formatting but copy it as a paragraph. + const maybeList = slice.content.firstChild; + if ( + maybeList && + LIST_NODE_TYPES.includes(maybeList.type.name) && + maybeList.childCount === 1 && + maybeList.firstChild + ) { + return transformCopied(new Slice(maybeList.firstChild.content, 0, 0)); + } + return slice; +} diff --git a/packages/editor/src/extensions/clipboard/index.ts b/packages/editor/src/extensions/clipboard/index.ts index 88adc7417..72e41804c 100644 --- a/packages/editor/src/extensions/clipboard/index.ts +++ b/packages/editor/src/extensions/clipboard/index.ts @@ -16,8 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { Clipboard } from "./clipboard"; export * from "./clipboard"; - -export default Clipboard; +export { Clipboard as default } from "./clipboard"; diff --git a/packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-dom-parser.test.ts.snap b/packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-dom-parser.test.ts.snap new file mode 100644 index 000000000..d7f1ebb76 --- /dev/null +++ b/packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-dom-parser.test.ts.snap @@ -0,0 +1,69 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`convert br tags to paragraphs ( + +A troll, they call me, but I have no wish
+to be associated with those dolls
+
+We lack religion, purpose, politics,
+and yet, we somehow manage to get by.
+ +) 1`] = ` +"A troll, they call me, but I have no wish
+to be associated with those dolls
+
+We lack religion, purpose, politics,
+and yet, we somehow manage to get by.
" +`; + +exports[`convert br tags to paragraphs ( +

When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.
+ Also when I share the selection to Notesnook via the share functionality from Android, I have the same issue.

+
+

Device information:
+ App version: 2.3.0
+ Platform: android
+ Model: OnePlus-CPH2409-31
+ Pro: true
+ Logged in: yes

+ + ) 1`] = ` +"

When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.
+ Also when I share the selection to Notesnook via the share functionality from Android, I have the same issue.

+
+

Device information:
+ App version: 2.3.0
+ Platform: android
+ Model: OnePlus-CPH2409-31
+ Pro: true
+ Logged in: yes

" +`; + +exports[`convert br tags to paragraphs ( + Why switch from Gmail? + + Not sacrificing features for more privacy, prefer using one app, in many public groups and channels (Telegram) + + LibreOffice Slow & buggy + + Switched to Brave for the better Android app, more private out of the box & unsure if uBlock Origin closes gap + + ) 1`] = ` +"Why switch from Gmail? + + Not sacrificing features for more privacy, prefer using one app, in many public groups and channels (Telegram) + + LibreOffice Slow & buggy + + Switched to Brave for the better Android app, more private out of the box & unsure if uBlock Origin closes gap" +`; + +exports[`convert br tags to paragraphs (


) 1`] = `"


"`; + +exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1
line
2

"`; + +exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1
line
2

"`; + +exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1
line
2

"`; + +exports[`convert br tags to paragraphs (

line 1
line 2

) 1`] = `"

line 1
line 2

"`; diff --git a/packages/editor/src/extensions/clipboard-text-serializer/tests/__snapshots__/clipboard-text-serializer.test.ts.snap b/packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-text-serializer.test.ts.snap similarity index 100% rename from packages/editor/src/extensions/clipboard-text-serializer/tests/__snapshots__/clipboard-text-serializer.test.ts.snap rename to packages/editor/src/extensions/clipboard/tests/__snapshots__/clipboard-text-serializer.test.ts.snap diff --git a/packages/editor/src/utils/tests/html.test.ts b/packages/editor/src/extensions/clipboard/tests/clipboard-dom-parser.test.ts similarity index 90% rename from packages/editor/src/utils/tests/html.test.ts rename to packages/editor/src/extensions/clipboard/tests/clipboard-dom-parser.test.ts index 2fb22f45c..de7dc774a 100644 --- a/packages/editor/src/utils/tests/html.test.ts +++ b/packages/editor/src/extensions/clipboard/tests/clipboard-dom-parser.test.ts @@ -18,7 +18,7 @@ along with this program. If not, see . */ import { test } from "vitest"; -import { convertBrToParagraph } from "../html"; +import { convertBrToSingleSpacedParagraphs } from "../clipboard-dom-parser"; const cases = [ [`

line 1
line 2

`], @@ -68,8 +68,8 @@ and yet, we somehow manage to get by.
for (const testCase of cases) { const [html, expected] = testCase; test(`convert br tags to paragraphs (${testCase})`, (t) => { - t.expect( - convertBrToParagraph(html).body.innerHTML.trim() - ).toMatchSnapshot(); + const element = new DOMParser().parseFromString(html, "text/html"); + convertBrToSingleSpacedParagraphs(element); + t.expect(element.body.innerHTML.trim()).toMatchSnapshot(); }); } diff --git a/packages/editor/src/extensions/clipboard-text-serializer/tests/clipboard-text-serializer.test.ts b/packages/editor/src/extensions/clipboard/tests/clipboard-text-serializer.test.ts similarity index 96% rename from packages/editor/src/extensions/clipboard-text-serializer/tests/clipboard-text-serializer.test.ts rename to packages/editor/src/extensions/clipboard/tests/clipboard-text-serializer.test.ts index 674700eaa..03629b8d3 100644 --- a/packages/editor/src/extensions/clipboard-text-serializer/tests/clipboard-text-serializer.test.ts +++ b/packages/editor/src/extensions/clipboard/tests/clipboard-text-serializer.test.ts @@ -21,12 +21,10 @@ import { test } from "vitest"; import { createEditor, h } from "../../../../test-utils"; import OrderedList from "../../ordered-list"; import { ListItem } from "../../list-item"; -import { - getTextBetween, - transformCopied, - ClipboardDOMSerializer -} from "../index"; +import { transformCopied } from "../index"; import { Paragraph } from "../../paragraph"; +import { ClipboardDOMSerializer } from "../clipboard-dom-serializer"; +import { clipboardTextSerializer } from "../clipboard-text-serializer"; test("copied list items shouldn't contain extra newlines", (t) => { const { editor } = createEditor({ @@ -59,9 +57,9 @@ test("copied list items shouldn't contain extra newlines", (t) => { ); t.expect( - getTextBetween( + clipboardTextSerializer( editor.state.doc.slice(0, editor.state.doc.nodeSize - 2), - editor.schema + editor.view ) ).toBe(`This is line: number 1. And this is line number 2. @@ -194,9 +192,9 @@ for (const testCase of paragraphTestCases) { ).toBe(testCase.expectedHtml); t.expect( - getTextBetween( + clipboardTextSerializer( editor.state.doc.slice(0, editor.state.doc.nodeSize - 2), - editor.schema + editor.view ) ).toBe(testCase.expectedText); }); diff --git a/packages/editor/src/extensions/code-block/tests/__snapshots__/code-block.test.ts.snap b/packages/editor/src/extensions/code-block/tests/__snapshots__/code-block.test.ts.snap index d4461a057..db97a3165 100644 --- a/packages/editor/src/extensions/code-block/tests/__snapshots__/code-block.test.ts.snap +++ b/packages/editor/src/extensions/code-block/tests/__snapshots__/code-block.test.ts.snap @@ -10,4 +10,9 @@ exports[`codeblocks should not be updated if other content is changed 1`] = `"
function hello() { }
function hello() { }const hello0 = 0;const hello1 = 1;const hello2 = 2;const hello3 = 3;const hello4 = 4;const hello5 = 5;const hello6 = 6;const hello7 = 7;const hello8 = 8;const hello9 = 9;const hello10 = 10;const hello11 = 11;const hello12 = 12;const hello13 = 13;const hello14 = 14;const hello15 = 15;const hello16 = 16;const hello17 = 17;const hello18 = 18;const hello19 = 19;const hello20 = 20;const hello21 = 21;const hello22 = 22;const hello23 = 23;const hello24 = 24;const hello25 = 25;const hello26 = 26;const hello27 = 27;const hello28 = 28;const hello29 = 29;const hello30 = 30;const hello31 = 31;const hello32 = 32;const hello33 = 33;const hello34 = 34;const hello35 = 35;const hello36 = 36;const hello37 = 37;const hello38 = 38;const hello39 = 39;const hello40 = 40;const hello41 = 41;const hello42 = 42;const hello43 = 43;const hello44 = 44;const hello45 = 45;const hello46 = 46;const hello47 = 47;const hello48 = 48;const hello49 = 49;const hello50 = 50;const hello51 = 51;const hello52 = 52;const hello53 = 53;const hello54 = 54;const hello55 = 55;const hello56 = 56;const hello57 = 57;const hello58 = 58;const hello59 = 59;const hello60 = 60;const hello61 = 61;const hello62 = 62;const hello63 = 63;const hello64 = 64;const hello65 = 65;const hello66 = 66;const hello67 = 67;const hello68 = 68;const hello69 = 69;const hello70 = 70;const hello71 = 71;const hello72 = 72;const hello73 = 73;const hello74 = 74;const hello75 = 75;const hello76 = 76;const hello77 = 77;const hello78 = 78;const hello79 = 79;const hello80 = 80;const hello81 = 81;const hello82 = 82;const hello83 = 83;const hello84 = 84;const hello85 = 85;const hello86 = 86;const hello87 = 87;const hello88 = 88;const hello89 = 89;const hello90 = 90;const hello91 = 91;const hello92 = 92;const hello93 = 93;const hello94 = 94;const hello95 = 95;const hello96 = 96;const hello97 = 97;const hello98 = 98;const hello99 = 99;
"`; -exports[`pasting code from vscode should automatically create a syntax highlighted codeblock 1`] = `"
function hello() { }
"`; +exports[`pasting code from vscode should automatically create a syntax highlighted codeblock 1`] = ` +"
function hello()
+{
+  const world = \\"hello\\";
+}
" +`; diff --git a/packages/editor/src/extensions/code-block/tests/code-block.test.ts b/packages/editor/src/extensions/code-block/tests/code-block.test.ts index 7138b1fd9..42f3eec05 100644 --- a/packages/editor/src/extensions/code-block/tests/code-block.test.ts +++ b/packages/editor/src/extensions/code-block/tests/code-block.test.ts @@ -122,7 +122,10 @@ test("pasting code from vscode should automatically create a syntax highlighted (clipboardEvent as unknown as any)["clipboardData"] = { getData: (type: string) => type === "text/plain" - ? "function hello() { }" + ? `function hello() +{ + const world = "hello"; +}` : type === "vscode-editor-data" ? JSON.stringify({ mode: "javascript" }) : undefined diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 1672cc183..0e21a59d2 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -40,7 +40,6 @@ import { useEffect, useMemo } from "react"; import "./extensions"; import { AttachmentNode, AttachmentOptions } from "./extensions/attachment"; import BulletList from "./extensions/bullet-list"; -import { ClipboardTextSerializer } from "./extensions/clipboard-text-serializer"; import { CodeBlock } from "./extensions/code-block"; import { Codemark } from "./extensions/code-mark"; import { DateTime, DateTimeOptions } from "./extensions/date-time"; @@ -76,7 +75,6 @@ import { useToolbarStore } from "./toolbar/stores/toolbar-store"; import { DownloadOptions } from "./utils/downloader"; import { Heading } from "./extensions/heading"; import Clipboard, { ClipboardOptions } from "./extensions/clipboard"; -import { convertBrToParagraph } from "./utils/html"; import Blockquote from "./extensions/blockquote"; declare global { @@ -147,14 +145,10 @@ const useTiptap = ( () => ({ enableCoreExtensions: false, editorProps: { - ...editorProps, - transformPastedHTML(html) { - return convertBrToParagraph(html).documentElement.outerHTML; - } + ...editorProps }, extensions: [ ...CoreExtensions, - ClipboardTextSerializer, NodeViewSelectionNotifier, SearchReplace, TextStyle.extend({ diff --git a/packages/editor/src/utils/tests/__snapshots__/html.test.ts.snap b/packages/editor/src/utils/tests/__snapshots__/html.test.ts.snap deleted file mode 100644 index 73d3cbdbd..000000000 --- a/packages/editor/src/utils/tests/__snapshots__/html.test.ts.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`convert br tags to paragraphs ( - -A troll, they call me, but I have no wish
-to be associated with those dolls
-
-We lack religion, purpose, politics,
-and yet, we somehow manage to get by.
- -) 1`] = `"

A troll, they call me, but I have no wish

to be associated with those dolls

We lack religion, purpose, politics,

and yet, we somehow manage to get by.

"`; - -exports[`convert br tags to paragraphs ( -

When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.
- Also when I share the selection to Notesnook via the share functionality from Android, I have the same issue.

-
-

Device information:
- App version: 2.3.0
- Platform: android
- Model: OnePlus-CPH2409-31
- Pro: true
- Logged in: yes

- - ) 1`] = `"

When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.

Also when I share the selection to Notesnook via the share functionality from Android, I have the same issue.


Device information:

App version: 2.3.0

Platform: android

Model: OnePlus-CPH2409-31

Pro: true

Logged in: yes

"`; - -exports[`convert br tags to paragraphs ( - Why switch from Gmail? - - Not sacrificing features for more privacy, prefer using one app, in many public groups and channels (Telegram) - - LibreOffice Slow & buggy - - Switched to Brave for the better Android app, more private out of the box & unsure if uBlock Origin closes gap - - ) 1`] = `"

Why switch from Gmail?

Not sacrificing features for more privacy, prefer using one app, in many public groups and channels (Telegram)

LibreOffice Slow & buggy

Switched to Brave for the better Android app, more private out of the box & unsure if uBlock Origin closes gap

"`; - -exports[`convert br tags to paragraphs (


) 1`] = `"


"`; - -exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1

line 2

"`; - -exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1

line 2

"`; - -exports[`convert br tags to paragraphs (

line 1
line
2

) 1`] = `"

line 1

line 2

"`; - -exports[`convert br tags to paragraphs (

line 1
line 2

) 1`] = `"

line 1

line 2

"`;