Skip to content

Commit 2878ef3

Browse files
committed
editor: add support for pasting unformatted links
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
1 parent b296f5d commit 2878ef3

File tree

6 files changed

+210
-50
lines changed

6 files changed

+210
-50
lines changed

packages/editor/package-lock.json

Lines changed: 45 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/editor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"detect-indent": "^7.0.1",
7070
"entities": "^5.0.0",
7171
"katex": "0.16.11",
72+
"linkify-html": "^4.2.0",
7273
"linkifyjs": "^4.1.3",
7374
"nanoid": "^5.0.7",
7475
"prism-themes": "^1.9.0",

packages/editor/src/extensions/link/helpers/pasteHandler.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,65 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2020
import { Editor } from "@tiptap/core";
2121
import { MarkType } from "@tiptap/pm/model";
2222
import { Plugin, PluginKey } from "@tiptap/pm/state";
23+
import linkifyHtml from "linkify-html";
2324
import { find } from "linkifyjs";
25+
import { linkRegex } from "../link";
2426

2527
type PasteHandlerOptions = {
2628
editor: Editor;
2729
type: MarkType;
30+
linkOnPaste: boolean;
2831
};
2932

3033
export function pasteHandler(options: PasteHandlerOptions): Plugin {
34+
let shiftKey = false;
3135
return new Plugin({
3236
key: new PluginKey("handlePasteLink"),
3337
props: {
38+
handleKeyDown(_, event) {
39+
shiftKey = event.shiftKey;
40+
return false;
41+
},
3442
handlePaste: (view, event, slice) => {
43+
const clipboardHtmlData = event.clipboardData?.getData("text/html");
44+
if (clipboardHtmlData) {
45+
return false;
46+
}
47+
3548
const { state } = view;
3649
const { selection } = state;
3750
const { empty } = selection;
3851

39-
if (empty) {
40-
return false;
41-
}
42-
4352
let textContent = "";
4453

4554
slice.content.forEach((node) => {
4655
textContent += node.textContent;
4756
});
4857

58+
// don't deal with markdown links in this handler
59+
if (linkRegex.test(textContent)) {
60+
// reset the regex since we don't want the above test method to affect other places where the regex is used
61+
linkRegex.lastIndex = 0;
62+
return false;
63+
}
64+
65+
if (shiftKey) {
66+
return false;
67+
}
68+
69+
if (empty) {
70+
if (textContent) {
71+
const html = linkifyHtml(textContent);
72+
options.editor.commands.insertContent(html);
73+
return true;
74+
}
75+
return false;
76+
}
77+
78+
if (!options.linkOnPaste) {
79+
return false;
80+
}
81+
4982
const link = find(textContent).find(
5083
(item) => item.isLink && item.value === textContent
5184
);

packages/editor/src/extensions/link/link.ts

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -227,35 +227,6 @@ export const Link = Mark.create<LinkOptions>({
227227
href: regExp.exec(match[0])?.[1]
228228
};
229229
}
230-
}),
231-
markPasteRule({
232-
find: (text) => {
233-
const foundLinks: PasteRuleMatch[] = [];
234-
235-
if (text) {
236-
const links = find(text).filter((item) => item.isLink);
237-
238-
if (links.length) {
239-
links.forEach((link) =>
240-
foundLinks.push({
241-
text: link.value,
242-
data: {
243-
href: link.href
244-
},
245-
index: link.start
246-
})
247-
);
248-
}
249-
}
250-
251-
return foundLinks;
252-
},
253-
type: this.type,
254-
getAttributes: (match) => {
255-
return {
256-
href: match.data?.href
257-
};
258-
}
259230
})
260231
];
261232
},
@@ -281,14 +252,13 @@ export const Link = Mark.create<LinkOptions>({
281252
);
282253
}
283254

284-
if (this.options.linkOnPaste) {
285-
plugins.push(
286-
pasteHandler({
287-
editor: this.editor,
288-
type: this.type
289-
})
290-
);
291-
}
255+
plugins.push(
256+
pasteHandler({
257+
editor: this.editor,
258+
type: this.type,
259+
linkOnPaste: this.options.linkOnPaste
260+
})
261+
);
292262

293263
return plugins;
294264
},
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`paste text > with link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="http://example.com">example.com</a></p></div></div>"`;
4+
35
exports[`paste text > with markdown link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a></p></div></div>"`;
46

7+
exports[`paste text > with multiple links 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="http://example.com">example.com</a> <a target="_blank" rel="noopener noreferrer nofollow" href="http://example2.com">example2.com</a></p></div></div>"`;
8+
59
exports[`paste text > with multiple markdown links 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a> some text <a target="_blank" rel="noopener noreferrer nofollow" href="example2.com">test2</a></p></div></div>"`;
10+
11+
exports[`unformatted paste text > with link shouldn't format as link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p>https://example.com</p></div></div>"`;
12+
13+
exports[`unformatted paste text > with markdown link should still format as link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">asdf</a></p></div></div>"`;

0 commit comments

Comments
 (0)