@tiptap/extension-link 3.0.0 → 3.0.2-beta.0

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.
Files changed (181) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +410 -319
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/{packages/extension-link/src/link.d.ts → index.d.cts} +57 -7
  6. package/dist/index.d.ts +149 -0
  7. package/dist/index.js +383 -315
  8. package/dist/index.js.map +1 -1
  9. package/package.json +14 -12
  10. package/src/helpers/autolink.ts +19 -31
  11. package/src/helpers/clickHandler.ts +25 -9
  12. package/src/helpers/pasteHandler.ts +6 -6
  13. package/src/helpers/whitespace.ts +7 -0
  14. package/src/link.ts +195 -47
  15. package/dist/index.umd.js +0 -344
  16. package/dist/index.umd.js.map +0 -1
  17. package/dist/packages/core/src/CommandManager.d.ts +0 -20
  18. package/dist/packages/core/src/Editor.d.ts +0 -161
  19. package/dist/packages/core/src/EventEmitter.d.ts +0 -11
  20. package/dist/packages/core/src/Extension.d.ts +0 -343
  21. package/dist/packages/core/src/ExtensionManager.d.ts +0 -55
  22. package/dist/packages/core/src/InputRule.d.ts +0 -42
  23. package/dist/packages/core/src/Mark.d.ts +0 -451
  24. package/dist/packages/core/src/Node.d.ts +0 -611
  25. package/dist/packages/core/src/NodePos.d.ts +0 -44
  26. package/dist/packages/core/src/NodeView.d.ts +0 -31
  27. package/dist/packages/core/src/PasteRule.d.ts +0 -50
  28. package/dist/packages/core/src/Tracker.d.ts +0 -11
  29. package/dist/packages/core/src/commands/blur.d.ts +0 -13
  30. package/dist/packages/core/src/commands/clearContent.d.ts +0 -14
  31. package/dist/packages/core/src/commands/clearNodes.d.ts +0 -13
  32. package/dist/packages/core/src/commands/command.d.ts +0 -18
  33. package/dist/packages/core/src/commands/createParagraphNear.d.ts +0 -13
  34. package/dist/packages/core/src/commands/cut.d.ts +0 -20
  35. package/dist/packages/core/src/commands/deleteCurrentNode.d.ts +0 -13
  36. package/dist/packages/core/src/commands/deleteNode.d.ts +0 -15
  37. package/dist/packages/core/src/commands/deleteRange.d.ts +0 -14
  38. package/dist/packages/core/src/commands/deleteSelection.d.ts +0 -13
  39. package/dist/packages/core/src/commands/enter.d.ts +0 -13
  40. package/dist/packages/core/src/commands/exitCode.d.ts +0 -13
  41. package/dist/packages/core/src/commands/extendMarkRange.d.ts +0 -25
  42. package/dist/packages/core/src/commands/first.d.ts +0 -14
  43. package/dist/packages/core/src/commands/focus.d.ts +0 -27
  44. package/dist/packages/core/src/commands/forEach.d.ts +0 -14
  45. package/dist/packages/core/src/commands/index.d.ts +0 -55
  46. package/dist/packages/core/src/commands/insertContent.d.ts +0 -34
  47. package/dist/packages/core/src/commands/insertContentAt.d.ts +0 -47
  48. package/dist/packages/core/src/commands/join.d.ts +0 -41
  49. package/dist/packages/core/src/commands/joinItemBackward.d.ts +0 -13
  50. package/dist/packages/core/src/commands/joinItemForward.d.ts +0 -13
  51. package/dist/packages/core/src/commands/joinTextblockBackward.d.ts +0 -12
  52. package/dist/packages/core/src/commands/joinTextblockForward.d.ts +0 -12
  53. package/dist/packages/core/src/commands/keyboardShortcut.d.ts +0 -14
  54. package/dist/packages/core/src/commands/lift.d.ts +0 -17
  55. package/dist/packages/core/src/commands/liftEmptyBlock.d.ts +0 -13
  56. package/dist/packages/core/src/commands/liftListItem.d.ts +0 -15
  57. package/dist/packages/core/src/commands/newlineInCode.d.ts +0 -13
  58. package/dist/packages/core/src/commands/resetAttributes.d.ts +0 -16
  59. package/dist/packages/core/src/commands/scrollIntoView.d.ts +0 -13
  60. package/dist/packages/core/src/commands/selectAll.d.ts +0 -13
  61. package/dist/packages/core/src/commands/selectNodeBackward.d.ts +0 -13
  62. package/dist/packages/core/src/commands/selectNodeForward.d.ts +0 -13
  63. package/dist/packages/core/src/commands/selectParentNode.d.ts +0 -13
  64. package/dist/packages/core/src/commands/selectTextblockEnd.d.ts +0 -13
  65. package/dist/packages/core/src/commands/selectTextblockStart.d.ts +0 -13
  66. package/dist/packages/core/src/commands/setContent.d.ts +0 -40
  67. package/dist/packages/core/src/commands/setMark.d.ts +0 -15
  68. package/dist/packages/core/src/commands/setMeta.d.ts +0 -15
  69. package/dist/packages/core/src/commands/setNode.d.ts +0 -16
  70. package/dist/packages/core/src/commands/setNodeSelection.d.ts +0 -14
  71. package/dist/packages/core/src/commands/setTextSelection.d.ts +0 -14
  72. package/dist/packages/core/src/commands/sinkListItem.d.ts +0 -15
  73. package/dist/packages/core/src/commands/splitBlock.d.ts +0 -17
  74. package/dist/packages/core/src/commands/splitListItem.d.ts +0 -15
  75. package/dist/packages/core/src/commands/toggleList.d.ts +0 -18
  76. package/dist/packages/core/src/commands/toggleMark.d.ts +0 -30
  77. package/dist/packages/core/src/commands/toggleNode.d.ts +0 -17
  78. package/dist/packages/core/src/commands/toggleWrap.d.ts +0 -16
  79. package/dist/packages/core/src/commands/undoInputRule.d.ts +0 -13
  80. package/dist/packages/core/src/commands/unsetAllMarks.d.ts +0 -13
  81. package/dist/packages/core/src/commands/unsetMark.d.ts +0 -25
  82. package/dist/packages/core/src/commands/updateAttributes.d.ts +0 -24
  83. package/dist/packages/core/src/commands/wrapIn.d.ts +0 -16
  84. package/dist/packages/core/src/commands/wrapInList.d.ts +0 -16
  85. package/dist/packages/core/src/extensions/clipboardTextSerializer.d.ts +0 -5
  86. package/dist/packages/core/src/extensions/commands.d.ts +0 -3
  87. package/dist/packages/core/src/extensions/editable.d.ts +0 -2
  88. package/dist/packages/core/src/extensions/focusEvents.d.ts +0 -2
  89. package/dist/packages/core/src/extensions/index.d.ts +0 -6
  90. package/dist/packages/core/src/extensions/keymap.d.ts +0 -2
  91. package/dist/packages/core/src/extensions/tabindex.d.ts +0 -2
  92. package/dist/packages/core/src/helpers/combineTransactionSteps.d.ts +0 -10
  93. package/dist/packages/core/src/helpers/createChainableState.d.ts +0 -10
  94. package/dist/packages/core/src/helpers/createDocument.d.ts +0 -12
  95. package/dist/packages/core/src/helpers/createNodeFromContent.d.ts +0 -15
  96. package/dist/packages/core/src/helpers/defaultBlockAt.d.ts +0 -7
  97. package/dist/packages/core/src/helpers/findChildren.d.ts +0 -9
  98. package/dist/packages/core/src/helpers/findChildrenInRange.d.ts +0 -10
  99. package/dist/packages/core/src/helpers/findParentNode.d.ts +0 -16
  100. package/dist/packages/core/src/helpers/findParentNodeClosestToPos.d.ts +0 -17
  101. package/dist/packages/core/src/helpers/generateHTML.d.ts +0 -8
  102. package/dist/packages/core/src/helpers/generateJSON.d.ts +0 -8
  103. package/dist/packages/core/src/helpers/generateText.d.ts +0 -12
  104. package/dist/packages/core/src/helpers/getAttributes.d.ts +0 -9
  105. package/dist/packages/core/src/helpers/getAttributesFromExtensions.d.ts +0 -6
  106. package/dist/packages/core/src/helpers/getChangedRanges.d.ts +0 -11
  107. package/dist/packages/core/src/helpers/getDebugJSON.d.ts +0 -8
  108. package/dist/packages/core/src/helpers/getExtensionField.d.ts +0 -9
  109. package/dist/packages/core/src/helpers/getHTMLFromFragment.d.ts +0 -2
  110. package/dist/packages/core/src/helpers/getMarkAttributes.d.ts +0 -3
  111. package/dist/packages/core/src/helpers/getMarkRange.d.ts +0 -3
  112. package/dist/packages/core/src/helpers/getMarkType.d.ts +0 -2
  113. package/dist/packages/core/src/helpers/getMarksBetween.d.ts +0 -3
  114. package/dist/packages/core/src/helpers/getNodeAtPosition.d.ts +0 -11
  115. package/dist/packages/core/src/helpers/getNodeAttributes.d.ts +0 -3
  116. package/dist/packages/core/src/helpers/getNodeType.d.ts +0 -2
  117. package/dist/packages/core/src/helpers/getRenderedAttributes.d.ts +0 -3
  118. package/dist/packages/core/src/helpers/getSchema.d.ts +0 -4
  119. package/dist/packages/core/src/helpers/getSchemaByResolvedExtensions.d.ts +0 -10
  120. package/dist/packages/core/src/helpers/getSchemaTypeByName.d.ts +0 -8
  121. package/dist/packages/core/src/helpers/getSchemaTypeNameByName.d.ts +0 -8
  122. package/dist/packages/core/src/helpers/getSplittedAttributes.d.ts +0 -9
  123. package/dist/packages/core/src/helpers/getText.d.ts +0 -15
  124. package/dist/packages/core/src/helpers/getTextBetween.d.ts +0 -14
  125. package/dist/packages/core/src/helpers/getTextContentFromNodes.d.ts +0 -8
  126. package/dist/packages/core/src/helpers/getTextSerializersFromSchema.d.ts +0 -8
  127. package/dist/packages/core/src/helpers/index.d.ts +0 -50
  128. package/dist/packages/core/src/helpers/injectExtensionAttributesToParseRule.d.ts +0 -9
  129. package/dist/packages/core/src/helpers/isActive.d.ts +0 -2
  130. package/dist/packages/core/src/helpers/isAtEndOfNode.d.ts +0 -2
  131. package/dist/packages/core/src/helpers/isAtStartOfNode.d.ts +0 -2
  132. package/dist/packages/core/src/helpers/isExtensionRulesEnabled.d.ts +0 -2
  133. package/dist/packages/core/src/helpers/isList.d.ts +0 -2
  134. package/dist/packages/core/src/helpers/isMarkActive.d.ts +0 -3
  135. package/dist/packages/core/src/helpers/isNodeActive.d.ts +0 -3
  136. package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +0 -2
  137. package/dist/packages/core/src/helpers/isNodeSelection.d.ts +0 -2
  138. package/dist/packages/core/src/helpers/isTextSelection.d.ts +0 -2
  139. package/dist/packages/core/src/helpers/posToDOMRect.d.ts +0 -2
  140. package/dist/packages/core/src/helpers/resolveFocusPosition.d.ts +0 -4
  141. package/dist/packages/core/src/helpers/selectionToInsertionEnd.d.ts +0 -2
  142. package/dist/packages/core/src/helpers/splitExtensions.d.ts +0 -9
  143. package/dist/packages/core/src/index.d.ts +0 -24
  144. package/dist/packages/core/src/inputRules/index.d.ts +0 -5
  145. package/dist/packages/core/src/inputRules/markInputRule.d.ts +0 -13
  146. package/dist/packages/core/src/inputRules/nodeInputRule.d.ts +0 -23
  147. package/dist/packages/core/src/inputRules/textInputRule.d.ts +0 -10
  148. package/dist/packages/core/src/inputRules/textblockTypeInputRule.d.ts +0 -15
  149. package/dist/packages/core/src/inputRules/wrappingInputRule.d.ts +0 -28
  150. package/dist/packages/core/src/pasteRules/index.d.ts +0 -3
  151. package/dist/packages/core/src/pasteRules/markPasteRule.d.ts +0 -13
  152. package/dist/packages/core/src/pasteRules/nodePasteRule.d.ts +0 -13
  153. package/dist/packages/core/src/pasteRules/textPasteRule.d.ts +0 -10
  154. package/dist/packages/core/src/style.d.ts +0 -1
  155. package/dist/packages/core/src/types.d.ts +0 -255
  156. package/dist/packages/core/src/utilities/callOrReturn.d.ts +0 -9
  157. package/dist/packages/core/src/utilities/createStyleTag.d.ts +0 -1
  158. package/dist/packages/core/src/utilities/deleteProps.d.ts +0 -6
  159. package/dist/packages/core/src/utilities/elementFromString.d.ts +0 -1
  160. package/dist/packages/core/src/utilities/escapeForRegEx.d.ts +0 -1
  161. package/dist/packages/core/src/utilities/findDuplicates.d.ts +0 -1
  162. package/dist/packages/core/src/utilities/fromString.d.ts +0 -1
  163. package/dist/packages/core/src/utilities/index.d.ts +0 -20
  164. package/dist/packages/core/src/utilities/isAndroid.d.ts +0 -1
  165. package/dist/packages/core/src/utilities/isEmptyObject.d.ts +0 -1
  166. package/dist/packages/core/src/utilities/isFunction.d.ts +0 -1
  167. package/dist/packages/core/src/utilities/isMacOS.d.ts +0 -1
  168. package/dist/packages/core/src/utilities/isNumber.d.ts +0 -1
  169. package/dist/packages/core/src/utilities/isPlainObject.d.ts +0 -1
  170. package/dist/packages/core/src/utilities/isRegExp.d.ts +0 -1
  171. package/dist/packages/core/src/utilities/isString.d.ts +0 -1
  172. package/dist/packages/core/src/utilities/isiOS.d.ts +0 -1
  173. package/dist/packages/core/src/utilities/mergeAttributes.d.ts +0 -1
  174. package/dist/packages/core/src/utilities/mergeDeep.d.ts +0 -1
  175. package/dist/packages/core/src/utilities/minMax.d.ts +0 -1
  176. package/dist/packages/core/src/utilities/objectIncludes.d.ts +0 -8
  177. package/dist/packages/core/src/utilities/removeDuplicates.d.ts +0 -8
  178. package/dist/packages/extension-link/src/helpers/autolink.d.ts +0 -14
  179. package/dist/packages/extension-link/src/helpers/clickHandler.d.ts +0 -7
  180. package/dist/packages/extension-link/src/helpers/pasteHandler.d.ts +0 -10
  181. package/dist/packages/extension-link/src/index.d.ts +0 -3
package/dist/index.js CHANGED
@@ -1,336 +1,404 @@
1
- import { combineTransactionSteps, getChangedRanges, findChildrenInRange, getMarksBetween, getAttributes, Mark, mergeAttributes, markPasteRule } from '@tiptap/core';
2
- import { tokenize, find, registerCustomProtocol, reset } from 'linkifyjs';
3
- import { Plugin, PluginKey } from '@tiptap/pm/state';
1
+ // src/link.ts
2
+ import { Mark, markPasteRule, mergeAttributes } from "@tiptap/core";
3
+ import { find as find2, registerCustomProtocol, reset } from "linkifyjs";
4
4
 
5
- /**
6
- * Check if the provided tokens form a valid link structure, which can either be a single link token
7
- * or a link token surrounded by parentheses or square brackets.
8
- *
9
- * This ensures that only complete and valid text is hyperlinked, preventing cases where a valid
10
- * top-level domain (TLD) is immediately followed by an invalid character, like a number. For
11
- * example, with the `find` method from Linkify, entering `example.com1` would result in
12
- * `example.com` being linked and the trailing `1` left as plain text. By using the `tokenize`
13
- * method, we can perform more comprehensive validation on the input text.
14
- */
5
+ // src/helpers/autolink.ts
6
+ import { combineTransactionSteps, findChildrenInRange, getChangedRanges, getMarksBetween } from "@tiptap/core";
7
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
8
+ import { tokenize } from "linkifyjs";
9
+
10
+ // src/helpers/whitespace.ts
11
+ var UNICODE_WHITESPACE_PATTERN = "[\0- \xA0\u1680\u180E\u2000-\u2029\u205F\u3000]";
12
+ var UNICODE_WHITESPACE_REGEX = new RegExp(UNICODE_WHITESPACE_PATTERN);
13
+ var UNICODE_WHITESPACE_REGEX_END = new RegExp(`${UNICODE_WHITESPACE_PATTERN}$`);
14
+ var UNICODE_WHITESPACE_REGEX_GLOBAL = new RegExp(UNICODE_WHITESPACE_PATTERN, "g");
15
+
16
+ // src/helpers/autolink.ts
15
17
  function isValidLinkStructure(tokens) {
16
- if (tokens.length === 1) {
17
- return tokens[0].isLink;
18
- }
19
- if (tokens.length === 3 && tokens[1].isLink) {
20
- return ['()', '[]'].includes(tokens[0].value + tokens[2].value);
21
- }
22
- return false;
18
+ if (tokens.length === 1) {
19
+ return tokens[0].isLink;
20
+ }
21
+ if (tokens.length === 3 && tokens[1].isLink) {
22
+ return ["()", "[]"].includes(tokens[0].value + tokens[2].value);
23
+ }
24
+ return false;
23
25
  }
24
- /**
25
- * This plugin allows you to automatically add links to your editor.
26
- * @param options The plugin options
27
- * @returns The plugin instance
28
- */
29
26
  function autolink(options) {
30
- return new Plugin({
31
- key: new PluginKey('autolink'),
32
- appendTransaction: (transactions, oldState, newState) => {
33
- /**
34
- * Does the transaction change the document?
35
- */
36
- const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc);
37
- /**
38
- * Prevent autolink if the transaction is not a document change or if the transaction has the meta `preventAutolink`.
39
- */
40
- const preventAutolink = transactions.some(transaction => transaction.getMeta('preventAutolink'));
41
- /**
42
- * Prevent autolink if the transaction is not a document change
43
- * or if the transaction has the meta `preventAutolink`.
44
- */
45
- if (!docChanges || preventAutolink) {
46
- return;
27
+ return new Plugin({
28
+ key: new PluginKey("autolink"),
29
+ appendTransaction: (transactions, oldState, newState) => {
30
+ const docChanges = transactions.some((transaction) => transaction.docChanged) && !oldState.doc.eq(newState.doc);
31
+ const preventAutolink = transactions.some((transaction) => transaction.getMeta("preventAutolink"));
32
+ if (!docChanges || preventAutolink) {
33
+ return;
34
+ }
35
+ const { tr } = newState;
36
+ const transform = combineTransactionSteps(oldState.doc, [...transactions]);
37
+ const changes = getChangedRanges(transform);
38
+ changes.forEach(({ newRange }) => {
39
+ const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, (node) => node.isTextblock);
40
+ let textBlock;
41
+ let textBeforeWhitespace;
42
+ if (nodesInChangedRanges.length > 1) {
43
+ textBlock = nodesInChangedRanges[0];
44
+ textBeforeWhitespace = newState.doc.textBetween(
45
+ textBlock.pos,
46
+ textBlock.pos + textBlock.node.nodeSize,
47
+ void 0,
48
+ " "
49
+ );
50
+ } else if (nodesInChangedRanges.length) {
51
+ const endText = newState.doc.textBetween(newRange.from, newRange.to, " ", " ");
52
+ if (!UNICODE_WHITESPACE_REGEX_END.test(endText)) {
53
+ return;
54
+ }
55
+ textBlock = nodesInChangedRanges[0];
56
+ textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, void 0, " ");
57
+ }
58
+ if (textBlock && textBeforeWhitespace) {
59
+ const wordsBeforeWhitespace = textBeforeWhitespace.split(UNICODE_WHITESPACE_REGEX).filter(Boolean);
60
+ if (wordsBeforeWhitespace.length <= 0) {
61
+ return false;
62
+ }
63
+ const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1];
64
+ const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace);
65
+ if (!lastWordBeforeSpace) {
66
+ return false;
67
+ }
68
+ const linksBeforeSpace = tokenize(lastWordBeforeSpace).map((t) => t.toObject(options.defaultProtocol));
69
+ if (!isValidLinkStructure(linksBeforeSpace)) {
70
+ return false;
71
+ }
72
+ linksBeforeSpace.filter((link) => link.isLink).map((link) => ({
73
+ ...link,
74
+ from: lastWordAndBlockOffset + link.start + 1,
75
+ to: lastWordAndBlockOffset + link.end + 1
76
+ })).filter((link) => {
77
+ if (!newState.schema.marks.code) {
78
+ return true;
47
79
  }
48
- const { tr } = newState;
49
- const transform = combineTransactionSteps(oldState.doc, [...transactions]);
50
- const changes = getChangedRanges(transform);
51
- changes.forEach(({ newRange }) => {
52
- // Now let’s see if we can add new links.
53
- const nodesInChangedRanges = findChildrenInRange(newState.doc, newRange, node => node.isTextblock);
54
- let textBlock;
55
- let textBeforeWhitespace;
56
- if (nodesInChangedRanges.length > 1) {
57
- // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).
58
- textBlock = nodesInChangedRanges[0];
59
- textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, textBlock.pos + textBlock.node.nodeSize, undefined, ' ');
60
- }
61
- else if (nodesInChangedRanges.length
62
- // We want to make sure to include the block seperator argument to treat hard breaks like spaces.
63
- && newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ')) {
64
- textBlock = nodesInChangedRanges[0];
65
- textBeforeWhitespace = newState.doc.textBetween(textBlock.pos, newRange.to, undefined, ' ');
66
- }
67
- if (textBlock && textBeforeWhitespace) {
68
- const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '');
69
- if (wordsBeforeWhitespace.length <= 0) {
70
- return false;
71
- }
72
- const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1];
73
- const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace);
74
- if (!lastWordBeforeSpace) {
75
- return false;
76
- }
77
- const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol));
78
- if (!isValidLinkStructure(linksBeforeSpace)) {
79
- return false;
80
- }
81
- linksBeforeSpace
82
- .filter(link => link.isLink)
83
- // Calculate link position.
84
- .map(link => ({
85
- ...link,
86
- from: lastWordAndBlockOffset + link.start + 1,
87
- to: lastWordAndBlockOffset + link.end + 1,
88
- }))
89
- // ignore link inside code mark
90
- .filter(link => {
91
- if (!newState.schema.marks.code) {
92
- return true;
93
- }
94
- return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code);
95
- })
96
- // validate link
97
- .filter(link => options.validate(link.value))
98
- // Add link mark.
99
- .forEach(link => {
100
- if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) {
101
- return;
102
- }
103
- tr.addMark(link.from, link.to, options.type.create({
104
- href: link.href,
105
- }));
106
- });
107
- }
108
- });
109
- if (!tr.steps.length) {
110
- return;
80
+ return !newState.doc.rangeHasMark(link.from, link.to, newState.schema.marks.code);
81
+ }).filter((link) => options.validate(link.value)).filter((link) => options.shouldAutoLink(link.value)).forEach((link) => {
82
+ if (getMarksBetween(link.from, link.to, newState.doc).some((item) => item.mark.type === options.type)) {
83
+ return;
111
84
  }
112
- return tr;
113
- },
114
- });
85
+ tr.addMark(
86
+ link.from,
87
+ link.to,
88
+ options.type.create({
89
+ href: link.href
90
+ })
91
+ );
92
+ });
93
+ }
94
+ });
95
+ if (!tr.steps.length) {
96
+ return;
97
+ }
98
+ return tr;
99
+ }
100
+ });
115
101
  }
116
102
 
103
+ // src/helpers/clickHandler.ts
104
+ import { getAttributes } from "@tiptap/core";
105
+ import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
117
106
  function clickHandler(options) {
118
- return new Plugin({
119
- key: new PluginKey('handleClickLink'),
120
- props: {
121
- handleClick: (view, pos, event) => {
122
- var _a, _b;
123
- if (event.button !== 0) {
124
- return false;
125
- }
126
- let a = event.target;
127
- const els = [];
128
- while (a.nodeName !== 'DIV') {
129
- els.push(a);
130
- a = a.parentNode;
131
- }
132
- if (!els.find(value => value.nodeName === 'A')) {
133
- return false;
134
- }
135
- const attrs = getAttributes(view.state, options.type.name);
136
- const link = event.target;
137
- const href = (_a = link === null || link === void 0 ? void 0 : link.href) !== null && _a !== void 0 ? _a : attrs.href;
138
- const target = (_b = link === null || link === void 0 ? void 0 : link.target) !== null && _b !== void 0 ? _b : attrs.target;
139
- if (link && href) {
140
- window.open(href, target);
141
- return true;
142
- }
143
- return false;
144
- },
145
- },
146
- });
107
+ return new Plugin2({
108
+ key: new PluginKey2("handleClickLink"),
109
+ props: {
110
+ handleClick: (view, pos, event) => {
111
+ var _a, _b;
112
+ if (event.button !== 0) {
113
+ return false;
114
+ }
115
+ if (!view.editable) {
116
+ return false;
117
+ }
118
+ let link = null;
119
+ if (event.target instanceof HTMLAnchorElement) {
120
+ link = event.target;
121
+ } else {
122
+ let a = event.target;
123
+ const els = [];
124
+ while (a.nodeName !== "DIV") {
125
+ els.push(a);
126
+ a = a.parentNode;
127
+ }
128
+ link = els.find((value) => value.nodeName === "A");
129
+ }
130
+ if (!link) {
131
+ return false;
132
+ }
133
+ const attrs = getAttributes(view.state, options.type.name);
134
+ const href = (_a = link == null ? void 0 : link.href) != null ? _a : attrs.href;
135
+ const target = (_b = link == null ? void 0 : link.target) != null ? _b : attrs.target;
136
+ if (options.enableClickSelection) {
137
+ options.editor.commands.extendMarkRange(options.type.name);
138
+ }
139
+ if (link && href) {
140
+ window.open(href, target);
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+ }
146
+ });
147
147
  }
148
148
 
149
+ // src/helpers/pasteHandler.ts
150
+ import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
151
+ import { find } from "linkifyjs";
149
152
  function pasteHandler(options) {
150
- return new Plugin({
151
- key: new PluginKey('handlePasteLink'),
152
- props: {
153
- handlePaste: (view, event, slice) => {
154
- const { state } = view;
155
- const { selection } = state;
156
- const { empty } = selection;
157
- if (empty) {
158
- return false;
159
- }
160
- let textContent = '';
161
- slice.content.forEach(node => {
162
- textContent += node.textContent;
163
- });
164
- const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(item => item.isLink && item.value === textContent);
165
- if (!textContent || !link) {
166
- return false;
167
- }
168
- options.editor.commands.setMark(options.type, {
169
- href: link.href,
170
- });
171
- return true;
172
- },
173
- },
174
- });
153
+ return new Plugin3({
154
+ key: new PluginKey3("handlePasteLink"),
155
+ props: {
156
+ handlePaste: (view, event, slice) => {
157
+ const { state } = view;
158
+ const { selection } = state;
159
+ const { empty } = selection;
160
+ if (empty) {
161
+ return false;
162
+ }
163
+ let textContent = "";
164
+ slice.content.forEach((node) => {
165
+ textContent += node.textContent;
166
+ });
167
+ const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(
168
+ (item) => item.isLink && item.value === textContent
169
+ );
170
+ if (!textContent || !link) {
171
+ return false;
172
+ }
173
+ return options.editor.commands.setMark(options.type, {
174
+ href: link.href
175
+ });
176
+ }
177
+ }
178
+ });
175
179
  }
176
180
 
177
- const pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi;
178
- // From DOMPurify
179
- // https://github.com/cure53/DOMPurify/blob/main/src/regexp.js
180
- const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g; // eslint-disable-line no-control-regex
181
- const IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
182
- function isAllowedUri(uri) {
183
- return !uri || uri.replace(ATTR_WHITESPACE, '').match(IS_ALLOWED_URI);
181
+ // src/link.ts
182
+ var pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi;
183
+ function isAllowedUri(uri, protocols) {
184
+ const allowedProtocols = ["http", "https", "ftp", "ftps", "mailto", "tel", "callto", "sms", "cid", "xmpp"];
185
+ if (protocols) {
186
+ protocols.forEach((protocol) => {
187
+ const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
188
+ if (nextProtocol) {
189
+ allowedProtocols.push(nextProtocol);
190
+ }
191
+ });
192
+ }
193
+ return !uri || uri.replace(UNICODE_WHITESPACE_REGEX_GLOBAL, "").match(
194
+ new RegExp(
195
+ // eslint-disable-next-line no-useless-escape
196
+ `^(?:(?:${allowedProtocols.join("|")}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,
197
+ "i"
198
+ )
199
+ );
184
200
  }
185
- /**
186
- * This extension allows you to create links.
187
- * @see https://www.tiptap.dev/api/marks/link
188
- */
189
- const Link = Mark.create({
190
- name: 'link',
191
- priority: 1000,
192
- keepOnSplit: false,
193
- exitable: true,
194
- onCreate() {
195
- this.options.protocols.forEach(protocol => {
196
- if (typeof protocol === 'string') {
197
- registerCustomProtocol(protocol);
198
- return;
199
- }
200
- registerCustomProtocol(protocol.scheme, protocol.optionalSlashes);
201
- });
202
- },
203
- onDestroy() {
204
- reset();
205
- },
206
- inclusive() {
207
- return this.options.autolink;
208
- },
209
- addOptions() {
210
- return {
211
- openOnClick: true,
212
- linkOnPaste: true,
213
- autolink: true,
214
- protocols: [],
215
- defaultProtocol: 'http',
216
- HTMLAttributes: {
217
- target: '_blank',
218
- rel: 'noopener noreferrer nofollow',
219
- class: null,
220
- },
221
- validate: url => !!url,
222
- };
223
- },
224
- addAttributes() {
225
- return {
226
- href: {
227
- default: null,
228
- },
229
- target: {
230
- default: this.options.HTMLAttributes.target,
231
- },
232
- rel: {
233
- default: this.options.HTMLAttributes.rel,
234
- },
235
- class: {
236
- default: this.options.HTMLAttributes.class,
237
- },
238
- };
239
- },
240
- parseHTML() {
241
- return [{
242
- tag: 'a[href]',
243
- getAttrs: dom => {
244
- const href = dom.getAttribute('href');
245
- // prevent XSS attacks
246
- if (!href || !isAllowedUri(href)) {
247
- return false;
248
- }
249
- return { href };
250
- },
251
- }];
252
- },
253
- renderHTML({ HTMLAttributes }) {
254
- // prevent XSS attacks
255
- if (!isAllowedUri(HTMLAttributes.href)) {
256
- // strip out the href
257
- return ['a', mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }), 0];
201
+ var Link = Mark.create({
202
+ name: "link",
203
+ priority: 1e3,
204
+ keepOnSplit: false,
205
+ exitable: true,
206
+ onCreate() {
207
+ if (this.options.validate && !this.options.shouldAutoLink) {
208
+ this.options.shouldAutoLink = this.options.validate;
209
+ console.warn("The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.");
210
+ }
211
+ this.options.protocols.forEach((protocol) => {
212
+ if (typeof protocol === "string") {
213
+ registerCustomProtocol(protocol);
214
+ return;
215
+ }
216
+ registerCustomProtocol(protocol.scheme, protocol.optionalSlashes);
217
+ });
218
+ },
219
+ onDestroy() {
220
+ reset();
221
+ },
222
+ inclusive() {
223
+ return this.options.autolink;
224
+ },
225
+ addOptions() {
226
+ return {
227
+ openOnClick: true,
228
+ enableClickSelection: false,
229
+ linkOnPaste: true,
230
+ autolink: true,
231
+ protocols: [],
232
+ defaultProtocol: "http",
233
+ HTMLAttributes: {
234
+ target: "_blank",
235
+ rel: "noopener noreferrer nofollow",
236
+ class: null
237
+ },
238
+ isAllowedUri: (url, ctx) => !!isAllowedUri(url, ctx.protocols),
239
+ validate: (url) => !!url,
240
+ shouldAutoLink: (url) => !!url
241
+ };
242
+ },
243
+ addAttributes() {
244
+ return {
245
+ href: {
246
+ default: null,
247
+ parseHTML(element) {
248
+ return element.getAttribute("href");
258
249
  }
259
- return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
260
- },
261
- addCommands() {
262
- return {
263
- setLink: attributes => ({ chain }) => {
264
- return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run();
265
- },
266
- toggleLink: attributes => ({ chain }) => {
267
- return chain()
268
- .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
269
- .setMeta('preventAutolink', true)
270
- .run();
271
- },
272
- unsetLink: () => ({ chain }) => {
273
- return chain()
274
- .unsetMark(this.name, { extendEmptyMarkRange: true })
275
- .setMeta('preventAutolink', true)
276
- .run();
277
- },
278
- };
279
- },
280
- addPasteRules() {
281
- return [
282
- markPasteRule({
283
- find: text => {
284
- const foundLinks = [];
285
- if (text) {
286
- const { validate } = this.options;
287
- const links = find(text).filter(item => item.isLink && validate(item.value));
288
- if (links.length) {
289
- links.forEach(link => (foundLinks.push({
290
- text: link.value,
291
- data: {
292
- href: link.href,
293
- },
294
- index: link.start,
295
- })));
296
- }
297
- }
298
- return foundLinks;
299
- },
300
- type: this.type,
301
- getAttributes: match => {
302
- var _a;
303
- return {
304
- href: (_a = match.data) === null || _a === void 0 ? void 0 : _a.href,
305
- };
306
- },
307
- }),
308
- ];
309
- },
310
- addProseMirrorPlugins() {
311
- const plugins = [];
312
- if (this.options.autolink) {
313
- plugins.push(autolink({
314
- type: this.type,
315
- defaultProtocol: this.options.defaultProtocol,
316
- validate: this.options.validate,
317
- }));
250
+ },
251
+ target: {
252
+ default: this.options.HTMLAttributes.target
253
+ },
254
+ rel: {
255
+ default: this.options.HTMLAttributes.rel
256
+ },
257
+ class: {
258
+ default: this.options.HTMLAttributes.class
259
+ }
260
+ };
261
+ },
262
+ parseHTML() {
263
+ return [
264
+ {
265
+ tag: "a[href]",
266
+ getAttrs: (dom) => {
267
+ const href = dom.getAttribute("href");
268
+ if (!href || !this.options.isAllowedUri(href, {
269
+ defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
270
+ protocols: this.options.protocols,
271
+ defaultProtocol: this.options.defaultProtocol
272
+ })) {
273
+ return false;
274
+ }
275
+ return null;
318
276
  }
319
- if (this.options.openOnClick) {
320
- plugins.push(clickHandler({
321
- type: this.type,
322
- }));
277
+ }
278
+ ];
279
+ },
280
+ renderHTML({ HTMLAttributes }) {
281
+ if (!this.options.isAllowedUri(HTMLAttributes.href, {
282
+ defaultValidate: (href) => !!isAllowedUri(href, this.options.protocols),
283
+ protocols: this.options.protocols,
284
+ defaultProtocol: this.options.defaultProtocol
285
+ })) {
286
+ return ["a", mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: "" }), 0];
287
+ }
288
+ return ["a", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
289
+ },
290
+ addCommands() {
291
+ return {
292
+ setLink: (attributes) => ({ chain }) => {
293
+ const { href } = attributes;
294
+ if (!this.options.isAllowedUri(href, {
295
+ defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
296
+ protocols: this.options.protocols,
297
+ defaultProtocol: this.options.defaultProtocol
298
+ })) {
299
+ return false;
323
300
  }
324
- if (this.options.linkOnPaste) {
325
- plugins.push(pasteHandler({
326
- editor: this.editor,
327
- defaultProtocol: this.options.defaultProtocol,
328
- type: this.type,
329
- }));
301
+ return chain().setMark(this.name, attributes).setMeta("preventAutolink", true).run();
302
+ },
303
+ toggleLink: (attributes) => ({ chain }) => {
304
+ const { href } = attributes || {};
305
+ if (href && !this.options.isAllowedUri(href, {
306
+ defaultValidate: (url) => !!isAllowedUri(url, this.options.protocols),
307
+ protocols: this.options.protocols,
308
+ defaultProtocol: this.options.defaultProtocol
309
+ })) {
310
+ return false;
330
311
  }
331
- return plugins;
332
- },
312
+ return chain().toggleMark(this.name, attributes, { extendEmptyMarkRange: true }).setMeta("preventAutolink", true).run();
313
+ },
314
+ unsetLink: () => ({ chain }) => {
315
+ return chain().unsetMark(this.name, { extendEmptyMarkRange: true }).setMeta("preventAutolink", true).run();
316
+ }
317
+ };
318
+ },
319
+ addPasteRules() {
320
+ return [
321
+ markPasteRule({
322
+ find: (text) => {
323
+ const foundLinks = [];
324
+ if (text) {
325
+ const { protocols, defaultProtocol } = this.options;
326
+ const links = find2(text).filter(
327
+ (item) => item.isLink && this.options.isAllowedUri(item.value, {
328
+ defaultValidate: (href) => !!isAllowedUri(href, protocols),
329
+ protocols,
330
+ defaultProtocol
331
+ })
332
+ );
333
+ if (links.length) {
334
+ links.forEach(
335
+ (link) => foundLinks.push({
336
+ text: link.value,
337
+ data: {
338
+ href: link.href
339
+ },
340
+ index: link.start
341
+ })
342
+ );
343
+ }
344
+ }
345
+ return foundLinks;
346
+ },
347
+ type: this.type,
348
+ getAttributes: (match) => {
349
+ var _a;
350
+ return {
351
+ href: (_a = match.data) == null ? void 0 : _a.href
352
+ };
353
+ }
354
+ })
355
+ ];
356
+ },
357
+ addProseMirrorPlugins() {
358
+ const plugins = [];
359
+ const { protocols, defaultProtocol } = this.options;
360
+ if (this.options.autolink) {
361
+ plugins.push(
362
+ autolink({
363
+ type: this.type,
364
+ defaultProtocol: this.options.defaultProtocol,
365
+ validate: (url) => this.options.isAllowedUri(url, {
366
+ defaultValidate: (href) => !!isAllowedUri(href, protocols),
367
+ protocols,
368
+ defaultProtocol
369
+ }),
370
+ shouldAutoLink: this.options.shouldAutoLink
371
+ })
372
+ );
373
+ }
374
+ if (this.options.openOnClick === true) {
375
+ plugins.push(
376
+ clickHandler({
377
+ type: this.type,
378
+ editor: this.editor,
379
+ enableClickSelection: this.options.enableClickSelection
380
+ })
381
+ );
382
+ }
383
+ if (this.options.linkOnPaste) {
384
+ plugins.push(
385
+ pasteHandler({
386
+ editor: this.editor,
387
+ defaultProtocol: this.options.defaultProtocol,
388
+ type: this.type
389
+ })
390
+ );
391
+ }
392
+ return plugins;
393
+ }
333
394
  });
334
395
 
335
- export { Link, Link as default, pasteRegex };
336
- //# sourceMappingURL=index.js.map
396
+ // src/index.ts
397
+ var index_default = Link;
398
+ export {
399
+ Link,
400
+ index_default as default,
401
+ isAllowedUri,
402
+ pasteRegex
403
+ };
404
+ //# sourceMappingURL=index.js.map