your personal website on atproto - mirror
blento.app
1import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core';
2import { Link } from '@tiptap/extension-link';
3
4import type { LinkOptions } from '@tiptap/extension-link';
5
6/**
7 * The input regex for Markdown links with title support, and multiple quotation marks (required
8 * in case the `Typography` extension is being included).
9 */
10const inputRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)$/i;
11
12/**
13 * The paste regex for Markdown links with title support, and multiple quotation marks (required
14 * in case the `Typography` extension is being included).
15 */
16const pasteRegex = /(?:^|\s)\[([^\]]*)?\]\((\S+)(?: ["“](.+)["”])?\)/gi;
17
18/**
19 * Input rule built specifically for the `Link` extension, which ignores the auto-linked URL in
20 * parentheses (e.g., `(https://doist.dev)`).
21 *
22 * @see https://github.com/ueberdosis/tiptap/discussions/1865
23 */
24function linkInputRule(config: Parameters<typeof markInputRule>[0]) {
25 const defaultMarkInputRule = markInputRule(config);
26
27 return new InputRule({
28 find: config.find,
29 handler(props) {
30 const { tr } = props.state;
31
32 defaultMarkInputRule.handler(props);
33 tr.setMeta('preventAutolink', true);
34 }
35 });
36}
37
38/**
39 * Paste rule built specifically for the `Link` extension, which ignores the auto-linked URL in
40 * parentheses (e.g., `(https://doist.dev)`). This extension was inspired from the multiple
41 * implementations found in a Tiptap discussion at GitHub.
42 *
43 * @see https://github.com/ueberdosis/tiptap/discussions/1865
44 */
45function linkPasteRule(config: Parameters<typeof markPasteRule>[0]) {
46 const defaultMarkPasteRule = markPasteRule(config);
47
48 return new PasteRule({
49 find: config.find,
50 handler(props) {
51 const { tr } = props.state;
52
53 defaultMarkPasteRule.handler(props);
54 tr.setMeta('preventAutolink', true);
55 }
56 });
57}
58
59/**
60 * The options available to customize the `RichTextLink` extension.
61 */
62type RichTextLinkOptions = LinkOptions;
63
64/**
65 * Custom extension that extends the built-in `Link` extension to add additional input/paste rules
66 * for converting the Markdown link syntax (i.e. `[Doist](https://doist.com)`) into links, and also
67 * adds support for the `title` attribute.
68 */
69const RichTextLink = Link.extend<RichTextLinkOptions>({
70 inclusive: false,
71 addOptions(): LinkOptions {
72 return {
73 ...this.parent?.(),
74 openOnClick: 'whenNotEditable'
75 } as LinkOptions;
76 },
77 addAttributes() {
78 return {
79 ...this.parent?.(),
80 title: {
81 default: null
82 }
83 };
84 },
85 addInputRules() {
86 return [
87 linkInputRule({
88 find: inputRegex,
89 type: this.type,
90
91 // We need to use `pop()` to remove the last capture groups from the match to
92 // satisfy Tiptap's `markPasteRule` expectation of having the content as the last
93 // capture group in the match (this makes the attribute order important)
94 getAttributes(match) {
95 return {
96 title: match.pop()?.trim(),
97 href: match.pop()?.trim()
98 };
99 }
100 })
101 ];
102 },
103 addPasteRules() {
104 return [
105 linkPasteRule({
106 find: pasteRegex,
107 type: this.type,
108
109 // We need to use `pop()` to remove the last capture groups from the match to
110 // satisfy Tiptap's `markInputRule` expectation of having the content as the last
111 // capture group in the match (this makes the attribute order important)
112 getAttributes(match) {
113 return {
114 title: match.pop()?.trim(),
115 href: match.pop()?.trim()
116 };
117 }
118 })
119 ];
120 }
121});
122
123export { RichTextLink };
124
125export type { RichTextLinkOptions };