From d309d538f5df201cf15f8740b1b4804979b104c7 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Fri, 10 Jun 2022 19:28:51 +0800 Subject: [PATCH] tiptap: fix paste link --- packages/client/package.json | 1 + .../src/tiptap/core/extensions/paste.ts | 49 +++++++++++++++---- packages/client/src/tiptap/prose-utils/url.ts | 13 ++++- pnpm-lock.yaml | 8 +-- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index eaef52c9..1c6c2047 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -73,6 +73,7 @@ "markdown-it-sup": "^1.0.0", "next": "12.1.0", "next-pwa": "^5.5.2", + "prosemirror-commands": "^1.3.0", "prosemirror-markdown": "^1.7.0", "prosemirror-model": "^1.16.1", "prosemirror-schema-list": "^1.1.6", diff --git a/packages/client/src/tiptap/core/extensions/paste.ts b/packages/client/src/tiptap/core/extensions/paste.ts index c4e05ebc..7456e651 100644 --- a/packages/client/src/tiptap/core/extensions/paste.ts +++ b/packages/client/src/tiptap/core/extensions/paste.ts @@ -1,5 +1,6 @@ import { Extension } from '@tiptap/core'; import { safeJSONParse } from 'helpers/json'; +import { toggleMark } from 'prosemirror-commands'; import { Fragment, Schema } from 'prosemirror-model'; import { Plugin, PluginKey } from 'prosemirror-state'; import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/core/constants'; @@ -9,6 +10,7 @@ import { isInCode, isMarkdown, isTitleNode, + isValidURL, LANGUAGES, normalizeMarkdown, } from 'tiptap/prose-utils'; @@ -76,7 +78,9 @@ export const Paste = Extension.create({ if (view.props.editable && !view.props.editable(view.state)) { return false; } + if (!event.clipboardData) return false; + // 文件 const files = Array.from(event.clipboardData.files); if (files.length) { @@ -87,19 +91,39 @@ export const Paste = Extension.create({ return true; } - const { markdownToProsemirror } = extensionThis.options; const text = event.clipboardData.getData('text/plain'); const html = event.clipboardData.getData('text/html'); const vscode = event.clipboardData.getData('vscode-editor-data'); const node = event.clipboardData.getData('text/node'); const markdownText = event.clipboardData.getData('text/markdown'); + const { state, dispatch } = view; + + const { markdownToProsemirror } = extensionThis.options; // 直接复制节点 if (node) { - const doc = safeJSONParse(node); + const json = safeJSONParse(node); const tr = view.state.tr; const selection = tr.selection; - view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(doc)).scrollIntoView()); + view.dispatch(tr.insert(selection.from - 1, view.state.schema.nodeFromJSON(json)).scrollIntoView()); + return true; + } + + // 链接 + if (isValidURL(text)) { + if (!state.selection.empty) { + toggleMark(this.editor.schema.marks.link, { href: text })(state, dispatch); + return true; + } + + const transaction = view.state.tr + .insertText(text, state.selection.from, state.selection.to) + .addMark( + state.selection.from, + state.selection.to + text.length, + state.schema.marks.link.create({ href: text }) + ); + view.dispatch(transaction); return true; } @@ -116,16 +140,21 @@ export const Paste = Extension.create({ if (pasteCodeLanguage && pasteCodeLanguage !== 'markdown') { event.preventDefault(); view.dispatch( - view.state.tr.replaceSelectionWith( - view.state.schema.nodes.codeBlock.create({ - language: Object.keys(LANGUAGES).includes(vscodeMeta.mode) ? vscodeMeta.mode : null, - }) - ) + view.state.tr + .replaceSelectionWith( + view.state.schema.nodes.codeBlock.create({ + language: Object.keys(LANGUAGES).includes(vscodeMeta.mode) ? vscodeMeta.mode : null, + }) + ) + .insertText(text) ); - view.dispatch(view.state.tr.insertText(text).scrollIntoView()); return true; } + if (html?.includes('data-pm-slice')) { + return false; + } + // 处理 markdown if (markdownText || isMarkdown(text) || html.length === 0 || pasteCodeLanguage === 'markdown') { event.preventDefault(); @@ -147,11 +176,13 @@ export const Paste = Extension.create({ view.dispatch(tr.scrollIntoView()); return true; } + if (text.length !== 0) { event.preventDefault(); view.dispatch(view.state.tr.insertText(text)); return true; } + return false; }, handleDrop: (view, event: any) => { diff --git a/packages/client/src/tiptap/prose-utils/url.ts b/packages/client/src/tiptap/prose-utils/url.ts index 0c0b9848..4c4824a1 100644 --- a/packages/client/src/tiptap/prose-utils/url.ts +++ b/packages/client/src/tiptap/prose-utils/url.ts @@ -1,3 +1,12 @@ -export function isValidURL(str: string) { - return str && str.startsWith('http'); +export function isValidURL(text: string) { + if (text.match(/\n/)) { + return false; + } + + try { + const url = new URL(text); + return url.hostname !== ''; + } catch (err) { + return false; + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdbcd6d7..31b17951 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,6 +118,7 @@ importers: markdown-it-sup: ^1.0.0 next: 12.1.0 next-pwa: ^5.5.2 + prosemirror-commands: ^1.3.0 prosemirror-markdown: ^1.7.0 prosemirror-model: ^1.16.1 prosemirror-schema-list: ^1.1.6 @@ -209,6 +210,7 @@ importers: markdown-it-sup: 1.0.0 next: 12.1.0_react-dom@17.0.2+react@17.0.2 next-pwa: 5.5.2_next@12.1.0 + prosemirror-commands: 1.3.0 prosemirror-markdown: 1.7.0 prosemirror-model: 1.16.1 prosemirror-schema-list: 1.1.6 @@ -2688,7 +2690,7 @@ packages: '@types/prosemirror-state': 1.2.8 '@types/prosemirror-transform': 1.1.5 '@types/prosemirror-view': 1.23.1 - prosemirror-commands: 1.2.1 + prosemirror-commands: 1.3.0 prosemirror-keymap: 1.1.5 prosemirror-model: 1.16.1 prosemirror-schema-list: 1.1.6 @@ -9143,8 +9145,8 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /prosemirror-commands/1.2.1: - resolution: {integrity: sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==} + /prosemirror-commands/1.3.0: + resolution: {integrity: sha512-BwBbZ5OAScPcm0x7H8SPbqjuEJnCU2RJT9LDyOiiIl/3NbL1nJZI4SFNHwU2e/tRr2Xe7JsptpzseqvZvToLBQ==} dependencies: prosemirror-model: 1.16.1 prosemirror-state: 1.3.4