···1+import { InputRule, markInputRule, markPasteRule, PasteRule } from '@tiptap/core';
2+import { Link } from '@tiptap/extension-link';
3+4+import 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+ */
10+const 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+ */
16+const 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+ */
24+function 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+ */
45+function 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+ */
62+type 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+ */
69+const RichTextLink = Link.extend<RichTextLinkOptions>({
70+ inclusive: false,
71+ addOptions() {
72+ return {
73+ ...this.parent?.(),
74+ openOnClick: 'whenNotEditable'
75+ };
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+123+export { RichTextLink };
124+125+export type { RichTextLinkOptions };