@tiptap/extension-link 3.0.0-next.3 → 3.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +5 -1
- package/dist/index.cjs +45 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +42 -55
- package/dist/index.js.map +1 -1
- package/package.json +11 -7
- package/src/helpers/autolink.ts +5 -18
- package/src/helpers/clickHandler.ts +2 -2
- package/src/helpers/pasteHandler.ts +4 -4
- package/src/link.ts +95 -92
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Tiptap GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# @tiptap/extension-link
|
|
2
|
+
|
|
2
3
|
[](https://www.npmjs.com/package/@tiptap/extension-link)
|
|
3
4
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
|
4
5
|
[](https://www.npmjs.com/package/@tiptap/extension-link)
|
|
5
6
|
[](https://github.com/sponsors/ueberdosis)
|
|
6
7
|
|
|
7
8
|
## Introduction
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as _New York Times_, _The Guardian_ or _Atlassian_.
|
|
9
11
|
|
|
10
12
|
## Official Documentation
|
|
13
|
+
|
|
11
14
|
Documentation can be found on the [Tiptap website](https://tiptap.dev).
|
|
12
15
|
|
|
13
16
|
## License
|
|
17
|
+
|
|
14
18
|
Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
|
package/dist/index.cjs
CHANGED
|
@@ -18,13 +18,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
23
|
Link: () => Link,
|
|
24
|
-
default: () =>
|
|
24
|
+
default: () => index_default,
|
|
25
|
+
isAllowedUri: () => isAllowedUri,
|
|
25
26
|
pasteRegex: () => pasteRegex
|
|
26
27
|
});
|
|
27
|
-
module.exports = __toCommonJS(
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
29
|
|
|
29
30
|
// src/link.ts
|
|
30
31
|
var import_core3 = require("@tiptap/core");
|
|
@@ -56,11 +57,7 @@ function autolink(options) {
|
|
|
56
57
|
const transform = (0, import_core.combineTransactionSteps)(oldState.doc, [...transactions]);
|
|
57
58
|
const changes = (0, import_core.getChangedRanges)(transform);
|
|
58
59
|
changes.forEach(({ newRange }) => {
|
|
59
|
-
const nodesInChangedRanges = (0, import_core.findChildrenInRange)(
|
|
60
|
-
newState.doc,
|
|
61
|
-
newRange,
|
|
62
|
-
(node) => node.isTextblock
|
|
63
|
-
);
|
|
60
|
+
const nodesInChangedRanges = (0, import_core.findChildrenInRange)(newState.doc, newRange, (node) => node.isTextblock);
|
|
64
61
|
let textBlock;
|
|
65
62
|
let textBeforeWhitespace;
|
|
66
63
|
if (nodesInChangedRanges.length > 1) {
|
|
@@ -71,14 +68,10 @@ function autolink(options) {
|
|
|
71
68
|
void 0,
|
|
72
69
|
" "
|
|
73
70
|
);
|
|
74
|
-
} else if (nodesInChangedRanges.length &&
|
|
71
|
+
} else if (nodesInChangedRanges.length && // We want to make sure to include the block seperator argument to treat hard breaks like spaces.
|
|
72
|
+
newState.doc.textBetween(newRange.from, newRange.to, " ", " ").endsWith(" ")) {
|
|
75
73
|
textBlock = nodesInChangedRanges[0];
|
|
76
|
-
textBeforeWhitespace = newState.doc.textBetween(
|
|
77
|
-
textBlock.pos,
|
|
78
|
-
newRange.to,
|
|
79
|
-
void 0,
|
|
80
|
-
" "
|
|
81
|
-
);
|
|
74
|
+
textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, void 0, " ");
|
|
82
75
|
}
|
|
83
76
|
if (textBlock && textBeforeWhitespace) {
|
|
84
77
|
const wordsBeforeWhitespace = textBeforeWhitespace.split(" ").filter((s) => s !== "");
|
|
@@ -102,11 +95,7 @@ function autolink(options) {
|
|
|
102
95
|
if (!newState.schema.marks.code) {
|
|
103
96
|
return true;
|
|
104
97
|
}
|
|
105
|
-
return !newState.doc.rangeHasMark(
|
|
106
|
-
link.from,
|
|
107
|
-
link.to,
|
|
108
|
-
newState.schema.marks.code
|
|
109
|
-
);
|
|
98
|
+
return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code);
|
|
110
99
|
}).filter((link) => options.validate(link.value)).filter((link) => options.shouldAutoLink(link.value)).forEach((link) => {
|
|
111
100
|
if ((0, import_core.getMarksBetween)(link.from, link.to, newState.doc).some((item) => item.mark.type === options.type)) {
|
|
112
101
|
return;
|
|
@@ -185,14 +174,15 @@ function pasteHandler(options) {
|
|
|
185
174
|
slice.content.forEach((node) => {
|
|
186
175
|
textContent += node.textContent;
|
|
187
176
|
});
|
|
188
|
-
const link = (0, import_linkifyjs2.find)(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
177
|
+
const link = (0, import_linkifyjs2.find)(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
178
|
+
(item) => item.isLink && item.value === textContent
|
|
179
|
+
);
|
|
189
180
|
if (!textContent || !link) {
|
|
190
181
|
return false;
|
|
191
182
|
}
|
|
192
|
-
options.editor.commands.setMark(options.type, {
|
|
183
|
+
return options.editor.commands.setMark(options.type, {
|
|
193
184
|
href: link.href
|
|
194
185
|
});
|
|
195
|
-
return true;
|
|
196
186
|
}
|
|
197
187
|
}
|
|
198
188
|
});
|
|
@@ -202,18 +192,7 @@ function pasteHandler(options) {
|
|
|
202
192
|
var pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi;
|
|
203
193
|
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
204
194
|
function isAllowedUri(uri, protocols) {
|
|
205
|
-
const allowedProtocols = [
|
|
206
|
-
"http",
|
|
207
|
-
"https",
|
|
208
|
-
"ftp",
|
|
209
|
-
"ftps",
|
|
210
|
-
"mailto",
|
|
211
|
-
"tel",
|
|
212
|
-
"callto",
|
|
213
|
-
"sms",
|
|
214
|
-
"cid",
|
|
215
|
-
"xmpp"
|
|
216
|
-
];
|
|
195
|
+
const allowedProtocols = ["http", "https", "ftp", "ftps", "mailto", "tel", "callto", "sms", "cid", "xmpp"];
|
|
217
196
|
if (protocols) {
|
|
218
197
|
protocols.forEach((protocol) => {
|
|
219
198
|
const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
|
|
@@ -238,9 +217,7 @@ var Link = import_core3.Mark.create({
|
|
|
238
217
|
onCreate() {
|
|
239
218
|
if (this.options.validate && !this.options.shouldAutoLink) {
|
|
240
219
|
this.options.shouldAutoLink = this.options.validate;
|
|
241
|
-
console.warn(
|
|
242
|
-
"The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead."
|
|
243
|
-
);
|
|
220
|
+
console.warn("The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.");
|
|
244
221
|
}
|
|
245
222
|
this.options.protocols.forEach((protocol) => {
|
|
246
223
|
if (typeof protocol === "string") {
|
|
@@ -316,20 +293,32 @@ var Link = import_core3.Mark.create({
|
|
|
316
293
|
protocols: this.options.protocols,
|
|
317
294
|
defaultProtocol: this.options.defaultProtocol
|
|
318
295
|
})) {
|
|
319
|
-
return [
|
|
320
|
-
"a",
|
|
321
|
-
(0, import_core3.mergeAttributes)(this.options.HTMLAttributes, { ...HTMLAttributes, href: "" }),
|
|
322
|
-
0
|
|
323
|
-
];
|
|
296
|
+
return ["a", (0, import_core3.mergeAttributes)(this.options.HTMLAttributes, { ...HTMLAttributes, href: "" }), 0];
|
|
324
297
|
}
|
|
325
298
|
return ["a", (0, import_core3.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
326
299
|
},
|
|
327
300
|
addCommands() {
|
|
328
301
|
return {
|
|
329
302
|
setLink: (attributes) => ({ chain }) => {
|
|
303
|
+
const { href } = attributes;
|
|
304
|
+
if (!this.options.isAllowedUri(href, {
|
|
305
|
+
defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
|
|
306
|
+
protocols: this.options.protocols,
|
|
307
|
+
defaultProtocol: this.options.defaultProtocol
|
|
308
|
+
})) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
330
311
|
return chain().setMark(this.name, attributes).setMeta("preventAutolink", true).run();
|
|
331
312
|
},
|
|
332
313
|
toggleLink: (attributes) => ({ chain }) => {
|
|
314
|
+
const { href } = attributes;
|
|
315
|
+
if (!this.options.isAllowedUri(href, {
|
|
316
|
+
defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
|
|
317
|
+
protocols: this.options.protocols,
|
|
318
|
+
defaultProtocol: this.options.defaultProtocol
|
|
319
|
+
})) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
333
322
|
return chain().toggleMark(this.name, attributes, { extendEmptyMarkRange: true }).setMeta("preventAutolink", true).run();
|
|
334
323
|
},
|
|
335
324
|
unsetLink: () => ({ chain }) => {
|
|
@@ -344,7 +333,7 @@ var Link = import_core3.Mark.create({
|
|
|
344
333
|
const foundLinks = [];
|
|
345
334
|
if (text) {
|
|
346
335
|
const { protocols, defaultProtocol } = this.options;
|
|
347
|
-
const links = (0, import_linkifyjs3.find)(text
|
|
336
|
+
const links = (0, import_linkifyjs3.find)(text).filter(
|
|
348
337
|
(item) => item.isLink && this.options.isAllowedUri(item.value, {
|
|
349
338
|
defaultValidate: (href) => !!isAllowedUri(href, protocols),
|
|
350
339
|
protocols,
|
|
@@ -352,13 +341,15 @@ var Link = import_core3.Mark.create({
|
|
|
352
341
|
})
|
|
353
342
|
);
|
|
354
343
|
if (links.length) {
|
|
355
|
-
links.forEach(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
344
|
+
links.forEach(
|
|
345
|
+
(link) => foundLinks.push({
|
|
346
|
+
text: link.value,
|
|
347
|
+
data: {
|
|
348
|
+
href: link.href
|
|
349
|
+
},
|
|
350
|
+
index: link.start
|
|
351
|
+
})
|
|
352
|
+
);
|
|
362
353
|
}
|
|
363
354
|
}
|
|
364
355
|
return foundLinks;
|
|
@@ -411,10 +402,11 @@ var Link = import_core3.Mark.create({
|
|
|
411
402
|
});
|
|
412
403
|
|
|
413
404
|
// src/index.ts
|
|
414
|
-
var
|
|
405
|
+
var index_default = Link;
|
|
415
406
|
// Annotate the CommonJS export names for ESM import in node:
|
|
416
407
|
0 && (module.exports = {
|
|
417
408
|
Link,
|
|
409
|
+
isAllowedUri,
|
|
418
410
|
pasteRegex
|
|
419
411
|
});
|
|
420
412
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/link.ts","../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts"],"sourcesContent":["import { Link } from './link.js'\n\nexport * from './link.js'\n\nexport default Link\n","import {\n Mark, markPasteRule, mergeAttributes, PasteRuleMatch,\n} from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { find, registerCustomProtocol, reset } from 'linkifyjs'\n\nimport { autolink } from './helpers/autolink.js'\nimport { clickHandler } from './helpers/clickHandler.js'\nimport { pasteHandler } from './helpers/pasteHandler.js'\n\nexport interface LinkProtocolOptions {\n /**\n * The protocol scheme to be registered.\n * @default '''\n * @example 'ftp'\n * @example 'git'\n */\n scheme: string;\n\n /**\n * If enabled, it allows optional slashes after the protocol.\n * @default false\n * @example true\n */\n optionalSlashes?: boolean;\n}\n\nexport const pasteRegex = /https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z]{2,}\\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi\n\n/**\n * @deprecated The default behavior is now to open links when the editor is not editable.\n */\ntype DeprecatedOpenWhenNotEditable = 'whenNotEditable';\n\nexport interface LinkOptions {\n /**\n * If enabled, the extension will automatically add links as you type.\n * @default true\n * @example false\n */\n autolink: boolean;\n\n /**\n * An array of custom protocols to be registered with linkifyjs.\n * @default []\n * @example ['ftp', 'git']\n */\n protocols: Array<LinkProtocolOptions | string>;\n\n /**\n * Default protocol to use when no protocol is specified.\n * @default 'http'\n */\n defaultProtocol: string;\n /**\n * If enabled, links will be opened on click.\n * @default true\n * @example false\n */\n openOnClick: boolean | DeprecatedOpenWhenNotEditable;\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n * @default true\n * @example false\n */\n linkOnPaste: boolean;\n\n /**\n * HTML attributes to add to the link element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n\n /**\n * @deprecated Use the `shouldAutoLink` option instead.\n * A validation function that modifies link verification for the auto linker.\n * @param url - The url to be validated.\n * @returns - True if the url is valid, false otherwise.\n */\n validate: (url: string) => boolean;\n\n /**\n * A validation function which is used for configuring link verification for preventing XSS attacks.\n * Only modify this if you know what you're doing.\n *\n * @returns {boolean} `true` if the URL is valid, `false` otherwise.\n *\n * @example\n * isAllowedUri: (url, { defaultValidate, protocols, defaultProtocol }) => {\n * return url.startsWith('./') || defaultValidate(url)\n * }\n */\n isAllowedUri: (\n /**\n * The URL to be validated.\n */\n url: string,\n ctx: {\n /**\n * The default validation function.\n */\n defaultValidate: (url: string) => boolean;\n /**\n * An array of allowed protocols for the URL (e.g., \"http\", \"https\"). As defined in the `protocols` option.\n */\n protocols: Array<LinkProtocolOptions | string>;\n /**\n * A string that represents the default protocol (e.g., 'http'). As defined in the `defaultProtocol` option.\n */\n defaultProtocol: string;\n }\n ) => boolean;\n\n /**\n * Determines whether a valid link should be automatically linked in the content.\n *\n * @param {string} url - The URL that has already been validated.\n * @returns {boolean} - True if the link should be auto-linked; false if it should not be auto-linked.\n */\n shouldAutoLink: (url: string) => boolean;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n * @param attributes The link attributes\n * @example editor.commands.setLink({ href: 'https://tiptap.dev' })\n */\n setLink: (attributes: {\n href: string;\n target?: string | null;\n rel?: string | null;\n class?: string | null;\n }) => ReturnType;\n /**\n * Toggle a link mark\n * @param attributes The link attributes\n * @example editor.commands.toggleLink({ href: 'https://tiptap.dev' })\n */\n toggleLink: (attributes: {\n href: string;\n target?: string | null;\n rel?: string | null;\n class?: string | null;\n }) => ReturnType;\n /**\n * Unset a link mark\n * @example editor.commands.unsetLink()\n */\n unsetLink: () => ReturnType;\n };\n }\n}\n\n// From DOMPurify\n// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js\n// eslint-disable-next-line no-control-regex\nconst ATTR_WHITESPACE = /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g\n\nfunction isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {\n const allowedProtocols: string[] = [\n 'http',\n 'https',\n 'ftp',\n 'ftps',\n 'mailto',\n 'tel',\n 'callto',\n 'sms',\n 'cid',\n 'xmpp',\n ]\n\n if (protocols) {\n protocols.forEach(protocol => {\n const nextProtocol = typeof protocol === 'string' ? protocol : protocol.scheme\n\n if (nextProtocol) {\n allowedProtocols.push(nextProtocol)\n }\n })\n }\n\n return (\n !uri\n || uri\n .replace(ATTR_WHITESPACE, '')\n .match(\n new RegExp(\n // eslint-disable-next-line no-useless-escape\n `^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))`,\n 'i',\n ),\n )\n )\n}\n\n/**\n * This extension allows you to create links.\n * @see https://www.tiptap.dev/api/marks/link\n */\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n exitable: true,\n\n onCreate() {\n if (this.options.validate && !this.options.shouldAutoLink) {\n // Copy the validate function to the shouldAutoLink option\n this.options.shouldAutoLink = this.options.validate\n console.warn(\n 'The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.',\n )\n }\n this.options.protocols.forEach(protocol => {\n if (typeof protocol === 'string') {\n registerCustomProtocol(protocol)\n return\n }\n registerCustomProtocol(protocol.scheme, protocol.optionalSlashes)\n })\n },\n\n onDestroy() {\n reset()\n },\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n protocols: [],\n defaultProtocol: 'http',\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n },\n isAllowedUri: (url, ctx) => !!isAllowedUri(url, ctx.protocols),\n validate: url => !!url,\n shouldAutoLink: url => !!url,\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n parseHTML(element) {\n return element.getAttribute('href')\n },\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n rel: {\n default: this.options.HTMLAttributes.rel,\n },\n class: {\n default: this.options.HTMLAttributes.class,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'a[href]',\n getAttrs: dom => {\n const href = (dom as HTMLElement).getAttribute('href')\n\n // prevent XSS attacks\n if (\n !href\n || !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n return null\n },\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n // prevent XSS attacks\n if (\n !this.options.isAllowedUri(HTMLAttributes.href, {\n defaultValidate: href => !!isAllowedUri(href, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n // strip out the href\n return [\n 'a',\n mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }),\n 0,\n ]\n }\n\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink:\n attributes => ({ chain }) => {\n return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()\n },\n\n toggleLink:\n attributes => ({ chain }) => {\n return chain()\n .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n\n unsetLink:\n () => ({ chain }) => {\n return chain()\n .unsetMark(this.name, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => {\n const foundLinks: PasteRuleMatch[] = []\n\n if (text) {\n const { protocols, defaultProtocol } = this.options\n // Prosemirror replaces zero-width non-joiner characters\n // with Object Replacement Character from unicode.\n // Therefore, linkifyjs does not recognize the\n // link properly. We replace these characters\n // with regular spaces to fix this issue.\n const links = find(text.replaceAll('\\uFFFC', ' ')).filter(\n item => item.isLink\n && this.options.isAllowedUri(item.value, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n )\n\n if (links.length) {\n links.forEach(link => foundLinks.push({\n text: link.value,\n data: {\n href: link.href,\n },\n index: link.start,\n }))\n }\n }\n\n return foundLinks\n },\n type: this.type,\n getAttributes: match => {\n return {\n href: match.data?.href,\n }\n },\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins: Plugin[] = []\n const { protocols, defaultProtocol } = this.options\n\n if (this.options.autolink) {\n plugins.push(\n autolink({\n type: this.type,\n defaultProtocol: this.options.defaultProtocol,\n validate: url => this.options.isAllowedUri(url, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n shouldAutoLink: this.options.shouldAutoLink,\n }),\n )\n }\n\n if (this.options.openOnClick === true) {\n plugins.push(\n clickHandler({\n type: this.type,\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n pasteHandler({\n editor: this.editor,\n defaultProtocol: this.options.defaultProtocol,\n type: this.type,\n }),\n )\n }\n\n return plugins\n },\n})\n","import {\n combineTransactionSteps,\n findChildrenInRange,\n getChangedRanges,\n getMarksBetween,\n NodeWithPos,\n} from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { MultiToken, tokenize } from 'linkifyjs'\n\n/**\n * Check if the provided tokens form a valid link structure, which can either be a single link token\n * or a link token surrounded by parentheses or square brackets.\n *\n * This ensures that only complete and valid text is hyperlinked, preventing cases where a valid\n * top-level domain (TLD) is immediately followed by an invalid character, like a number. For\n * example, with the `find` method from Linkify, entering `example.com1` would result in\n * `example.com` being linked and the trailing `1` left as plain text. By using the `tokenize`\n * method, we can perform more comprehensive validation on the input text.\n */\nfunction isValidLinkStructure(tokens: Array<ReturnType<MultiToken['toObject']>>) {\n if (tokens.length === 1) {\n return tokens[0].isLink\n }\n\n if (tokens.length === 3 && tokens[1].isLink) {\n return ['()', '[]'].includes(tokens[0].value + tokens[2].value)\n }\n\n return false\n}\n\ntype AutolinkOptions = {\n type: MarkType\n defaultProtocol: string\n validate: (url: string) => boolean\n shouldAutoLink: (url: string) => boolean\n}\n\n/**\n * This plugin allows you to automatically add links to your editor.\n * @param options The plugin options\n * @returns The plugin instance\n */\nexport function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n /**\n * Does the transaction change the document?\n */\n const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n\n /**\n * Prevent autolink if the transaction is not a document change or if the transaction has the meta `preventAutolink`.\n */\n const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink'))\n\n /**\n * Prevent autolink if the transaction is not a document change\n * or if the transaction has the meta `preventAutolink`.\n */\n if (!docChanges || preventAutolink) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, [...transactions])\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n // Now let’s see if we can add new links.\n const nodesInChangedRanges = findChildrenInRange(\n newState.doc,\n newRange,\n node => node.isTextblock,\n )\n\n let textBlock: NodeWithPos | undefined\n let textBeforeWhitespace: string | undefined\n\n if (nodesInChangedRanges.length > 1) {\n // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n textBlock.pos + textBlock.node.nodeSize,\n undefined,\n ' ',\n )\n } else if (\n nodesInChangedRanges.length\n // We want to make sure to include the block seperator argument to treat hard breaks like spaces.\n && newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')\n ) {\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n newRange.to,\n undefined,\n ' ',\n )\n }\n\n if (textBlock && textBeforeWhitespace) {\n const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '')\n\n if (wordsBeforeWhitespace.length <= 0) {\n return false\n }\n\n const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1]\n const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace)\n\n if (!lastWordBeforeSpace) {\n return false\n }\n\n const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol))\n\n if (!isValidLinkStructure(linksBeforeSpace)) {\n return false\n }\n\n linksBeforeSpace\n .filter(link => link.isLink)\n // Calculate link position.\n .map(link => ({\n ...link,\n from: lastWordAndBlockOffset + link.start + 1,\n to: lastWordAndBlockOffset + link.end + 1,\n }))\n // ignore link inside code mark\n .filter(link => {\n if (!newState.schema.marks.code) {\n return true\n }\n\n return !newState.doc.rangeHasMark(\n link.from,\n link.to,\n newState.schema.marks.code,\n )\n })\n // validate link\n .filter(link => options.validate(link.value))\n // check whether should autolink\n .filter(link => options.shouldAutoLink(link.value))\n // Add link mark.\n .forEach(link => {\n if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) {\n return\n }\n\n tr.addMark(\n link.from,\n link.to,\n options.type.create({\n href: link.href,\n }),\n )\n })\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n return tr\n },\n })\n}\n","import { getAttributes } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\ntype ClickHandlerOptions = {\n type: MarkType;\n}\n\nexport function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return false\n }\n\n if (!view.editable) {\n return false\n }\n\n let a = event.target as HTMLElement\n const els = []\n\n while (a.nodeName !== 'DIV') {\n els.push(a)\n a = a.parentNode as HTMLElement\n }\n\n if (!els.find(value => value.nodeName === 'A')) {\n return false\n }\n\n const attrs = getAttributes(view.state, options.type.name)\n const link = (event.target as HTMLAnchorElement)\n\n const href = link?.href ?? attrs.href\n const target = link?.target ?? attrs.target\n\n if (link && href) {\n window.open(href, target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor\n defaultProtocol: string\n type: MarkType\n}\n\nexport function pasteHandler(options: PasteHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handlePasteLink'),\n props: {\n handlePaste: (view, event, slice) => {\n const { state } = view\n const { selection } = state\n const { empty } = selection\n\n if (empty) {\n return false\n }\n\n let textContent = ''\n\n slice.content.forEach(node => {\n textContent += node.textContent\n })\n\n const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(item => item.isLink && item.value === textContent)\n\n if (!textContent || !link) {\n return false\n }\n\n options.editor.commands.setMark(options.type, {\n href: link.href,\n })\n\n return true\n },\n },\n })\n}\n"],"mappings":"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","names":["import_core","import_linkifyjs","import_core","import_state","import_state","import_linkifyjs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/link.ts","../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts"],"sourcesContent":["import { Link } from './link.js'\n\nexport * from './link.js'\n\nexport default Link\n","import { Mark, markPasteRule, mergeAttributes, PasteRuleMatch } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { find, registerCustomProtocol, reset } from 'linkifyjs'\n\nimport { autolink } from './helpers/autolink.js'\nimport { clickHandler } from './helpers/clickHandler.js'\nimport { pasteHandler } from './helpers/pasteHandler.js'\n\nexport interface LinkProtocolOptions {\n /**\n * The protocol scheme to be registered.\n * @default '''\n * @example 'ftp'\n * @example 'git'\n */\n scheme: string\n\n /**\n * If enabled, it allows optional slashes after the protocol.\n * @default false\n * @example true\n */\n optionalSlashes?: boolean\n}\n\nexport const pasteRegex =\n /https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z]{2,}\\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi\n\n/**\n * @deprecated The default behavior is now to open links when the editor is not editable.\n */\ntype DeprecatedOpenWhenNotEditable = 'whenNotEditable'\n\nexport interface LinkOptions {\n /**\n * If enabled, the extension will automatically add links as you type.\n * @default true\n * @example false\n */\n autolink: boolean\n\n /**\n * An array of custom protocols to be registered with linkifyjs.\n * @default []\n * @example ['ftp', 'git']\n */\n protocols: Array<LinkProtocolOptions | string>\n\n /**\n * Default protocol to use when no protocol is specified.\n * @default 'http'\n */\n defaultProtocol: string\n /**\n * If enabled, links will be opened on click.\n * @default true\n * @example false\n */\n openOnClick: boolean | DeprecatedOpenWhenNotEditable\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n * @default true\n * @example false\n */\n linkOnPaste: boolean\n\n /**\n * HTML attributes to add to the link element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * @deprecated Use the `shouldAutoLink` option instead.\n * A validation function that modifies link verification for the auto linker.\n * @param url - The url to be validated.\n * @returns - True if the url is valid, false otherwise.\n */\n validate: (url: string) => boolean\n\n /**\n * A validation function which is used for configuring link verification for preventing XSS attacks.\n * Only modify this if you know what you're doing.\n *\n * @returns {boolean} `true` if the URL is valid, `false` otherwise.\n *\n * @example\n * isAllowedUri: (url, { defaultValidate, protocols, defaultProtocol }) => {\n * return url.startsWith('./') || defaultValidate(url)\n * }\n */\n isAllowedUri: (\n /**\n * The URL to be validated.\n */\n url: string,\n ctx: {\n /**\n * The default validation function.\n */\n defaultValidate: (url: string) => boolean\n /**\n * An array of allowed protocols for the URL (e.g., \"http\", \"https\"). As defined in the `protocols` option.\n */\n protocols: Array<LinkProtocolOptions | string>\n /**\n * A string that represents the default protocol (e.g., 'http'). As defined in the `defaultProtocol` option.\n */\n defaultProtocol: string\n },\n ) => boolean\n\n /**\n * Determines whether a valid link should be automatically linked in the content.\n *\n * @param {string} url - The URL that has already been validated.\n * @returns {boolean} - True if the link should be auto-linked; false if it should not be auto-linked.\n */\n shouldAutoLink: (url: string) => boolean\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n * @param attributes The link attributes\n * @example editor.commands.setLink({ href: 'https://tiptap.dev' })\n */\n setLink: (attributes: {\n href: string\n target?: string | null\n rel?: string | null\n class?: string | null\n }) => ReturnType\n /**\n * Toggle a link mark\n * @param attributes The link attributes\n * @example editor.commands.toggleLink({ href: 'https://tiptap.dev' })\n */\n toggleLink: (attributes: {\n href: string\n target?: string | null\n rel?: string | null\n class?: string | null\n }) => ReturnType\n /**\n * Unset a link mark\n * @example editor.commands.unsetLink()\n */\n unsetLink: () => ReturnType\n }\n }\n}\n\n// From DOMPurify\n// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js\n// eslint-disable-next-line no-control-regex\nconst ATTR_WHITESPACE = /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g\n\nexport function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {\n const allowedProtocols: string[] = ['http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp']\n\n if (protocols) {\n protocols.forEach(protocol => {\n const nextProtocol = typeof protocol === 'string' ? protocol : protocol.scheme\n\n if (nextProtocol) {\n allowedProtocols.push(nextProtocol)\n }\n })\n }\n\n return (\n !uri ||\n uri.replace(ATTR_WHITESPACE, '').match(\n new RegExp(\n // eslint-disable-next-line no-useless-escape\n `^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))`,\n 'i',\n ),\n )\n )\n}\n\n/**\n * This extension allows you to create links.\n * @see https://www.tiptap.dev/api/marks/link\n */\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n exitable: true,\n\n onCreate() {\n if (this.options.validate && !this.options.shouldAutoLink) {\n // Copy the validate function to the shouldAutoLink option\n this.options.shouldAutoLink = this.options.validate\n console.warn('The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.')\n }\n this.options.protocols.forEach(protocol => {\n if (typeof protocol === 'string') {\n registerCustomProtocol(protocol)\n return\n }\n registerCustomProtocol(protocol.scheme, protocol.optionalSlashes)\n })\n },\n\n onDestroy() {\n reset()\n },\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n protocols: [],\n defaultProtocol: 'http',\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n },\n isAllowedUri: (url, ctx) => !!isAllowedUri(url, ctx.protocols),\n validate: url => !!url,\n shouldAutoLink: url => !!url,\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n parseHTML(element) {\n return element.getAttribute('href')\n },\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n rel: {\n default: this.options.HTMLAttributes.rel,\n },\n class: {\n default: this.options.HTMLAttributes.class,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'a[href]',\n getAttrs: dom => {\n const href = (dom as HTMLElement).getAttribute('href')\n\n // prevent XSS attacks\n if (\n !href ||\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n return null\n },\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n // prevent XSS attacks\n if (\n !this.options.isAllowedUri(HTMLAttributes.href, {\n defaultValidate: href => !!isAllowedUri(href, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n // strip out the href\n return ['a', mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }), 0]\n }\n\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink:\n attributes =>\n ({ chain }) => {\n const { href } = attributes\n\n if (\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n\n return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()\n },\n\n toggleLink:\n attributes =>\n ({ chain }) => {\n const { href } = attributes\n\n if (\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n\n return chain()\n .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n\n unsetLink:\n () =>\n ({ chain }) => {\n return chain().unsetMark(this.name, { extendEmptyMarkRange: true }).setMeta('preventAutolink', true).run()\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => {\n const foundLinks: PasteRuleMatch[] = []\n\n if (text) {\n const { protocols, defaultProtocol } = this.options\n const links = find(text).filter(\n item =>\n item.isLink &&\n this.options.isAllowedUri(item.value, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n )\n\n if (links.length) {\n links.forEach(link =>\n foundLinks.push({\n text: link.value,\n data: {\n href: link.href,\n },\n index: link.start,\n }),\n )\n }\n }\n\n return foundLinks\n },\n type: this.type,\n getAttributes: match => {\n return {\n href: match.data?.href,\n }\n },\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins: Plugin[] = []\n const { protocols, defaultProtocol } = this.options\n\n if (this.options.autolink) {\n plugins.push(\n autolink({\n type: this.type,\n defaultProtocol: this.options.defaultProtocol,\n validate: url =>\n this.options.isAllowedUri(url, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n shouldAutoLink: this.options.shouldAutoLink,\n }),\n )\n }\n\n if (this.options.openOnClick === true) {\n plugins.push(\n clickHandler({\n type: this.type,\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n pasteHandler({\n editor: this.editor,\n defaultProtocol: this.options.defaultProtocol,\n type: this.type,\n }),\n )\n }\n\n return plugins\n },\n})\n","import {\n combineTransactionSteps,\n findChildrenInRange,\n getChangedRanges,\n getMarksBetween,\n NodeWithPos,\n} from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { MultiToken, tokenize } from 'linkifyjs'\n\n/**\n * Check if the provided tokens form a valid link structure, which can either be a single link token\n * or a link token surrounded by parentheses or square brackets.\n *\n * This ensures that only complete and valid text is hyperlinked, preventing cases where a valid\n * top-level domain (TLD) is immediately followed by an invalid character, like a number. For\n * example, with the `find` method from Linkify, entering `example.com1` would result in\n * `example.com` being linked and the trailing `1` left as plain text. By using the `tokenize`\n * method, we can perform more comprehensive validation on the input text.\n */\nfunction isValidLinkStructure(tokens: Array<ReturnType<MultiToken['toObject']>>) {\n if (tokens.length === 1) {\n return tokens[0].isLink\n }\n\n if (tokens.length === 3 && tokens[1].isLink) {\n return ['()', '[]'].includes(tokens[0].value + tokens[2].value)\n }\n\n return false\n}\n\ntype AutolinkOptions = {\n type: MarkType\n defaultProtocol: string\n validate: (url: string) => boolean\n shouldAutoLink: (url: string) => boolean\n}\n\n/**\n * This plugin allows you to automatically add links to your editor.\n * @param options The plugin options\n * @returns The plugin instance\n */\nexport function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n /**\n * Does the transaction change the document?\n */\n const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n\n /**\n * Prevent autolink if the transaction is not a document change or if the transaction has the meta `preventAutolink`.\n */\n const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink'))\n\n /**\n * Prevent autolink if the transaction is not a document change\n * or if the transaction has the meta `preventAutolink`.\n */\n if (!docChanges || preventAutolink) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, [...transactions])\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n // Now let’s see if we can add new links.\n const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, node => node.isTextblock)\n\n let textBlock: NodeWithPos | undefined\n let textBeforeWhitespace: string | undefined\n\n if (nodesInChangedRanges.length > 1) {\n // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n textBlock.pos + textBlock.node.nodeSize,\n undefined,\n ' ',\n )\n } else if (\n nodesInChangedRanges.length &&\n // We want to make sure to include the block seperator argument to treat hard breaks like spaces.\n newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')\n ) {\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, undefined, ' ')\n }\n\n if (textBlock && textBeforeWhitespace) {\n const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '')\n\n if (wordsBeforeWhitespace.length <= 0) {\n return false\n }\n\n const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1]\n const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace)\n\n if (!lastWordBeforeSpace) {\n return false\n }\n\n const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol))\n\n if (!isValidLinkStructure(linksBeforeSpace)) {\n return false\n }\n\n linksBeforeSpace\n .filter(link => link.isLink)\n // Calculate link position.\n .map(link => ({\n ...link,\n from: lastWordAndBlockOffset + link.start + 1,\n to: lastWordAndBlockOffset + link.end + 1,\n }))\n // ignore link inside code mark\n .filter(link => {\n if (!newState.schema.marks.code) {\n return true\n }\n\n return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code)\n })\n // validate link\n .filter(link => options.validate(link.value))\n // check whether should autolink\n .filter(link => options.shouldAutoLink(link.value))\n // Add link mark.\n .forEach(link => {\n if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) {\n return\n }\n\n tr.addMark(\n link.from,\n link.to,\n options.type.create({\n href: link.href,\n }),\n )\n })\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n return tr\n },\n })\n}\n","import { getAttributes } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\ntype ClickHandlerOptions = {\n type: MarkType\n}\n\nexport function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return false\n }\n\n if (!view.editable) {\n return false\n }\n\n let a = event.target as HTMLElement\n const els = []\n\n while (a.nodeName !== 'DIV') {\n els.push(a)\n a = a.parentNode as HTMLElement\n }\n\n if (!els.find(value => value.nodeName === 'A')) {\n return false\n }\n\n const attrs = getAttributes(view.state, options.type.name)\n const link = event.target as HTMLAnchorElement\n\n const href = link?.href ?? attrs.href\n const target = link?.target ?? attrs.target\n\n if (link && href) {\n window.open(href, target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor\n defaultProtocol: string\n type: MarkType\n}\n\nexport function pasteHandler(options: PasteHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handlePasteLink'),\n props: {\n handlePaste: (view, event, slice) => {\n const { state } = view\n const { selection } = state\n const { empty } = selection\n\n if (empty) {\n return false\n }\n\n let textContent = ''\n\n slice.content.forEach(node => {\n textContent += node.textContent\n })\n\n const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(\n item => item.isLink && item.value === textContent,\n )\n\n if (!textContent || !link) {\n return false\n }\n\n return options.editor.commands.setMark(options.type, {\n href: link.href,\n })\n },\n },\n })\n}\n"],"mappings":"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","names":["import_core","import_linkifyjs","import_core","import_state","import_state","import_linkifyjs"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -133,10 +133,11 @@ declare module '@tiptap/core' {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
declare function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']): true | RegExpMatchArray | null;
|
|
136
137
|
/**
|
|
137
138
|
* This extension allows you to create links.
|
|
138
139
|
* @see https://www.tiptap.dev/api/marks/link
|
|
139
140
|
*/
|
|
140
141
|
declare const Link: Mark<LinkOptions, any>;
|
|
141
142
|
|
|
142
|
-
export { Link, type LinkOptions, type LinkProtocolOptions, Link as default, pasteRegex };
|
|
143
|
+
export { Link, type LinkOptions, type LinkProtocolOptions, Link as default, isAllowedUri, pasteRegex };
|
package/dist/index.d.ts
CHANGED
|
@@ -133,10 +133,11 @@ declare module '@tiptap/core' {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
declare function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']): true | RegExpMatchArray | null;
|
|
136
137
|
/**
|
|
137
138
|
* This extension allows you to create links.
|
|
138
139
|
* @see https://www.tiptap.dev/api/marks/link
|
|
139
140
|
*/
|
|
140
141
|
declare const Link: Mark<LinkOptions, any>;
|
|
141
142
|
|
|
142
|
-
export { Link, type LinkOptions, type LinkProtocolOptions, Link as default, pasteRegex };
|
|
143
|
+
export { Link, type LinkOptions, type LinkProtocolOptions, Link as default, isAllowedUri, pasteRegex };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
// src/link.ts
|
|
2
|
-
import {
|
|
3
|
-
Mark,
|
|
4
|
-
markPasteRule,
|
|
5
|
-
mergeAttributes
|
|
6
|
-
} from "@tiptap/core";
|
|
2
|
+
import { Mark, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
7
3
|
import { find as find2, registerCustomProtocol, reset } from "linkifyjs";
|
|
8
4
|
|
|
9
5
|
// src/helpers/autolink.ts
|
|
@@ -37,11 +33,7 @@ function autolink(options) {
|
|
|
37
33
|
const transform = combineTransactionSteps(oldState.doc, [...transactions]);
|
|
38
34
|
const changes = getChangedRanges(transform);
|
|
39
35
|
changes.forEach(({ newRange }) => {
|
|
40
|
-
const nodesInChangedRanges = findChildrenInRange(
|
|
41
|
-
newState.doc,
|
|
42
|
-
newRange,
|
|
43
|
-
(node) => node.isTextblock
|
|
44
|
-
);
|
|
36
|
+
const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, (node) => node.isTextblock);
|
|
45
37
|
let textBlock;
|
|
46
38
|
let textBeforeWhitespace;
|
|
47
39
|
if (nodesInChangedRanges.length > 1) {
|
|
@@ -52,14 +44,10 @@ function autolink(options) {
|
|
|
52
44
|
void 0,
|
|
53
45
|
" "
|
|
54
46
|
);
|
|
55
|
-
} else if (nodesInChangedRanges.length &&
|
|
47
|
+
} else if (nodesInChangedRanges.length && // We want to make sure to include the block seperator argument to treat hard breaks like spaces.
|
|
48
|
+
newState.doc.textBetween(newRange.from, newRange.to, " ", " ").endsWith(" ")) {
|
|
56
49
|
textBlock = nodesInChangedRanges[0];
|
|
57
|
-
textBeforeWhitespace = newState.doc.textBetween(
|
|
58
|
-
textBlock.pos,
|
|
59
|
-
newRange.to,
|
|
60
|
-
void 0,
|
|
61
|
-
" "
|
|
62
|
-
);
|
|
50
|
+
textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, void 0, " ");
|
|
63
51
|
}
|
|
64
52
|
if (textBlock && textBeforeWhitespace) {
|
|
65
53
|
const wordsBeforeWhitespace = textBeforeWhitespace.split(" ").filter((s) => s !== "");
|
|
@@ -83,11 +71,7 @@ function autolink(options) {
|
|
|
83
71
|
if (!newState.schema.marks.code) {
|
|
84
72
|
return true;
|
|
85
73
|
}
|
|
86
|
-
return !newState.doc.rangeHasMark(
|
|
87
|
-
link.from,
|
|
88
|
-
link.to,
|
|
89
|
-
newState.schema.marks.code
|
|
90
|
-
);
|
|
74
|
+
return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code);
|
|
91
75
|
}).filter((link) => options.validate(link.value)).filter((link) => options.shouldAutoLink(link.value)).forEach((link) => {
|
|
92
76
|
if (getMarksBetween(link.from, link.to, newState.doc).some((item) => item.mark.type === options.type)) {
|
|
93
77
|
return;
|
|
@@ -166,14 +150,15 @@ function pasteHandler(options) {
|
|
|
166
150
|
slice.content.forEach((node) => {
|
|
167
151
|
textContent += node.textContent;
|
|
168
152
|
});
|
|
169
|
-
const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
153
|
+
const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
154
|
+
(item) => item.isLink && item.value === textContent
|
|
155
|
+
);
|
|
170
156
|
if (!textContent || !link) {
|
|
171
157
|
return false;
|
|
172
158
|
}
|
|
173
|
-
options.editor.commands.setMark(options.type, {
|
|
159
|
+
return options.editor.commands.setMark(options.type, {
|
|
174
160
|
href: link.href
|
|
175
161
|
});
|
|
176
|
-
return true;
|
|
177
162
|
}
|
|
178
163
|
}
|
|
179
164
|
});
|
|
@@ -183,18 +168,7 @@ function pasteHandler(options) {
|
|
|
183
168
|
var pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi;
|
|
184
169
|
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
185
170
|
function isAllowedUri(uri, protocols) {
|
|
186
|
-
const allowedProtocols = [
|
|
187
|
-
"http",
|
|
188
|
-
"https",
|
|
189
|
-
"ftp",
|
|
190
|
-
"ftps",
|
|
191
|
-
"mailto",
|
|
192
|
-
"tel",
|
|
193
|
-
"callto",
|
|
194
|
-
"sms",
|
|
195
|
-
"cid",
|
|
196
|
-
"xmpp"
|
|
197
|
-
];
|
|
171
|
+
const allowedProtocols = ["http", "https", "ftp", "ftps", "mailto", "tel", "callto", "sms", "cid", "xmpp"];
|
|
198
172
|
if (protocols) {
|
|
199
173
|
protocols.forEach((protocol) => {
|
|
200
174
|
const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
|
|
@@ -219,9 +193,7 @@ var Link = Mark.create({
|
|
|
219
193
|
onCreate() {
|
|
220
194
|
if (this.options.validate && !this.options.shouldAutoLink) {
|
|
221
195
|
this.options.shouldAutoLink = this.options.validate;
|
|
222
|
-
console.warn(
|
|
223
|
-
"The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead."
|
|
224
|
-
);
|
|
196
|
+
console.warn("The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.");
|
|
225
197
|
}
|
|
226
198
|
this.options.protocols.forEach((protocol) => {
|
|
227
199
|
if (typeof protocol === "string") {
|
|
@@ -297,20 +269,32 @@ var Link = Mark.create({
|
|
|
297
269
|
protocols: this.options.protocols,
|
|
298
270
|
defaultProtocol: this.options.defaultProtocol
|
|
299
271
|
})) {
|
|
300
|
-
return [
|
|
301
|
-
"a",
|
|
302
|
-
mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: "" }),
|
|
303
|
-
0
|
|
304
|
-
];
|
|
272
|
+
return ["a", mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: "" }), 0];
|
|
305
273
|
}
|
|
306
274
|
return ["a", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
307
275
|
},
|
|
308
276
|
addCommands() {
|
|
309
277
|
return {
|
|
310
278
|
setLink: (attributes) => ({ chain }) => {
|
|
279
|
+
const { href } = attributes;
|
|
280
|
+
if (!this.options.isAllowedUri(href, {
|
|
281
|
+
defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
|
|
282
|
+
protocols: this.options.protocols,
|
|
283
|
+
defaultProtocol: this.options.defaultProtocol
|
|
284
|
+
})) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
311
287
|
return chain().setMark(this.name, attributes).setMeta("preventAutolink", true).run();
|
|
312
288
|
},
|
|
313
289
|
toggleLink: (attributes) => ({ chain }) => {
|
|
290
|
+
const { href } = attributes;
|
|
291
|
+
if (!this.options.isAllowedUri(href, {
|
|
292
|
+
defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
|
|
293
|
+
protocols: this.options.protocols,
|
|
294
|
+
defaultProtocol: this.options.defaultProtocol
|
|
295
|
+
})) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
314
298
|
return chain().toggleMark(this.name, attributes, { extendEmptyMarkRange: true }).setMeta("preventAutolink", true).run();
|
|
315
299
|
},
|
|
316
300
|
unsetLink: () => ({ chain }) => {
|
|
@@ -325,7 +309,7 @@ var Link = Mark.create({
|
|
|
325
309
|
const foundLinks = [];
|
|
326
310
|
if (text) {
|
|
327
311
|
const { protocols, defaultProtocol } = this.options;
|
|
328
|
-
const links = find2(text
|
|
312
|
+
const links = find2(text).filter(
|
|
329
313
|
(item) => item.isLink && this.options.isAllowedUri(item.value, {
|
|
330
314
|
defaultValidate: (href) => !!isAllowedUri(href, protocols),
|
|
331
315
|
protocols,
|
|
@@ -333,13 +317,15 @@ var Link = Mark.create({
|
|
|
333
317
|
})
|
|
334
318
|
);
|
|
335
319
|
if (links.length) {
|
|
336
|
-
links.forEach(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
320
|
+
links.forEach(
|
|
321
|
+
(link) => foundLinks.push({
|
|
322
|
+
text: link.value,
|
|
323
|
+
data: {
|
|
324
|
+
href: link.href
|
|
325
|
+
},
|
|
326
|
+
index: link.start
|
|
327
|
+
})
|
|
328
|
+
);
|
|
343
329
|
}
|
|
344
330
|
}
|
|
345
331
|
return foundLinks;
|
|
@@ -392,10 +378,11 @@ var Link = Mark.create({
|
|
|
392
378
|
});
|
|
393
379
|
|
|
394
380
|
// src/index.ts
|
|
395
|
-
var
|
|
381
|
+
var index_default = Link;
|
|
396
382
|
export {
|
|
397
383
|
Link,
|
|
398
|
-
|
|
384
|
+
index_default as default,
|
|
385
|
+
isAllowedUri,
|
|
399
386
|
pasteRegex
|
|
400
387
|
};
|
|
401
388
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/link.ts","../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts","../src/index.ts"],"sourcesContent":["import {\n Mark, markPasteRule, mergeAttributes, PasteRuleMatch,\n} from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { find, registerCustomProtocol, reset } from 'linkifyjs'\n\nimport { autolink } from './helpers/autolink.js'\nimport { clickHandler } from './helpers/clickHandler.js'\nimport { pasteHandler } from './helpers/pasteHandler.js'\n\nexport interface LinkProtocolOptions {\n /**\n * The protocol scheme to be registered.\n * @default '''\n * @example 'ftp'\n * @example 'git'\n */\n scheme: string;\n\n /**\n * If enabled, it allows optional slashes after the protocol.\n * @default false\n * @example true\n */\n optionalSlashes?: boolean;\n}\n\nexport const pasteRegex = /https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z]{2,}\\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi\n\n/**\n * @deprecated The default behavior is now to open links when the editor is not editable.\n */\ntype DeprecatedOpenWhenNotEditable = 'whenNotEditable';\n\nexport interface LinkOptions {\n /**\n * If enabled, the extension will automatically add links as you type.\n * @default true\n * @example false\n */\n autolink: boolean;\n\n /**\n * An array of custom protocols to be registered with linkifyjs.\n * @default []\n * @example ['ftp', 'git']\n */\n protocols: Array<LinkProtocolOptions | string>;\n\n /**\n * Default protocol to use when no protocol is specified.\n * @default 'http'\n */\n defaultProtocol: string;\n /**\n * If enabled, links will be opened on click.\n * @default true\n * @example false\n */\n openOnClick: boolean | DeprecatedOpenWhenNotEditable;\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n * @default true\n * @example false\n */\n linkOnPaste: boolean;\n\n /**\n * HTML attributes to add to the link element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n\n /**\n * @deprecated Use the `shouldAutoLink` option instead.\n * A validation function that modifies link verification for the auto linker.\n * @param url - The url to be validated.\n * @returns - True if the url is valid, false otherwise.\n */\n validate: (url: string) => boolean;\n\n /**\n * A validation function which is used for configuring link verification for preventing XSS attacks.\n * Only modify this if you know what you're doing.\n *\n * @returns {boolean} `true` if the URL is valid, `false` otherwise.\n *\n * @example\n * isAllowedUri: (url, { defaultValidate, protocols, defaultProtocol }) => {\n * return url.startsWith('./') || defaultValidate(url)\n * }\n */\n isAllowedUri: (\n /**\n * The URL to be validated.\n */\n url: string,\n ctx: {\n /**\n * The default validation function.\n */\n defaultValidate: (url: string) => boolean;\n /**\n * An array of allowed protocols for the URL (e.g., \"http\", \"https\"). As defined in the `protocols` option.\n */\n protocols: Array<LinkProtocolOptions | string>;\n /**\n * A string that represents the default protocol (e.g., 'http'). As defined in the `defaultProtocol` option.\n */\n defaultProtocol: string;\n }\n ) => boolean;\n\n /**\n * Determines whether a valid link should be automatically linked in the content.\n *\n * @param {string} url - The URL that has already been validated.\n * @returns {boolean} - True if the link should be auto-linked; false if it should not be auto-linked.\n */\n shouldAutoLink: (url: string) => boolean;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n * @param attributes The link attributes\n * @example editor.commands.setLink({ href: 'https://tiptap.dev' })\n */\n setLink: (attributes: {\n href: string;\n target?: string | null;\n rel?: string | null;\n class?: string | null;\n }) => ReturnType;\n /**\n * Toggle a link mark\n * @param attributes The link attributes\n * @example editor.commands.toggleLink({ href: 'https://tiptap.dev' })\n */\n toggleLink: (attributes: {\n href: string;\n target?: string | null;\n rel?: string | null;\n class?: string | null;\n }) => ReturnType;\n /**\n * Unset a link mark\n * @example editor.commands.unsetLink()\n */\n unsetLink: () => ReturnType;\n };\n }\n}\n\n// From DOMPurify\n// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js\n// eslint-disable-next-line no-control-regex\nconst ATTR_WHITESPACE = /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g\n\nfunction isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {\n const allowedProtocols: string[] = [\n 'http',\n 'https',\n 'ftp',\n 'ftps',\n 'mailto',\n 'tel',\n 'callto',\n 'sms',\n 'cid',\n 'xmpp',\n ]\n\n if (protocols) {\n protocols.forEach(protocol => {\n const nextProtocol = typeof protocol === 'string' ? protocol : protocol.scheme\n\n if (nextProtocol) {\n allowedProtocols.push(nextProtocol)\n }\n })\n }\n\n return (\n !uri\n || uri\n .replace(ATTR_WHITESPACE, '')\n .match(\n new RegExp(\n // eslint-disable-next-line no-useless-escape\n `^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))`,\n 'i',\n ),\n )\n )\n}\n\n/**\n * This extension allows you to create links.\n * @see https://www.tiptap.dev/api/marks/link\n */\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n exitable: true,\n\n onCreate() {\n if (this.options.validate && !this.options.shouldAutoLink) {\n // Copy the validate function to the shouldAutoLink option\n this.options.shouldAutoLink = this.options.validate\n console.warn(\n 'The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.',\n )\n }\n this.options.protocols.forEach(protocol => {\n if (typeof protocol === 'string') {\n registerCustomProtocol(protocol)\n return\n }\n registerCustomProtocol(protocol.scheme, protocol.optionalSlashes)\n })\n },\n\n onDestroy() {\n reset()\n },\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n protocols: [],\n defaultProtocol: 'http',\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n },\n isAllowedUri: (url, ctx) => !!isAllowedUri(url, ctx.protocols),\n validate: url => !!url,\n shouldAutoLink: url => !!url,\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n parseHTML(element) {\n return element.getAttribute('href')\n },\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n rel: {\n default: this.options.HTMLAttributes.rel,\n },\n class: {\n default: this.options.HTMLAttributes.class,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'a[href]',\n getAttrs: dom => {\n const href = (dom as HTMLElement).getAttribute('href')\n\n // prevent XSS attacks\n if (\n !href\n || !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n return null\n },\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n // prevent XSS attacks\n if (\n !this.options.isAllowedUri(HTMLAttributes.href, {\n defaultValidate: href => !!isAllowedUri(href, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n // strip out the href\n return [\n 'a',\n mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }),\n 0,\n ]\n }\n\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink:\n attributes => ({ chain }) => {\n return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()\n },\n\n toggleLink:\n attributes => ({ chain }) => {\n return chain()\n .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n\n unsetLink:\n () => ({ chain }) => {\n return chain()\n .unsetMark(this.name, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => {\n const foundLinks: PasteRuleMatch[] = []\n\n if (text) {\n const { protocols, defaultProtocol } = this.options\n // Prosemirror replaces zero-width non-joiner characters\n // with Object Replacement Character from unicode.\n // Therefore, linkifyjs does not recognize the\n // link properly. We replace these characters\n // with regular spaces to fix this issue.\n const links = find(text.replaceAll('\\uFFFC', ' ')).filter(\n item => item.isLink\n && this.options.isAllowedUri(item.value, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n )\n\n if (links.length) {\n links.forEach(link => foundLinks.push({\n text: link.value,\n data: {\n href: link.href,\n },\n index: link.start,\n }))\n }\n }\n\n return foundLinks\n },\n type: this.type,\n getAttributes: match => {\n return {\n href: match.data?.href,\n }\n },\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins: Plugin[] = []\n const { protocols, defaultProtocol } = this.options\n\n if (this.options.autolink) {\n plugins.push(\n autolink({\n type: this.type,\n defaultProtocol: this.options.defaultProtocol,\n validate: url => this.options.isAllowedUri(url, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n shouldAutoLink: this.options.shouldAutoLink,\n }),\n )\n }\n\n if (this.options.openOnClick === true) {\n plugins.push(\n clickHandler({\n type: this.type,\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n pasteHandler({\n editor: this.editor,\n defaultProtocol: this.options.defaultProtocol,\n type: this.type,\n }),\n )\n }\n\n return plugins\n },\n})\n","import {\n combineTransactionSteps,\n findChildrenInRange,\n getChangedRanges,\n getMarksBetween,\n NodeWithPos,\n} from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { MultiToken, tokenize } from 'linkifyjs'\n\n/**\n * Check if the provided tokens form a valid link structure, which can either be a single link token\n * or a link token surrounded by parentheses or square brackets.\n *\n * This ensures that only complete and valid text is hyperlinked, preventing cases where a valid\n * top-level domain (TLD) is immediately followed by an invalid character, like a number. For\n * example, with the `find` method from Linkify, entering `example.com1` would result in\n * `example.com` being linked and the trailing `1` left as plain text. By using the `tokenize`\n * method, we can perform more comprehensive validation on the input text.\n */\nfunction isValidLinkStructure(tokens: Array<ReturnType<MultiToken['toObject']>>) {\n if (tokens.length === 1) {\n return tokens[0].isLink\n }\n\n if (tokens.length === 3 && tokens[1].isLink) {\n return ['()', '[]'].includes(tokens[0].value + tokens[2].value)\n }\n\n return false\n}\n\ntype AutolinkOptions = {\n type: MarkType\n defaultProtocol: string\n validate: (url: string) => boolean\n shouldAutoLink: (url: string) => boolean\n}\n\n/**\n * This plugin allows you to automatically add links to your editor.\n * @param options The plugin options\n * @returns The plugin instance\n */\nexport function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n /**\n * Does the transaction change the document?\n */\n const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n\n /**\n * Prevent autolink if the transaction is not a document change or if the transaction has the meta `preventAutolink`.\n */\n const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink'))\n\n /**\n * Prevent autolink if the transaction is not a document change\n * or if the transaction has the meta `preventAutolink`.\n */\n if (!docChanges || preventAutolink) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, [...transactions])\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n // Now let’s see if we can add new links.\n const nodesInChangedRanges = findChildrenInRange(\n newState.doc,\n newRange,\n node => node.isTextblock,\n )\n\n let textBlock: NodeWithPos | undefined\n let textBeforeWhitespace: string | undefined\n\n if (nodesInChangedRanges.length > 1) {\n // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n textBlock.pos + textBlock.node.nodeSize,\n undefined,\n ' ',\n )\n } else if (\n nodesInChangedRanges.length\n // We want to make sure to include the block seperator argument to treat hard breaks like spaces.\n && newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')\n ) {\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n newRange.to,\n undefined,\n ' ',\n )\n }\n\n if (textBlock && textBeforeWhitespace) {\n const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '')\n\n if (wordsBeforeWhitespace.length <= 0) {\n return false\n }\n\n const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1]\n const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace)\n\n if (!lastWordBeforeSpace) {\n return false\n }\n\n const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol))\n\n if (!isValidLinkStructure(linksBeforeSpace)) {\n return false\n }\n\n linksBeforeSpace\n .filter(link => link.isLink)\n // Calculate link position.\n .map(link => ({\n ...link,\n from: lastWordAndBlockOffset + link.start + 1,\n to: lastWordAndBlockOffset + link.end + 1,\n }))\n // ignore link inside code mark\n .filter(link => {\n if (!newState.schema.marks.code) {\n return true\n }\n\n return !newState.doc.rangeHasMark(\n link.from,\n link.to,\n newState.schema.marks.code,\n )\n })\n // validate link\n .filter(link => options.validate(link.value))\n // check whether should autolink\n .filter(link => options.shouldAutoLink(link.value))\n // Add link mark.\n .forEach(link => {\n if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) {\n return\n }\n\n tr.addMark(\n link.from,\n link.to,\n options.type.create({\n href: link.href,\n }),\n )\n })\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n return tr\n },\n })\n}\n","import { getAttributes } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\ntype ClickHandlerOptions = {\n type: MarkType;\n}\n\nexport function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return false\n }\n\n if (!view.editable) {\n return false\n }\n\n let a = event.target as HTMLElement\n const els = []\n\n while (a.nodeName !== 'DIV') {\n els.push(a)\n a = a.parentNode as HTMLElement\n }\n\n if (!els.find(value => value.nodeName === 'A')) {\n return false\n }\n\n const attrs = getAttributes(view.state, options.type.name)\n const link = (event.target as HTMLAnchorElement)\n\n const href = link?.href ?? attrs.href\n const target = link?.target ?? attrs.target\n\n if (link && href) {\n window.open(href, target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor\n defaultProtocol: string\n type: MarkType\n}\n\nexport function pasteHandler(options: PasteHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handlePasteLink'),\n props: {\n handlePaste: (view, event, slice) => {\n const { state } = view\n const { selection } = state\n const { empty } = selection\n\n if (empty) {\n return false\n }\n\n let textContent = ''\n\n slice.content.forEach(node => {\n textContent += node.textContent\n })\n\n const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(item => item.isLink && item.value === textContent)\n\n if (!textContent || !link) {\n return false\n }\n\n options.editor.commands.setMark(options.type, {\n href: link.href,\n })\n\n return true\n },\n },\n })\n}\n","import { Link } from './link.js'\n\nexport * from './link.js'\n\nexport default Link\n"],"mappings":"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","names":["find","Plugin","PluginKey","Plugin","PluginKey","find"]}
|
|
1
|
+
{"version":3,"sources":["../src/link.ts","../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts","../src/index.ts"],"sourcesContent":["import { Mark, markPasteRule, mergeAttributes, PasteRuleMatch } from '@tiptap/core'\nimport { Plugin } from '@tiptap/pm/state'\nimport { find, registerCustomProtocol, reset } from 'linkifyjs'\n\nimport { autolink } from './helpers/autolink.js'\nimport { clickHandler } from './helpers/clickHandler.js'\nimport { pasteHandler } from './helpers/pasteHandler.js'\n\nexport interface LinkProtocolOptions {\n /**\n * The protocol scheme to be registered.\n * @default '''\n * @example 'ftp'\n * @example 'git'\n */\n scheme: string\n\n /**\n * If enabled, it allows optional slashes after the protocol.\n * @default false\n * @example true\n */\n optionalSlashes?: boolean\n}\n\nexport const pasteRegex =\n /https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z]{2,}\\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi\n\n/**\n * @deprecated The default behavior is now to open links when the editor is not editable.\n */\ntype DeprecatedOpenWhenNotEditable = 'whenNotEditable'\n\nexport interface LinkOptions {\n /**\n * If enabled, the extension will automatically add links as you type.\n * @default true\n * @example false\n */\n autolink: boolean\n\n /**\n * An array of custom protocols to be registered with linkifyjs.\n * @default []\n * @example ['ftp', 'git']\n */\n protocols: Array<LinkProtocolOptions | string>\n\n /**\n * Default protocol to use when no protocol is specified.\n * @default 'http'\n */\n defaultProtocol: string\n /**\n * If enabled, links will be opened on click.\n * @default true\n * @example false\n */\n openOnClick: boolean | DeprecatedOpenWhenNotEditable\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n * @default true\n * @example false\n */\n linkOnPaste: boolean\n\n /**\n * HTML attributes to add to the link element.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n\n /**\n * @deprecated Use the `shouldAutoLink` option instead.\n * A validation function that modifies link verification for the auto linker.\n * @param url - The url to be validated.\n * @returns - True if the url is valid, false otherwise.\n */\n validate: (url: string) => boolean\n\n /**\n * A validation function which is used for configuring link verification for preventing XSS attacks.\n * Only modify this if you know what you're doing.\n *\n * @returns {boolean} `true` if the URL is valid, `false` otherwise.\n *\n * @example\n * isAllowedUri: (url, { defaultValidate, protocols, defaultProtocol }) => {\n * return url.startsWith('./') || defaultValidate(url)\n * }\n */\n isAllowedUri: (\n /**\n * The URL to be validated.\n */\n url: string,\n ctx: {\n /**\n * The default validation function.\n */\n defaultValidate: (url: string) => boolean\n /**\n * An array of allowed protocols for the URL (e.g., \"http\", \"https\"). As defined in the `protocols` option.\n */\n protocols: Array<LinkProtocolOptions | string>\n /**\n * A string that represents the default protocol (e.g., 'http'). As defined in the `defaultProtocol` option.\n */\n defaultProtocol: string\n },\n ) => boolean\n\n /**\n * Determines whether a valid link should be automatically linked in the content.\n *\n * @param {string} url - The URL that has already been validated.\n * @returns {boolean} - True if the link should be auto-linked; false if it should not be auto-linked.\n */\n shouldAutoLink: (url: string) => boolean\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n * @param attributes The link attributes\n * @example editor.commands.setLink({ href: 'https://tiptap.dev' })\n */\n setLink: (attributes: {\n href: string\n target?: string | null\n rel?: string | null\n class?: string | null\n }) => ReturnType\n /**\n * Toggle a link mark\n * @param attributes The link attributes\n * @example editor.commands.toggleLink({ href: 'https://tiptap.dev' })\n */\n toggleLink: (attributes: {\n href: string\n target?: string | null\n rel?: string | null\n class?: string | null\n }) => ReturnType\n /**\n * Unset a link mark\n * @example editor.commands.unsetLink()\n */\n unsetLink: () => ReturnType\n }\n }\n}\n\n// From DOMPurify\n// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js\n// eslint-disable-next-line no-control-regex\nconst ATTR_WHITESPACE = /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g\n\nexport function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {\n const allowedProtocols: string[] = ['http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp']\n\n if (protocols) {\n protocols.forEach(protocol => {\n const nextProtocol = typeof protocol === 'string' ? protocol : protocol.scheme\n\n if (nextProtocol) {\n allowedProtocols.push(nextProtocol)\n }\n })\n }\n\n return (\n !uri ||\n uri.replace(ATTR_WHITESPACE, '').match(\n new RegExp(\n // eslint-disable-next-line no-useless-escape\n `^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))`,\n 'i',\n ),\n )\n )\n}\n\n/**\n * This extension allows you to create links.\n * @see https://www.tiptap.dev/api/marks/link\n */\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n exitable: true,\n\n onCreate() {\n if (this.options.validate && !this.options.shouldAutoLink) {\n // Copy the validate function to the shouldAutoLink option\n this.options.shouldAutoLink = this.options.validate\n console.warn('The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.')\n }\n this.options.protocols.forEach(protocol => {\n if (typeof protocol === 'string') {\n registerCustomProtocol(protocol)\n return\n }\n registerCustomProtocol(protocol.scheme, protocol.optionalSlashes)\n })\n },\n\n onDestroy() {\n reset()\n },\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n protocols: [],\n defaultProtocol: 'http',\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n },\n isAllowedUri: (url, ctx) => !!isAllowedUri(url, ctx.protocols),\n validate: url => !!url,\n shouldAutoLink: url => !!url,\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n parseHTML(element) {\n return element.getAttribute('href')\n },\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n rel: {\n default: this.options.HTMLAttributes.rel,\n },\n class: {\n default: this.options.HTMLAttributes.class,\n },\n }\n },\n\n parseHTML() {\n return [\n {\n tag: 'a[href]',\n getAttrs: dom => {\n const href = (dom as HTMLElement).getAttribute('href')\n\n // prevent XSS attacks\n if (\n !href ||\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n return null\n },\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n // prevent XSS attacks\n if (\n !this.options.isAllowedUri(HTMLAttributes.href, {\n defaultValidate: href => !!isAllowedUri(href, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n // strip out the href\n return ['a', mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }), 0]\n }\n\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink:\n attributes =>\n ({ chain }) => {\n const { href } = attributes\n\n if (\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n\n return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()\n },\n\n toggleLink:\n attributes =>\n ({ chain }) => {\n const { href } = attributes\n\n if (\n !this.options.isAllowedUri(href, {\n defaultValidate: url => !!isAllowedUri(url, this.options.protocols),\n protocols: this.options.protocols,\n defaultProtocol: this.options.defaultProtocol,\n })\n ) {\n return false\n }\n\n return chain()\n .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n .setMeta('preventAutolink', true)\n .run()\n },\n\n unsetLink:\n () =>\n ({ chain }) => {\n return chain().unsetMark(this.name, { extendEmptyMarkRange: true }).setMeta('preventAutolink', true).run()\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => {\n const foundLinks: PasteRuleMatch[] = []\n\n if (text) {\n const { protocols, defaultProtocol } = this.options\n const links = find(text).filter(\n item =>\n item.isLink &&\n this.options.isAllowedUri(item.value, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n )\n\n if (links.length) {\n links.forEach(link =>\n foundLinks.push({\n text: link.value,\n data: {\n href: link.href,\n },\n index: link.start,\n }),\n )\n }\n }\n\n return foundLinks\n },\n type: this.type,\n getAttributes: match => {\n return {\n href: match.data?.href,\n }\n },\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins: Plugin[] = []\n const { protocols, defaultProtocol } = this.options\n\n if (this.options.autolink) {\n plugins.push(\n autolink({\n type: this.type,\n defaultProtocol: this.options.defaultProtocol,\n validate: url =>\n this.options.isAllowedUri(url, {\n defaultValidate: href => !!isAllowedUri(href, protocols),\n protocols,\n defaultProtocol,\n }),\n shouldAutoLink: this.options.shouldAutoLink,\n }),\n )\n }\n\n if (this.options.openOnClick === true) {\n plugins.push(\n clickHandler({\n type: this.type,\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n pasteHandler({\n editor: this.editor,\n defaultProtocol: this.options.defaultProtocol,\n type: this.type,\n }),\n )\n }\n\n return plugins\n },\n})\n","import {\n combineTransactionSteps,\n findChildrenInRange,\n getChangedRanges,\n getMarksBetween,\n NodeWithPos,\n} from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { MultiToken, tokenize } from 'linkifyjs'\n\n/**\n * Check if the provided tokens form a valid link structure, which can either be a single link token\n * or a link token surrounded by parentheses or square brackets.\n *\n * This ensures that only complete and valid text is hyperlinked, preventing cases where a valid\n * top-level domain (TLD) is immediately followed by an invalid character, like a number. For\n * example, with the `find` method from Linkify, entering `example.com1` would result in\n * `example.com` being linked and the trailing `1` left as plain text. By using the `tokenize`\n * method, we can perform more comprehensive validation on the input text.\n */\nfunction isValidLinkStructure(tokens: Array<ReturnType<MultiToken['toObject']>>) {\n if (tokens.length === 1) {\n return tokens[0].isLink\n }\n\n if (tokens.length === 3 && tokens[1].isLink) {\n return ['()', '[]'].includes(tokens[0].value + tokens[2].value)\n }\n\n return false\n}\n\ntype AutolinkOptions = {\n type: MarkType\n defaultProtocol: string\n validate: (url: string) => boolean\n shouldAutoLink: (url: string) => boolean\n}\n\n/**\n * This plugin allows you to automatically add links to your editor.\n * @param options The plugin options\n * @returns The plugin instance\n */\nexport function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n /**\n * Does the transaction change the document?\n */\n const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n\n /**\n * Prevent autolink if the transaction is not a document change or if the transaction has the meta `preventAutolink`.\n */\n const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink'))\n\n /**\n * Prevent autolink if the transaction is not a document change\n * or if the transaction has the meta `preventAutolink`.\n */\n if (!docChanges || preventAutolink) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, [...transactions])\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n // Now let’s see if we can add new links.\n const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, node => node.isTextblock)\n\n let textBlock: NodeWithPos | undefined\n let textBeforeWhitespace: string | undefined\n\n if (nodesInChangedRanges.length > 1) {\n // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(\n textBlock.pos,\n textBlock.pos + textBlock.node.nodeSize,\n undefined,\n ' ',\n )\n } else if (\n nodesInChangedRanges.length &&\n // We want to make sure to include the block seperator argument to treat hard breaks like spaces.\n newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')\n ) {\n textBlock = nodesInChangedRanges[0]\n textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, undefined, ' ')\n }\n\n if (textBlock && textBeforeWhitespace) {\n const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '')\n\n if (wordsBeforeWhitespace.length <= 0) {\n return false\n }\n\n const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1]\n const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace)\n\n if (!lastWordBeforeSpace) {\n return false\n }\n\n const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol))\n\n if (!isValidLinkStructure(linksBeforeSpace)) {\n return false\n }\n\n linksBeforeSpace\n .filter(link => link.isLink)\n // Calculate link position.\n .map(link => ({\n ...link,\n from: lastWordAndBlockOffset + link.start + 1,\n to: lastWordAndBlockOffset + link.end + 1,\n }))\n // ignore link inside code mark\n .filter(link => {\n if (!newState.schema.marks.code) {\n return true\n }\n\n return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code)\n })\n // validate link\n .filter(link => options.validate(link.value))\n // check whether should autolink\n .filter(link => options.shouldAutoLink(link.value))\n // Add link mark.\n .forEach(link => {\n if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) {\n return\n }\n\n tr.addMark(\n link.from,\n link.to,\n options.type.create({\n href: link.href,\n }),\n )\n })\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n return tr\n },\n })\n}\n","import { getAttributes } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\ntype ClickHandlerOptions = {\n type: MarkType\n}\n\nexport function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n if (event.button !== 0) {\n return false\n }\n\n if (!view.editable) {\n return false\n }\n\n let a = event.target as HTMLElement\n const els = []\n\n while (a.nodeName !== 'DIV') {\n els.push(a)\n a = a.parentNode as HTMLElement\n }\n\n if (!els.find(value => value.nodeName === 'A')) {\n return false\n }\n\n const attrs = getAttributes(view.state, options.type.name)\n const link = event.target as HTMLAnchorElement\n\n const href = link?.href ?? attrs.href\n const target = link?.target ?? attrs.target\n\n if (link && href) {\n window.open(href, target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor\n defaultProtocol: string\n type: MarkType\n}\n\nexport function pasteHandler(options: PasteHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handlePasteLink'),\n props: {\n handlePaste: (view, event, slice) => {\n const { state } = view\n const { selection } = state\n const { empty } = selection\n\n if (empty) {\n return false\n }\n\n let textContent = ''\n\n slice.content.forEach(node => {\n textContent += node.textContent\n })\n\n const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(\n item => item.isLink && item.value === textContent,\n )\n\n if (!textContent || !link) {\n return false\n }\n\n return options.editor.commands.setMark(options.type, {\n href: link.href,\n })\n },\n },\n })\n}\n","import { Link } from './link.js'\n\nexport * from './link.js'\n\nexport default Link\n"],"mappings":"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","names":["find","Plugin","PluginKey","Plugin","PluginKey","find"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/extension-link",
|
|
3
3
|
"description": "link extension for tiptap",
|
|
4
|
-
"version": "3.0.0-next.
|
|
4
|
+
"version": "3.0.0-next.5",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"types":
|
|
18
|
+
"types": {
|
|
19
|
+
"import": "./dist/index.d.ts",
|
|
20
|
+
"require": "./dist/index.d.cts"
|
|
21
|
+
},
|
|
19
22
|
"import": "./dist/index.js",
|
|
20
23
|
"require": "./dist/index.cjs"
|
|
21
24
|
}
|
|
@@ -28,11 +31,11 @@
|
|
|
28
31
|
"dist"
|
|
29
32
|
],
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"linkifyjs": "^4.
|
|
34
|
+
"linkifyjs": "^4.2.0"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
|
-
"@tiptap/core": "^3.0.0-next.
|
|
35
|
-
"@tiptap/pm": "^3.0.0-next.
|
|
37
|
+
"@tiptap/core": "^3.0.0-next.5",
|
|
38
|
+
"@tiptap/pm": "^3.0.0-next.5"
|
|
36
39
|
},
|
|
37
40
|
"peerDependencies": {
|
|
38
41
|
"@tiptap/core": "^3.0.0-next.1",
|
|
@@ -44,6 +47,7 @@
|
|
|
44
47
|
"directory": "packages/extension-link"
|
|
45
48
|
},
|
|
46
49
|
"scripts": {
|
|
47
|
-
"build": "tsup"
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
|
|
48
52
|
}
|
|
49
|
-
}
|
|
53
|
+
}
|
package/src/helpers/autolink.ts
CHANGED
|
@@ -71,11 +71,7 @@ export function autolink(options: AutolinkOptions): Plugin {
|
|
|
71
71
|
|
|
72
72
|
changes.forEach(({ newRange }) => {
|
|
73
73
|
// Now let’s see if we can add new links.
|
|
74
|
-
const nodesInChangedRanges = findChildrenInRange(
|
|
75
|
-
newState.doc,
|
|
76
|
-
newRange,
|
|
77
|
-
node => node.isTextblock,
|
|
78
|
-
)
|
|
74
|
+
const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
|
|
79
75
|
|
|
80
76
|
let textBlock: NodeWithPos | undefined
|
|
81
77
|
let textBeforeWhitespace: string | undefined
|
|
@@ -90,17 +86,12 @@ export function autolink(options: AutolinkOptions): Plugin {
|
|
|
90
86
|
' ',
|
|
91
87
|
)
|
|
92
88
|
} else if (
|
|
93
|
-
nodesInChangedRanges.length
|
|
89
|
+
nodesInChangedRanges.length &&
|
|
94
90
|
// We want to make sure to include the block seperator argument to treat hard breaks like spaces.
|
|
95
|
-
|
|
91
|
+
newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')
|
|
96
92
|
) {
|
|
97
93
|
textBlock = nodesInChangedRanges[0]
|
|
98
|
-
textBeforeWhitespace = newState.doc.textBetween(
|
|
99
|
-
textBlock.pos,
|
|
100
|
-
newRange.to,
|
|
101
|
-
undefined,
|
|
102
|
-
' ',
|
|
103
|
-
)
|
|
94
|
+
textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, undefined, ' ')
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
if (textBlock && textBeforeWhitespace) {
|
|
@@ -137,11 +128,7 @@ export function autolink(options: AutolinkOptions): Plugin {
|
|
|
137
128
|
return true
|
|
138
129
|
}
|
|
139
130
|
|
|
140
|
-
return !newState.doc.rangeHasMark(
|
|
141
|
-
link.from,
|
|
142
|
-
link.to,
|
|
143
|
-
newState.schema.marks.code,
|
|
144
|
-
)
|
|
131
|
+
return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code)
|
|
145
132
|
})
|
|
146
133
|
// validate link
|
|
147
134
|
.filter(link => options.validate(link.value))
|
|
@@ -3,7 +3,7 @@ import { MarkType } from '@tiptap/pm/model'
|
|
|
3
3
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
4
4
|
|
|
5
5
|
type ClickHandlerOptions = {
|
|
6
|
-
type: MarkType
|
|
6
|
+
type: MarkType
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function clickHandler(options: ClickHandlerOptions): Plugin {
|
|
@@ -32,7 +32,7 @@ export function clickHandler(options: ClickHandlerOptions): Plugin {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const attrs = getAttributes(view.state, options.type.name)
|
|
35
|
-
const link =
|
|
35
|
+
const link = event.target as HTMLAnchorElement
|
|
36
36
|
|
|
37
37
|
const href = link?.href ?? attrs.href
|
|
38
38
|
const target = link?.target ?? attrs.target
|
|
@@ -28,17 +28,17 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
|
|
|
28
28
|
textContent += node.textContent
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
-
const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
31
|
+
const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(
|
|
32
|
+
item => item.isLink && item.value === textContent,
|
|
33
|
+
)
|
|
32
34
|
|
|
33
35
|
if (!textContent || !link) {
|
|
34
36
|
return false
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
options.editor.commands.setMark(options.type, {
|
|
39
|
+
return options.editor.commands.setMark(options.type, {
|
|
38
40
|
href: link.href,
|
|
39
41
|
})
|
|
40
|
-
|
|
41
|
-
return true
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
})
|
package/src/link.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Mark, markPasteRule, mergeAttributes, PasteRuleMatch,
|
|
3
|
-
} from '@tiptap/core'
|
|
1
|
+
import { Mark, markPasteRule, mergeAttributes, PasteRuleMatch } from '@tiptap/core'
|
|
4
2
|
import { Plugin } from '@tiptap/pm/state'
|
|
5
3
|
import { find, registerCustomProtocol, reset } from 'linkifyjs'
|
|
6
4
|
|
|
@@ -15,22 +13,23 @@ export interface LinkProtocolOptions {
|
|
|
15
13
|
* @example 'ftp'
|
|
16
14
|
* @example 'git'
|
|
17
15
|
*/
|
|
18
|
-
scheme: string
|
|
16
|
+
scheme: string
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* If enabled, it allows optional slashes after the protocol.
|
|
22
20
|
* @default false
|
|
23
21
|
* @example true
|
|
24
22
|
*/
|
|
25
|
-
optionalSlashes?: boolean
|
|
23
|
+
optionalSlashes?: boolean
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
export const pasteRegex =
|
|
26
|
+
export const pasteRegex =
|
|
27
|
+
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* @deprecated The default behavior is now to open links when the editor is not editable.
|
|
32
31
|
*/
|
|
33
|
-
type DeprecatedOpenWhenNotEditable = 'whenNotEditable'
|
|
32
|
+
type DeprecatedOpenWhenNotEditable = 'whenNotEditable'
|
|
34
33
|
|
|
35
34
|
export interface LinkOptions {
|
|
36
35
|
/**
|
|
@@ -38,39 +37,39 @@ export interface LinkOptions {
|
|
|
38
37
|
* @default true
|
|
39
38
|
* @example false
|
|
40
39
|
*/
|
|
41
|
-
autolink: boolean
|
|
40
|
+
autolink: boolean
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
43
|
* An array of custom protocols to be registered with linkifyjs.
|
|
45
44
|
* @default []
|
|
46
45
|
* @example ['ftp', 'git']
|
|
47
46
|
*/
|
|
48
|
-
protocols: Array<LinkProtocolOptions | string
|
|
47
|
+
protocols: Array<LinkProtocolOptions | string>
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
50
|
* Default protocol to use when no protocol is specified.
|
|
52
51
|
* @default 'http'
|
|
53
52
|
*/
|
|
54
|
-
defaultProtocol: string
|
|
53
|
+
defaultProtocol: string
|
|
55
54
|
/**
|
|
56
55
|
* If enabled, links will be opened on click.
|
|
57
56
|
* @default true
|
|
58
57
|
* @example false
|
|
59
58
|
*/
|
|
60
|
-
openOnClick: boolean | DeprecatedOpenWhenNotEditable
|
|
59
|
+
openOnClick: boolean | DeprecatedOpenWhenNotEditable
|
|
61
60
|
/**
|
|
62
61
|
* Adds a link to the current selection if the pasted content only contains an url.
|
|
63
62
|
* @default true
|
|
64
63
|
* @example false
|
|
65
64
|
*/
|
|
66
|
-
linkOnPaste: boolean
|
|
65
|
+
linkOnPaste: boolean
|
|
67
66
|
|
|
68
67
|
/**
|
|
69
68
|
* HTML attributes to add to the link element.
|
|
70
69
|
* @default {}
|
|
71
70
|
* @example { class: 'foo' }
|
|
72
71
|
*/
|
|
73
|
-
HTMLAttributes: Record<string, any
|
|
72
|
+
HTMLAttributes: Record<string, any>
|
|
74
73
|
|
|
75
74
|
/**
|
|
76
75
|
* @deprecated Use the `shouldAutoLink` option instead.
|
|
@@ -78,7 +77,7 @@ export interface LinkOptions {
|
|
|
78
77
|
* @param url - The url to be validated.
|
|
79
78
|
* @returns - True if the url is valid, false otherwise.
|
|
80
79
|
*/
|
|
81
|
-
validate: (url: string) => boolean
|
|
80
|
+
validate: (url: string) => boolean
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* A validation function which is used for configuring link verification for preventing XSS attacks.
|
|
@@ -100,17 +99,17 @@ export interface LinkOptions {
|
|
|
100
99
|
/**
|
|
101
100
|
* The default validation function.
|
|
102
101
|
*/
|
|
103
|
-
defaultValidate: (url: string) => boolean
|
|
102
|
+
defaultValidate: (url: string) => boolean
|
|
104
103
|
/**
|
|
105
104
|
* An array of allowed protocols for the URL (e.g., "http", "https"). As defined in the `protocols` option.
|
|
106
105
|
*/
|
|
107
|
-
protocols: Array<LinkProtocolOptions | string
|
|
106
|
+
protocols: Array<LinkProtocolOptions | string>
|
|
108
107
|
/**
|
|
109
108
|
* A string that represents the default protocol (e.g., 'http'). As defined in the `defaultProtocol` option.
|
|
110
109
|
*/
|
|
111
|
-
defaultProtocol: string
|
|
112
|
-
}
|
|
113
|
-
) => boolean
|
|
110
|
+
defaultProtocol: string
|
|
111
|
+
},
|
|
112
|
+
) => boolean
|
|
114
113
|
|
|
115
114
|
/**
|
|
116
115
|
* Determines whether a valid link should be automatically linked in the content.
|
|
@@ -118,7 +117,7 @@ export interface LinkOptions {
|
|
|
118
117
|
* @param {string} url - The URL that has already been validated.
|
|
119
118
|
* @returns {boolean} - True if the link should be auto-linked; false if it should not be auto-linked.
|
|
120
119
|
*/
|
|
121
|
-
shouldAutoLink: (url: string) => boolean
|
|
120
|
+
shouldAutoLink: (url: string) => boolean
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
declare module '@tiptap/core' {
|
|
@@ -130,28 +129,28 @@ declare module '@tiptap/core' {
|
|
|
130
129
|
* @example editor.commands.setLink({ href: 'https://tiptap.dev' })
|
|
131
130
|
*/
|
|
132
131
|
setLink: (attributes: {
|
|
133
|
-
href: string
|
|
134
|
-
target?: string | null
|
|
135
|
-
rel?: string | null
|
|
136
|
-
class?: string | null
|
|
137
|
-
}) => ReturnType
|
|
132
|
+
href: string
|
|
133
|
+
target?: string | null
|
|
134
|
+
rel?: string | null
|
|
135
|
+
class?: string | null
|
|
136
|
+
}) => ReturnType
|
|
138
137
|
/**
|
|
139
138
|
* Toggle a link mark
|
|
140
139
|
* @param attributes The link attributes
|
|
141
140
|
* @example editor.commands.toggleLink({ href: 'https://tiptap.dev' })
|
|
142
141
|
*/
|
|
143
142
|
toggleLink: (attributes: {
|
|
144
|
-
href: string
|
|
145
|
-
target?: string | null
|
|
146
|
-
rel?: string | null
|
|
147
|
-
class?: string | null
|
|
148
|
-
}) => ReturnType
|
|
143
|
+
href: string
|
|
144
|
+
target?: string | null
|
|
145
|
+
rel?: string | null
|
|
146
|
+
class?: string | null
|
|
147
|
+
}) => ReturnType
|
|
149
148
|
/**
|
|
150
149
|
* Unset a link mark
|
|
151
150
|
* @example editor.commands.unsetLink()
|
|
152
151
|
*/
|
|
153
|
-
unsetLink: () => ReturnType
|
|
154
|
-
}
|
|
152
|
+
unsetLink: () => ReturnType
|
|
153
|
+
}
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
|
|
@@ -160,19 +159,8 @@ declare module '@tiptap/core' {
|
|
|
160
159
|
// eslint-disable-next-line no-control-regex
|
|
161
160
|
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
|
|
162
161
|
|
|
163
|
-
function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {
|
|
164
|
-
const allowedProtocols: string[] = [
|
|
165
|
-
'http',
|
|
166
|
-
'https',
|
|
167
|
-
'ftp',
|
|
168
|
-
'ftps',
|
|
169
|
-
'mailto',
|
|
170
|
-
'tel',
|
|
171
|
-
'callto',
|
|
172
|
-
'sms',
|
|
173
|
-
'cid',
|
|
174
|
-
'xmpp',
|
|
175
|
-
]
|
|
162
|
+
export function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {
|
|
163
|
+
const allowedProtocols: string[] = ['http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'callto', 'sms', 'cid', 'xmpp']
|
|
176
164
|
|
|
177
165
|
if (protocols) {
|
|
178
166
|
protocols.forEach(protocol => {
|
|
@@ -185,16 +173,14 @@ function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocol
|
|
|
185
173
|
}
|
|
186
174
|
|
|
187
175
|
return (
|
|
188
|
-
!uri
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
),
|
|
197
|
-
)
|
|
176
|
+
!uri ||
|
|
177
|
+
uri.replace(ATTR_WHITESPACE, '').match(
|
|
178
|
+
new RegExp(
|
|
179
|
+
// eslint-disable-next-line no-useless-escape
|
|
180
|
+
`^(?:(?:${allowedProtocols.join('|')}):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))`,
|
|
181
|
+
'i',
|
|
182
|
+
),
|
|
183
|
+
)
|
|
198
184
|
)
|
|
199
185
|
}
|
|
200
186
|
|
|
@@ -215,9 +201,7 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
215
201
|
if (this.options.validate && !this.options.shouldAutoLink) {
|
|
216
202
|
// Copy the validate function to the shouldAutoLink option
|
|
217
203
|
this.options.shouldAutoLink = this.options.validate
|
|
218
|
-
console.warn(
|
|
219
|
-
'The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.',
|
|
220
|
-
)
|
|
204
|
+
console.warn('The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.')
|
|
221
205
|
}
|
|
222
206
|
this.options.protocols.forEach(protocol => {
|
|
223
207
|
if (typeof protocol === 'string') {
|
|
@@ -283,8 +267,8 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
283
267
|
|
|
284
268
|
// prevent XSS attacks
|
|
285
269
|
if (
|
|
286
|
-
!href
|
|
287
|
-
|
|
270
|
+
!href ||
|
|
271
|
+
!this.options.isAllowedUri(href, {
|
|
288
272
|
defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
|
|
289
273
|
protocols: this.options.protocols,
|
|
290
274
|
defaultProtocol: this.options.defaultProtocol,
|
|
@@ -308,11 +292,7 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
308
292
|
})
|
|
309
293
|
) {
|
|
310
294
|
// strip out the href
|
|
311
|
-
return [
|
|
312
|
-
'a',
|
|
313
|
-
mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }),
|
|
314
|
-
0,
|
|
315
|
-
]
|
|
295
|
+
return ['a', mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }), 0]
|
|
316
296
|
}
|
|
317
297
|
|
|
318
298
|
return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
|
@@ -321,12 +301,38 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
321
301
|
addCommands() {
|
|
322
302
|
return {
|
|
323
303
|
setLink:
|
|
324
|
-
attributes =>
|
|
304
|
+
attributes =>
|
|
305
|
+
({ chain }) => {
|
|
306
|
+
const { href } = attributes
|
|
307
|
+
|
|
308
|
+
if (
|
|
309
|
+
!this.options.isAllowedUri(href, {
|
|
310
|
+
defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
|
|
311
|
+
protocols: this.options.protocols,
|
|
312
|
+
defaultProtocol: this.options.defaultProtocol,
|
|
313
|
+
})
|
|
314
|
+
) {
|
|
315
|
+
return false
|
|
316
|
+
}
|
|
317
|
+
|
|
325
318
|
return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()
|
|
326
319
|
},
|
|
327
320
|
|
|
328
321
|
toggleLink:
|
|
329
|
-
attributes =>
|
|
322
|
+
attributes =>
|
|
323
|
+
({ chain }) => {
|
|
324
|
+
const { href } = attributes
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
!this.options.isAllowedUri(href, {
|
|
328
|
+
defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
|
|
329
|
+
protocols: this.options.protocols,
|
|
330
|
+
defaultProtocol: this.options.defaultProtocol,
|
|
331
|
+
})
|
|
332
|
+
) {
|
|
333
|
+
return false
|
|
334
|
+
}
|
|
335
|
+
|
|
330
336
|
return chain()
|
|
331
337
|
.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
|
|
332
338
|
.setMeta('preventAutolink', true)
|
|
@@ -334,11 +340,9 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
334
340
|
},
|
|
335
341
|
|
|
336
342
|
unsetLink:
|
|
337
|
-
() =>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
.setMeta('preventAutolink', true)
|
|
341
|
-
.run()
|
|
343
|
+
() =>
|
|
344
|
+
({ chain }) => {
|
|
345
|
+
return chain().unsetMark(this.name, { extendEmptyMarkRange: true }).setMeta('preventAutolink', true).run()
|
|
342
346
|
},
|
|
343
347
|
}
|
|
344
348
|
},
|
|
@@ -351,14 +355,10 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
351
355
|
|
|
352
356
|
if (text) {
|
|
353
357
|
const { protocols, defaultProtocol } = this.options
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// with regular spaces to fix this issue.
|
|
359
|
-
const links = find(text.replaceAll('\uFFFC', ' ')).filter(
|
|
360
|
-
item => item.isLink
|
|
361
|
-
&& this.options.isAllowedUri(item.value, {
|
|
358
|
+
const links = find(text).filter(
|
|
359
|
+
item =>
|
|
360
|
+
item.isLink &&
|
|
361
|
+
this.options.isAllowedUri(item.value, {
|
|
362
362
|
defaultValidate: href => !!isAllowedUri(href, protocols),
|
|
363
363
|
protocols,
|
|
364
364
|
defaultProtocol,
|
|
@@ -366,13 +366,15 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
366
366
|
)
|
|
367
367
|
|
|
368
368
|
if (links.length) {
|
|
369
|
-
links.forEach(link =>
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
369
|
+
links.forEach(link =>
|
|
370
|
+
foundLinks.push({
|
|
371
|
+
text: link.value,
|
|
372
|
+
data: {
|
|
373
|
+
href: link.href,
|
|
374
|
+
},
|
|
375
|
+
index: link.start,
|
|
376
|
+
}),
|
|
377
|
+
)
|
|
376
378
|
}
|
|
377
379
|
}
|
|
378
380
|
|
|
@@ -397,11 +399,12 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
397
399
|
autolink({
|
|
398
400
|
type: this.type,
|
|
399
401
|
defaultProtocol: this.options.defaultProtocol,
|
|
400
|
-
validate: url =>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
402
|
+
validate: url =>
|
|
403
|
+
this.options.isAllowedUri(url, {
|
|
404
|
+
defaultValidate: href => !!isAllowedUri(href, protocols),
|
|
405
|
+
protocols,
|
|
406
|
+
defaultProtocol,
|
|
407
|
+
}),
|
|
405
408
|
shouldAutoLink: this.options.shouldAutoLink,
|
|
406
409
|
}),
|
|
407
410
|
)
|