diff --git a/package.json b/package.json index 3f0daaf..b99e9f6 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "fractional-indexing-jittered": "^0.9.1", "ioredis": "^5.4.1", "jszip": "^3.10.1", + "linkifyjs": "^4.2.0", "marked": "^13.0.3", "uuid": "^11.0.3", "y-indexeddb": "^9.0.12", diff --git a/packages/editor-ext/src/lib/link.ts b/packages/editor-ext/src/lib/link.ts index 7aa4aac..a9b68ec 100644 --- a/packages/editor-ext/src/lib/link.ts +++ b/packages/editor-ext/src/lib/link.ts @@ -10,11 +10,35 @@ export const LinkExtension = TiptapLink.extend({ return [ { tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])', + getAttrs: (element) => { + if ( + element + .getAttribute("href") + ?.toLowerCase() + .startsWith("javascript:") + ) { + return false; + } + + return null; + }, }, ]; }, renderHTML({ HTMLAttributes }) { + if (HTMLAttributes.href?.toLowerCase().startsWith("javascript:")) { + return [ + "a", + mergeAttributes( + this.options.HTMLAttributes, + { ...HTMLAttributes, href: "" }, + { class: "link" }, + ), + 0, + ]; + } + return [ "a", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { diff --git a/packages/editor-ext/src/lib/markdown/markdown-clipboard.ts b/packages/editor-ext/src/lib/markdown/markdown-clipboard.ts index 764daf0..f3e0c74 100644 --- a/packages/editor-ext/src/lib/markdown/markdown-clipboard.ts +++ b/packages/editor-ext/src/lib/markdown/markdown-clipboard.ts @@ -3,9 +3,15 @@ import { Extension } from "@tiptap/core"; import { Plugin, PluginKey } from "@tiptap/pm/state"; import { DOMParser } from "@tiptap/pm/model"; import { markdownToHtml } from "./utils/marked.utils"; +import { find } from "linkifyjs"; export const MarkdownClipboard = Extension.create({ name: "markdownClipboard", + priority: 50, + +export const MarkdownClipboard = Extension.create({ + name: "markdownClipboard", + addOptions() { return { transformPastedText: false, @@ -17,9 +23,17 @@ export const MarkdownClipboard = Extension.create({ key: new PluginKey("markdownClipboard"), props: { clipboardTextParser: (text, context, plainText) => { - if (plainText || !this.options.transformPastedText) { - return null; // pasting with shift key prevents formatting + + const link = find(text, { + defaultProtocol: "http", + }).find((item) => item.isLink && item.value === text); + + if (plainText || !this.options.transformPastedText || link) { + // don't parse plaintext link to allow link paste handler to work + // pasting with shift key prevents formatting + return null; } + const parsed = markdownToHtml(text); return DOMParser.fromSchema(this.editor.schema).parseSlice( elementFromString(parsed), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 581be32..0bf60c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,6 +160,9 @@ importers: jszip: specifier: ^3.10.1 version: 3.10.1 + linkifyjs: + specifier: ^4.2.0 + version: 4.2.0 marked: specifier: ^13.0.3 version: 13.0.3 @@ -6459,8 +6462,8 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - linkifyjs@4.1.3: - resolution: {integrity: sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==} + linkifyjs@4.2.0: + resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==} loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} @@ -12349,7 +12352,7 @@ snapshots: dependencies: '@tiptap/core': 2.10.3(@tiptap/pm@2.10.3) '@tiptap/pm': 2.10.3 - linkifyjs: 4.1.3 + linkifyjs: 4.2.0 '@tiptap/extension-list-item@2.10.3(@tiptap/core@2.10.3(@tiptap/pm@2.10.3))': dependencies: @@ -15922,7 +15925,7 @@ snapshots: dependencies: uc.micro: 2.1.0 - linkifyjs@4.1.3: {} + linkifyjs@4.2.0: {} loader-runner@4.3.0: {}