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