@tiptap/extension-link 2.0.0-beta.28 → 2.0.0-beta.31
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/dist/packages/extension-link/src/helpers/autolink.d.ts +7 -0
- package/dist/packages/extension-link/src/helpers/clickHandler.d.ts +7 -0
- package/dist/packages/extension-link/src/helpers/pasteHandler.d.ts +9 -0
- package/dist/packages/extension-link/src/link.d.ts +4 -0
- package/dist/tiptap-extension-link.cjs.js +139 -45
- package/dist/tiptap-extension-link.cjs.js.map +1 -1
- package/dist/tiptap-extension-link.esm.js +140 -46
- package/dist/tiptap-extension-link.esm.js.map +1 -1
- package/dist/tiptap-extension-link.umd.js +142 -48
- package/dist/tiptap-extension-link.umd.js.map +1 -1
- package/package.json +4 -3
- package/src/helpers/autolink.ts +92 -0
- package/src/helpers/clickHandler.ts +27 -0
- package/src/helpers/pasteHandler.ts +44 -0
- package/src/link.ts +37 -65
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
3
|
+
import { MarkType } from 'prosemirror-model';
|
|
4
|
+
declare type PasteHandlerOptions = {
|
|
5
|
+
editor: Editor;
|
|
6
|
+
type: MarkType;
|
|
7
|
+
};
|
|
8
|
+
export default function pasteHandler(options: PasteHandlerOptions): Plugin;
|
|
9
|
+
export {};
|
|
@@ -3,17 +3,136 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var core = require('@tiptap/core');
|
|
6
|
-
var prosemirrorState = require('prosemirror-state');
|
|
7
6
|
var linkifyjs = require('linkifyjs');
|
|
7
|
+
var prosemirrorState = require('prosemirror-state');
|
|
8
|
+
|
|
9
|
+
function autolink(options) {
|
|
10
|
+
return new prosemirrorState.Plugin({
|
|
11
|
+
key: new prosemirrorState.PluginKey('autolink'),
|
|
12
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
13
|
+
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
14
|
+
&& !oldState.doc.eq(newState.doc);
|
|
15
|
+
if (!docChanges) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const { tr } = newState;
|
|
19
|
+
const transform = core.combineTransactionSteps(oldState.doc, transactions);
|
|
20
|
+
const { mapping } = transform;
|
|
21
|
+
const changes = core.getChangedRanges(transform);
|
|
22
|
+
changes.forEach(({ oldRange, newRange }) => {
|
|
23
|
+
// at first we check if we have to remove links
|
|
24
|
+
core.getMarksBetween(oldRange.from, oldRange.to, oldState.doc)
|
|
25
|
+
.filter(item => item.mark.type === options.type)
|
|
26
|
+
.forEach(oldMark => {
|
|
27
|
+
const newFrom = mapping.map(oldMark.from);
|
|
28
|
+
const newTo = mapping.map(oldMark.to);
|
|
29
|
+
const newMarks = core.getMarksBetween(newFrom, newTo, newState.doc)
|
|
30
|
+
.filter(item => item.mark.type === options.type);
|
|
31
|
+
if (!newMarks.length) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const newMark = newMarks[0];
|
|
35
|
+
const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to);
|
|
36
|
+
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
|
|
37
|
+
const wasLink = linkifyjs.test(oldLinkText);
|
|
38
|
+
const isLink = linkifyjs.test(newLinkText);
|
|
39
|
+
// remove only the link, if it was a link before too
|
|
40
|
+
// because we don’t want to remove links that were set manually
|
|
41
|
+
if (wasLink && !isLink) {
|
|
42
|
+
tr.removeMark(newMark.from, newMark.to, options.type);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// now let’s see if we can add new links
|
|
46
|
+
core.findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
|
|
47
|
+
.forEach(textBlock => {
|
|
48
|
+
linkifyjs.find(textBlock.node.textContent)
|
|
49
|
+
.filter(link => link.isLink)
|
|
50
|
+
// calculate link position
|
|
51
|
+
.map(link => ({
|
|
52
|
+
...link,
|
|
53
|
+
from: textBlock.pos + link.start + 1,
|
|
54
|
+
to: textBlock.pos + link.end + 1,
|
|
55
|
+
}))
|
|
56
|
+
// check if link is within the changed range
|
|
57
|
+
.filter(link => {
|
|
58
|
+
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
|
|
59
|
+
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
|
|
60
|
+
return fromIsInRange || toIsInRange;
|
|
61
|
+
})
|
|
62
|
+
// add link mark
|
|
63
|
+
.forEach(link => {
|
|
64
|
+
tr.addMark(link.from, link.to, options.type.create({
|
|
65
|
+
href: link.href,
|
|
66
|
+
}));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
if (!tr.steps.length) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return tr;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function clickHandler(options) {
|
|
79
|
+
return new prosemirrorState.Plugin({
|
|
80
|
+
key: new prosemirrorState.PluginKey('handleClickLink'),
|
|
81
|
+
props: {
|
|
82
|
+
handleClick: (view, pos, event) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const attrs = core.getAttributes(view.state, options.type.name);
|
|
85
|
+
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
86
|
+
if (link && attrs.href) {
|
|
87
|
+
window.open(attrs.href, attrs.target);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function pasteHandler(options) {
|
|
97
|
+
return new prosemirrorState.Plugin({
|
|
98
|
+
key: new prosemirrorState.PluginKey('handlePasteLink'),
|
|
99
|
+
props: {
|
|
100
|
+
handlePaste: (view, event, slice) => {
|
|
101
|
+
const { state } = view;
|
|
102
|
+
const { selection } = state;
|
|
103
|
+
const { empty } = selection;
|
|
104
|
+
if (empty) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
let textContent = '';
|
|
108
|
+
slice.content.forEach(node => {
|
|
109
|
+
textContent += node.textContent;
|
|
110
|
+
});
|
|
111
|
+
const link = linkifyjs.find(textContent).find(item => item.isLink && item.value === textContent);
|
|
112
|
+
if (!textContent || !link) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
options.editor.commands.setMark(options.type, {
|
|
116
|
+
href: link.href,
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
8
123
|
|
|
9
124
|
const Link = core.Mark.create({
|
|
10
125
|
name: 'link',
|
|
11
126
|
priority: 1000,
|
|
12
|
-
|
|
127
|
+
keepOnSplit: false,
|
|
128
|
+
inclusive() {
|
|
129
|
+
return this.options.autolink;
|
|
130
|
+
},
|
|
13
131
|
addOptions() {
|
|
14
132
|
return {
|
|
15
133
|
openOnClick: true,
|
|
16
134
|
linkOnPaste: true,
|
|
135
|
+
autolink: true,
|
|
17
136
|
HTMLAttributes: {
|
|
18
137
|
target: '_blank',
|
|
19
138
|
rel: 'noopener noreferrer nofollow',
|
|
@@ -36,18 +155,22 @@ const Link = core.Mark.create({
|
|
|
36
155
|
];
|
|
37
156
|
},
|
|
38
157
|
renderHTML({ HTMLAttributes }) {
|
|
39
|
-
return [
|
|
158
|
+
return [
|
|
159
|
+
'a',
|
|
160
|
+
core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
161
|
+
0,
|
|
162
|
+
];
|
|
40
163
|
},
|
|
41
164
|
addCommands() {
|
|
42
165
|
return {
|
|
43
166
|
setLink: attributes => ({ commands }) => {
|
|
44
|
-
return commands.setMark(
|
|
167
|
+
return commands.setMark(this.name, attributes);
|
|
45
168
|
},
|
|
46
169
|
toggleLink: attributes => ({ commands }) => {
|
|
47
|
-
return commands.toggleMark(
|
|
170
|
+
return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true });
|
|
48
171
|
},
|
|
49
172
|
unsetLink: () => ({ commands }) => {
|
|
50
|
-
return commands.unsetMark(
|
|
173
|
+
return commands.unsetMark(this.name, { extendEmptyMarkRange: true });
|
|
51
174
|
},
|
|
52
175
|
};
|
|
53
176
|
},
|
|
@@ -73,49 +196,20 @@ const Link = core.Mark.create({
|
|
|
73
196
|
},
|
|
74
197
|
addProseMirrorPlugins() {
|
|
75
198
|
const plugins = [];
|
|
199
|
+
if (this.options.autolink) {
|
|
200
|
+
plugins.push(autolink({
|
|
201
|
+
type: this.type,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
76
204
|
if (this.options.openOnClick) {
|
|
77
|
-
plugins.push(
|
|
78
|
-
|
|
79
|
-
props: {
|
|
80
|
-
handleClick: (view, pos, event) => {
|
|
81
|
-
var _a;
|
|
82
|
-
const attrs = this.editor.getAttributes('link');
|
|
83
|
-
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
84
|
-
if (link && attrs.href) {
|
|
85
|
-
window.open(attrs.href, attrs.target);
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
},
|
|
90
|
-
},
|
|
205
|
+
plugins.push(clickHandler({
|
|
206
|
+
type: this.type,
|
|
91
207
|
}));
|
|
92
208
|
}
|
|
93
209
|
if (this.options.linkOnPaste) {
|
|
94
|
-
plugins.push(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
handlePaste: (view, event, slice) => {
|
|
98
|
-
const { state } = view;
|
|
99
|
-
const { selection } = state;
|
|
100
|
-
const { empty } = selection;
|
|
101
|
-
if (empty) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
let textContent = '';
|
|
105
|
-
slice.content.forEach(node => {
|
|
106
|
-
textContent += node.textContent;
|
|
107
|
-
});
|
|
108
|
-
const link = linkifyjs.find(textContent)
|
|
109
|
-
.find(item => item.isLink && item.value === textContent);
|
|
110
|
-
if (!textContent || !link) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
this.editor.commands.setMark(this.type, {
|
|
114
|
-
href: link.href,
|
|
115
|
-
});
|
|
116
|
-
return true;
|
|
117
|
-
},
|
|
118
|
-
},
|
|
210
|
+
plugins.push(pasteHandler({
|
|
211
|
+
editor: this.editor,
|
|
212
|
+
type: this.type,
|
|
119
213
|
}));
|
|
120
214
|
}
|
|
121
215
|
return plugins;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-extension-link.cjs.js","sources":["../src/link.ts"],"sourcesContent":["import {\n Mark,\n markPasteRule,\n mergeAttributes,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { find } from 'linkifyjs'\n\nexport interface LinkOptions {\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n inclusive: false,\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark('link', attributes)\n },\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark('link', attributes, { extendEmptyMarkRange: true })\n },\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark('link', { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.openOnClick) {\n plugins.push(\n new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = this.editor.getAttributes('link')\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n 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)\n .find(item => item.isLink && item.value === textContent)\n\n if (!textContent || !link) {\n return false\n }\n\n this.editor.commands.setMark(this.type, {\n href: link.href,\n })\n\n return true\n },\n },\n }),\n )\n }\n\n return plugins\n },\n})\n"],"names":["Mark","mergeAttributes","markPasteRule","find","Plugin","PluginKey"],"mappings":";;;;;;;;MA0Ca,IAAI,GAAGA,SAAI,CAAC,MAAM,CAAc;IAC3C,IAAI,EAAE,MAAM;IAEZ,QAAQ,EAAE,IAAI;IAEd,SAAS,EAAE,KAAK;IAEhB,UAAU;QACR,OAAO;YACL,WAAW,EAAE,IAAI;YACjB,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE;gBACd,MAAM,EAAE,QAAQ;gBAChB,GAAG,EAAE,8BAA8B;aACpC;SACF,CAAA;KACF;IAED,aAAa;QACX,OAAO;YACL,IAAI,EAAE;gBACJ,OAAO,EAAE,IAAI;aACd;YACD,MAAM,EAAE;gBACN,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM;aAC5C;SACF,CAAA;KACF;IAED,SAAS;QACP,OAAO;YACL,EAAE,GAAG,EAAE,SAAS,EAAE;SACnB,CAAA;KACF;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,GAAG,EAAEC,oBAAe,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,CAAA;KAC9E;IAED,WAAW;QACT,OAAO;YACL,OAAO,EAAE,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE;gBAClC,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;aAC5C;YACD,UAAU,EAAE,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE;gBACrC,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAA;aAC/E;YACD,SAAS,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE;gBAC5B,OAAO,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAA;aAClE;SACF,CAAA;KACF;IAED,aAAa;QACX,OAAO;YACLC,kBAAa,CAAC;gBACZ,IAAI,EAAE,IAAI,IAAIC,cAAI,CAAC,IAAI,CAAC;qBACrB,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;qBAC3B,GAAG,CAAC,IAAI,KAAK;oBACZ,IAAI,EAAE,IAAI,CAAC,KAAK;oBAChB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,IAAI,EAAE,IAAI;iBACX,CAAC,CAAC;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,aAAa,EAAE,KAAK;;oBAAI,QAAC;wBACvB,IAAI,EAAE,MAAA,KAAK,CAAC,IAAI,0CAAE,IAAI;qBACvB,EAAC;iBAAA;aACH,CAAC;SACH,CAAA;KACF;IAED,qBAAqB;QACnB,MAAM,OAAO,GAAG,EAAE,CAAA;QAElB,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAC5B,OAAO,CAAC,IAAI,CACV,IAAIC,uBAAM,CAAC;gBACT,GAAG,EAAE,IAAIC,0BAAS,CAAC,iBAAiB,CAAC;gBACrC,KAAK,EAAE;oBACL,WAAW,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK;;wBAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;wBAC/C,MAAM,IAAI,GAAG,MAAC,KAAK,CAAC,MAAsB,0CAAE,OAAO,CAAC,GAAG,CAAC,CAAA;wBAExD,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE;4BACtB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;4BAErC,OAAO,IAAI,CAAA;yBACZ;wBAED,OAAO,KAAK,CAAA;qBACb;iBACF;aACF,CAAC,CACH,CAAA;SACF;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAC5B,OAAO,CAAC,IAAI,CACV,IAAID,uBAAM,CAAC;gBACT,GAAG,EAAE,IAAIC,0BAAS,CAAC,iBAAiB,CAAC;gBACrC,KAAK,EAAE;oBACL,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK;wBAC9B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;wBACtB,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;wBAC3B,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAA;wBAE3B,IAAI,KAAK,EAAE;4BACT,OAAO,KAAK,CAAA;yBACb;wBAED,IAAI,WAAW,GAAG,EAAE,CAAA;wBAEpB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI;4BACxB,WAAW,IAAI,IAAI,CAAC,WAAW,CAAA;yBAChC,CAAC,CAAA;wBAEF,MAAM,IAAI,GAAGF,cAAI,CAAC,WAAW,CAAC;6BAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAA;wBAE1D,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE;4BACzB,OAAO,KAAK,CAAA;yBACb;wBAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;4BACtC,IAAI,EAAE,IAAI,CAAC,IAAI;yBAChB,CAAC,CAAA;wBAEF,OAAO,IAAI,CAAA;qBACZ;iBACF;aACF,CAAC,CACH,CAAA;SACF;QAED,OAAO,OAAO,CAAA;KACf;CACF;;;;;"}
|
|
1
|
+
{"version":3,"file":"tiptap-extension-link.cjs.js","sources":["../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts","../src/link.ts"],"sourcesContent":["import {\n getMarksBetween,\n findChildrenInRange,\n combineTransactionSteps,\n getChangedRanges,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find, test } from 'linkifyjs'\n\ntype AutolinkOptions = {\n type: MarkType,\n}\n\nexport default function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n const docChanges = transactions.some(transaction => transaction.docChanged)\n && !oldState.doc.eq(newState.doc)\n\n if (!docChanges) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, transactions)\n const { mapping } = transform\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ oldRange, newRange }) => {\n // at first we check if we have to remove links\n getMarksBetween(oldRange.from, oldRange.to, oldState.doc)\n .filter(item => item.mark.type === options.type)\n .forEach(oldMark => {\n const newFrom = mapping.map(oldMark.from)\n const newTo = mapping.map(oldMark.to)\n const newMarks = getMarksBetween(newFrom, newTo, newState.doc)\n .filter(item => item.mark.type === options.type)\n\n if (!newMarks.length) {\n return\n }\n\n const newMark = newMarks[0]\n const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to)\n const newLinkText = newState.doc.textBetween(newMark.from, newMark.to)\n const wasLink = test(oldLinkText)\n const isLink = test(newLinkText)\n\n // remove only the link, if it was a link before too\n // because we don’t want to remove links that were set manually\n if (wasLink && !isLink) {\n tr.removeMark(newMark.from, newMark.to, options.type)\n }\n })\n\n // now let’s see if we can add new links\n findChildrenInRange(newState.doc, newRange, node => node.isTextblock)\n .forEach(textBlock => {\n find(textBlock.node.textContent)\n .filter(link => link.isLink)\n // calculate link position\n .map(link => ({\n ...link,\n from: textBlock.pos + link.start + 1,\n to: textBlock.pos + link.end + 1,\n }))\n // check if link is within the changed range\n .filter(link => {\n const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to\n const toIsInRange = newRange.to >= link.from && newRange.to <= link.to\n\n return fromIsInRange || toIsInRange\n })\n // add link mark\n .forEach(link => {\n tr.addMark(link.from, link.to, options.type.create({\n href: link.href,\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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\n\ntype ClickHandlerOptions = {\n type: MarkType,\n}\n\nexport default function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = getAttributes(view.state, options.type.name)\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor,\n type: MarkType,\n}\n\nexport default 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).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 { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'\nimport { find } from 'linkifyjs'\nimport autolink from './helpers/autolink'\nimport clickHandler from './helpers/clickHandler'\nimport pasteHandler from './helpers/pasteHandler'\n\nexport interface LinkOptions {\n /**\n * If enabled, it adds links as you type.\n */\n autolink: boolean,\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'a',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),\n 0,\n ]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark(this.name, attributes)\n },\n\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n },\n\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark(this.name, { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.autolink) {\n plugins.push(autolink({\n type: this.type,\n }))\n }\n\n if (this.options.openOnClick) {\n plugins.push(clickHandler({\n type: this.type,\n }))\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(pasteHandler({\n editor: this.editor,\n type: this.type,\n }))\n }\n\n return plugins\n },\n})\n"],"names":["Plugin","PluginKey","combineTransactionSteps","getChangedRanges","getMarksBetween","test","findChildrenInRange","find","getAttributes","Mark","mergeAttributes","markPasteRule"],"mappings":"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"}
|
|
@@ -1,15 +1,134 @@
|
|
|
1
|
-
import { Mark, mergeAttributes, markPasteRule } from '@tiptap/core';
|
|
1
|
+
import { combineTransactionSteps, getChangedRanges, getMarksBetween, findChildrenInRange, getAttributes, Mark, mergeAttributes, markPasteRule } from '@tiptap/core';
|
|
2
|
+
import { test, find } from 'linkifyjs';
|
|
2
3
|
import { Plugin, PluginKey } from 'prosemirror-state';
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
function autolink(options) {
|
|
6
|
+
return new Plugin({
|
|
7
|
+
key: new PluginKey('autolink'),
|
|
8
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
9
|
+
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
10
|
+
&& !oldState.doc.eq(newState.doc);
|
|
11
|
+
if (!docChanges) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const { tr } = newState;
|
|
15
|
+
const transform = combineTransactionSteps(oldState.doc, transactions);
|
|
16
|
+
const { mapping } = transform;
|
|
17
|
+
const changes = getChangedRanges(transform);
|
|
18
|
+
changes.forEach(({ oldRange, newRange }) => {
|
|
19
|
+
// at first we check if we have to remove links
|
|
20
|
+
getMarksBetween(oldRange.from, oldRange.to, oldState.doc)
|
|
21
|
+
.filter(item => item.mark.type === options.type)
|
|
22
|
+
.forEach(oldMark => {
|
|
23
|
+
const newFrom = mapping.map(oldMark.from);
|
|
24
|
+
const newTo = mapping.map(oldMark.to);
|
|
25
|
+
const newMarks = getMarksBetween(newFrom, newTo, newState.doc)
|
|
26
|
+
.filter(item => item.mark.type === options.type);
|
|
27
|
+
if (!newMarks.length) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const newMark = newMarks[0];
|
|
31
|
+
const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to);
|
|
32
|
+
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
|
|
33
|
+
const wasLink = test(oldLinkText);
|
|
34
|
+
const isLink = test(newLinkText);
|
|
35
|
+
// remove only the link, if it was a link before too
|
|
36
|
+
// because we don’t want to remove links that were set manually
|
|
37
|
+
if (wasLink && !isLink) {
|
|
38
|
+
tr.removeMark(newMark.from, newMark.to, options.type);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// now let’s see if we can add new links
|
|
42
|
+
findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
|
|
43
|
+
.forEach(textBlock => {
|
|
44
|
+
find(textBlock.node.textContent)
|
|
45
|
+
.filter(link => link.isLink)
|
|
46
|
+
// calculate link position
|
|
47
|
+
.map(link => ({
|
|
48
|
+
...link,
|
|
49
|
+
from: textBlock.pos + link.start + 1,
|
|
50
|
+
to: textBlock.pos + link.end + 1,
|
|
51
|
+
}))
|
|
52
|
+
// check if link is within the changed range
|
|
53
|
+
.filter(link => {
|
|
54
|
+
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
|
|
55
|
+
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
|
|
56
|
+
return fromIsInRange || toIsInRange;
|
|
57
|
+
})
|
|
58
|
+
// add link mark
|
|
59
|
+
.forEach(link => {
|
|
60
|
+
tr.addMark(link.from, link.to, options.type.create({
|
|
61
|
+
href: link.href,
|
|
62
|
+
}));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
if (!tr.steps.length) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
return tr;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function clickHandler(options) {
|
|
75
|
+
return new Plugin({
|
|
76
|
+
key: new PluginKey('handleClickLink'),
|
|
77
|
+
props: {
|
|
78
|
+
handleClick: (view, pos, event) => {
|
|
79
|
+
var _a;
|
|
80
|
+
const attrs = getAttributes(view.state, options.type.name);
|
|
81
|
+
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
82
|
+
if (link && attrs.href) {
|
|
83
|
+
window.open(attrs.href, attrs.target);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function pasteHandler(options) {
|
|
93
|
+
return new Plugin({
|
|
94
|
+
key: new PluginKey('handlePasteLink'),
|
|
95
|
+
props: {
|
|
96
|
+
handlePaste: (view, event, slice) => {
|
|
97
|
+
const { state } = view;
|
|
98
|
+
const { selection } = state;
|
|
99
|
+
const { empty } = selection;
|
|
100
|
+
if (empty) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
let textContent = '';
|
|
104
|
+
slice.content.forEach(node => {
|
|
105
|
+
textContent += node.textContent;
|
|
106
|
+
});
|
|
107
|
+
const link = find(textContent).find(item => item.isLink && item.value === textContent);
|
|
108
|
+
if (!textContent || !link) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
options.editor.commands.setMark(options.type, {
|
|
112
|
+
href: link.href,
|
|
113
|
+
});
|
|
114
|
+
return true;
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
4
119
|
|
|
5
120
|
const Link = Mark.create({
|
|
6
121
|
name: 'link',
|
|
7
122
|
priority: 1000,
|
|
8
|
-
|
|
123
|
+
keepOnSplit: false,
|
|
124
|
+
inclusive() {
|
|
125
|
+
return this.options.autolink;
|
|
126
|
+
},
|
|
9
127
|
addOptions() {
|
|
10
128
|
return {
|
|
11
129
|
openOnClick: true,
|
|
12
130
|
linkOnPaste: true,
|
|
131
|
+
autolink: true,
|
|
13
132
|
HTMLAttributes: {
|
|
14
133
|
target: '_blank',
|
|
15
134
|
rel: 'noopener noreferrer nofollow',
|
|
@@ -32,18 +151,22 @@ const Link = Mark.create({
|
|
|
32
151
|
];
|
|
33
152
|
},
|
|
34
153
|
renderHTML({ HTMLAttributes }) {
|
|
35
|
-
return [
|
|
154
|
+
return [
|
|
155
|
+
'a',
|
|
156
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
157
|
+
0,
|
|
158
|
+
];
|
|
36
159
|
},
|
|
37
160
|
addCommands() {
|
|
38
161
|
return {
|
|
39
162
|
setLink: attributes => ({ commands }) => {
|
|
40
|
-
return commands.setMark(
|
|
163
|
+
return commands.setMark(this.name, attributes);
|
|
41
164
|
},
|
|
42
165
|
toggleLink: attributes => ({ commands }) => {
|
|
43
|
-
return commands.toggleMark(
|
|
166
|
+
return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true });
|
|
44
167
|
},
|
|
45
168
|
unsetLink: () => ({ commands }) => {
|
|
46
|
-
return commands.unsetMark(
|
|
169
|
+
return commands.unsetMark(this.name, { extendEmptyMarkRange: true });
|
|
47
170
|
},
|
|
48
171
|
};
|
|
49
172
|
},
|
|
@@ -69,49 +192,20 @@ const Link = Mark.create({
|
|
|
69
192
|
},
|
|
70
193
|
addProseMirrorPlugins() {
|
|
71
194
|
const plugins = [];
|
|
195
|
+
if (this.options.autolink) {
|
|
196
|
+
plugins.push(autolink({
|
|
197
|
+
type: this.type,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
72
200
|
if (this.options.openOnClick) {
|
|
73
|
-
plugins.push(
|
|
74
|
-
|
|
75
|
-
props: {
|
|
76
|
-
handleClick: (view, pos, event) => {
|
|
77
|
-
var _a;
|
|
78
|
-
const attrs = this.editor.getAttributes('link');
|
|
79
|
-
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
80
|
-
if (link && attrs.href) {
|
|
81
|
-
window.open(attrs.href, attrs.target);
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
},
|
|
86
|
-
},
|
|
201
|
+
plugins.push(clickHandler({
|
|
202
|
+
type: this.type,
|
|
87
203
|
}));
|
|
88
204
|
}
|
|
89
205
|
if (this.options.linkOnPaste) {
|
|
90
|
-
plugins.push(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
handlePaste: (view, event, slice) => {
|
|
94
|
-
const { state } = view;
|
|
95
|
-
const { selection } = state;
|
|
96
|
-
const { empty } = selection;
|
|
97
|
-
if (empty) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
let textContent = '';
|
|
101
|
-
slice.content.forEach(node => {
|
|
102
|
-
textContent += node.textContent;
|
|
103
|
-
});
|
|
104
|
-
const link = find(textContent)
|
|
105
|
-
.find(item => item.isLink && item.value === textContent);
|
|
106
|
-
if (!textContent || !link) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
this.editor.commands.setMark(this.type, {
|
|
110
|
-
href: link.href,
|
|
111
|
-
});
|
|
112
|
-
return true;
|
|
113
|
-
},
|
|
114
|
-
},
|
|
206
|
+
plugins.push(pasteHandler({
|
|
207
|
+
editor: this.editor,
|
|
208
|
+
type: this.type,
|
|
115
209
|
}));
|
|
116
210
|
}
|
|
117
211
|
return plugins;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-extension-link.esm.js","sources":["../src/link.ts"],"sourcesContent":["import {\n Mark,\n markPasteRule,\n mergeAttributes,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { find } from 'linkifyjs'\n\nexport interface LinkOptions {\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n inclusive: false,\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark('link', attributes)\n },\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark('link', attributes, { extendEmptyMarkRange: true })\n },\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark('link', { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.openOnClick) {\n plugins.push(\n new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = this.editor.getAttributes('link')\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n 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)\n .find(item => item.isLink && item.value === textContent)\n\n if (!textContent || !link) {\n return false\n }\n\n this.editor.commands.setMark(this.type, {\n href: link.href,\n })\n\n return true\n },\n },\n }),\n )\n }\n\n return plugins\n },\n})\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tiptap-extension-link.esm.js","sources":["../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts","../src/link.ts"],"sourcesContent":["import {\n getMarksBetween,\n findChildrenInRange,\n combineTransactionSteps,\n getChangedRanges,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find, test } from 'linkifyjs'\n\ntype AutolinkOptions = {\n type: MarkType,\n}\n\nexport default function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n const docChanges = transactions.some(transaction => transaction.docChanged)\n && !oldState.doc.eq(newState.doc)\n\n if (!docChanges) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, transactions)\n const { mapping } = transform\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ oldRange, newRange }) => {\n // at first we check if we have to remove links\n getMarksBetween(oldRange.from, oldRange.to, oldState.doc)\n .filter(item => item.mark.type === options.type)\n .forEach(oldMark => {\n const newFrom = mapping.map(oldMark.from)\n const newTo = mapping.map(oldMark.to)\n const newMarks = getMarksBetween(newFrom, newTo, newState.doc)\n .filter(item => item.mark.type === options.type)\n\n if (!newMarks.length) {\n return\n }\n\n const newMark = newMarks[0]\n const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to)\n const newLinkText = newState.doc.textBetween(newMark.from, newMark.to)\n const wasLink = test(oldLinkText)\n const isLink = test(newLinkText)\n\n // remove only the link, if it was a link before too\n // because we don’t want to remove links that were set manually\n if (wasLink && !isLink) {\n tr.removeMark(newMark.from, newMark.to, options.type)\n }\n })\n\n // now let’s see if we can add new links\n findChildrenInRange(newState.doc, newRange, node => node.isTextblock)\n .forEach(textBlock => {\n find(textBlock.node.textContent)\n .filter(link => link.isLink)\n // calculate link position\n .map(link => ({\n ...link,\n from: textBlock.pos + link.start + 1,\n to: textBlock.pos + link.end + 1,\n }))\n // check if link is within the changed range\n .filter(link => {\n const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to\n const toIsInRange = newRange.to >= link.from && newRange.to <= link.to\n\n return fromIsInRange || toIsInRange\n })\n // add link mark\n .forEach(link => {\n tr.addMark(link.from, link.to, options.type.create({\n href: link.href,\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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\n\ntype ClickHandlerOptions = {\n type: MarkType,\n}\n\nexport default function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = getAttributes(view.state, options.type.name)\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor,\n type: MarkType,\n}\n\nexport default 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).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 { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'\nimport { find } from 'linkifyjs'\nimport autolink from './helpers/autolink'\nimport clickHandler from './helpers/clickHandler'\nimport pasteHandler from './helpers/pasteHandler'\n\nexport interface LinkOptions {\n /**\n * If enabled, it adds links as you type.\n */\n autolink: boolean,\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'a',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),\n 0,\n ]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark(this.name, attributes)\n },\n\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n },\n\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark(this.name, { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.autolink) {\n plugins.push(autolink({\n type: this.type,\n }))\n }\n\n if (this.options.openOnClick) {\n plugins.push(clickHandler({\n type: this.type,\n }))\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(pasteHandler({\n editor: this.editor,\n type: this.type,\n }))\n }\n\n return plugins\n },\n})\n"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,136 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', '
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-link"] = {}, global.core, global.
|
|
5
|
-
})(this, (function (exports, core,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('linkifyjs'), require('prosemirror-state')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', 'linkifyjs', 'prosemirror-state'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-link"] = {}, global.core, global.linkifyjs, global.prosemirrorState));
|
|
5
|
+
})(this, (function (exports, core, linkifyjs, prosemirrorState) { 'use strict';
|
|
6
|
+
|
|
7
|
+
function autolink(options) {
|
|
8
|
+
return new prosemirrorState.Plugin({
|
|
9
|
+
key: new prosemirrorState.PluginKey('autolink'),
|
|
10
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
11
|
+
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
12
|
+
&& !oldState.doc.eq(newState.doc);
|
|
13
|
+
if (!docChanges) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const { tr } = newState;
|
|
17
|
+
const transform = core.combineTransactionSteps(oldState.doc, transactions);
|
|
18
|
+
const { mapping } = transform;
|
|
19
|
+
const changes = core.getChangedRanges(transform);
|
|
20
|
+
changes.forEach(({ oldRange, newRange }) => {
|
|
21
|
+
// at first we check if we have to remove links
|
|
22
|
+
core.getMarksBetween(oldRange.from, oldRange.to, oldState.doc)
|
|
23
|
+
.filter(item => item.mark.type === options.type)
|
|
24
|
+
.forEach(oldMark => {
|
|
25
|
+
const newFrom = mapping.map(oldMark.from);
|
|
26
|
+
const newTo = mapping.map(oldMark.to);
|
|
27
|
+
const newMarks = core.getMarksBetween(newFrom, newTo, newState.doc)
|
|
28
|
+
.filter(item => item.mark.type === options.type);
|
|
29
|
+
if (!newMarks.length) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const newMark = newMarks[0];
|
|
33
|
+
const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to);
|
|
34
|
+
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to);
|
|
35
|
+
const wasLink = linkifyjs.test(oldLinkText);
|
|
36
|
+
const isLink = linkifyjs.test(newLinkText);
|
|
37
|
+
// remove only the link, if it was a link before too
|
|
38
|
+
// because we don’t want to remove links that were set manually
|
|
39
|
+
if (wasLink && !isLink) {
|
|
40
|
+
tr.removeMark(newMark.from, newMark.to, options.type);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// now let’s see if we can add new links
|
|
44
|
+
core.findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
|
|
45
|
+
.forEach(textBlock => {
|
|
46
|
+
linkifyjs.find(textBlock.node.textContent)
|
|
47
|
+
.filter(link => link.isLink)
|
|
48
|
+
// calculate link position
|
|
49
|
+
.map(link => ({
|
|
50
|
+
...link,
|
|
51
|
+
from: textBlock.pos + link.start + 1,
|
|
52
|
+
to: textBlock.pos + link.end + 1,
|
|
53
|
+
}))
|
|
54
|
+
// check if link is within the changed range
|
|
55
|
+
.filter(link => {
|
|
56
|
+
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to;
|
|
57
|
+
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to;
|
|
58
|
+
return fromIsInRange || toIsInRange;
|
|
59
|
+
})
|
|
60
|
+
// add link mark
|
|
61
|
+
.forEach(link => {
|
|
62
|
+
tr.addMark(link.from, link.to, options.type.create({
|
|
63
|
+
href: link.href,
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
if (!tr.steps.length) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
return tr;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clickHandler(options) {
|
|
77
|
+
return new prosemirrorState.Plugin({
|
|
78
|
+
key: new prosemirrorState.PluginKey('handleClickLink'),
|
|
79
|
+
props: {
|
|
80
|
+
handleClick: (view, pos, event) => {
|
|
81
|
+
var _a;
|
|
82
|
+
const attrs = core.getAttributes(view.state, options.type.name);
|
|
83
|
+
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
84
|
+
if (link && attrs.href) {
|
|
85
|
+
window.open(attrs.href, attrs.target);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pasteHandler(options) {
|
|
95
|
+
return new prosemirrorState.Plugin({
|
|
96
|
+
key: new prosemirrorState.PluginKey('handlePasteLink'),
|
|
97
|
+
props: {
|
|
98
|
+
handlePaste: (view, event, slice) => {
|
|
99
|
+
const { state } = view;
|
|
100
|
+
const { selection } = state;
|
|
101
|
+
const { empty } = selection;
|
|
102
|
+
if (empty) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
let textContent = '';
|
|
106
|
+
slice.content.forEach(node => {
|
|
107
|
+
textContent += node.textContent;
|
|
108
|
+
});
|
|
109
|
+
const link = linkifyjs.find(textContent).find(item => item.isLink && item.value === textContent);
|
|
110
|
+
if (!textContent || !link) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
options.editor.commands.setMark(options.type, {
|
|
114
|
+
href: link.href,
|
|
115
|
+
});
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
6
121
|
|
|
7
122
|
const Link = core.Mark.create({
|
|
8
123
|
name: 'link',
|
|
9
124
|
priority: 1000,
|
|
10
|
-
|
|
125
|
+
keepOnSplit: false,
|
|
126
|
+
inclusive() {
|
|
127
|
+
return this.options.autolink;
|
|
128
|
+
},
|
|
11
129
|
addOptions() {
|
|
12
130
|
return {
|
|
13
131
|
openOnClick: true,
|
|
14
132
|
linkOnPaste: true,
|
|
133
|
+
autolink: true,
|
|
15
134
|
HTMLAttributes: {
|
|
16
135
|
target: '_blank',
|
|
17
136
|
rel: 'noopener noreferrer nofollow',
|
|
@@ -34,18 +153,22 @@
|
|
|
34
153
|
];
|
|
35
154
|
},
|
|
36
155
|
renderHTML({ HTMLAttributes }) {
|
|
37
|
-
return [
|
|
156
|
+
return [
|
|
157
|
+
'a',
|
|
158
|
+
core.mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
159
|
+
0,
|
|
160
|
+
];
|
|
38
161
|
},
|
|
39
162
|
addCommands() {
|
|
40
163
|
return {
|
|
41
164
|
setLink: attributes => ({ commands }) => {
|
|
42
|
-
return commands.setMark(
|
|
165
|
+
return commands.setMark(this.name, attributes);
|
|
43
166
|
},
|
|
44
167
|
toggleLink: attributes => ({ commands }) => {
|
|
45
|
-
return commands.toggleMark(
|
|
168
|
+
return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true });
|
|
46
169
|
},
|
|
47
170
|
unsetLink: () => ({ commands }) => {
|
|
48
|
-
return commands.unsetMark(
|
|
171
|
+
return commands.unsetMark(this.name, { extendEmptyMarkRange: true });
|
|
49
172
|
},
|
|
50
173
|
};
|
|
51
174
|
},
|
|
@@ -71,49 +194,20 @@
|
|
|
71
194
|
},
|
|
72
195
|
addProseMirrorPlugins() {
|
|
73
196
|
const plugins = [];
|
|
197
|
+
if (this.options.autolink) {
|
|
198
|
+
plugins.push(autolink({
|
|
199
|
+
type: this.type,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
74
202
|
if (this.options.openOnClick) {
|
|
75
|
-
plugins.push(
|
|
76
|
-
|
|
77
|
-
props: {
|
|
78
|
-
handleClick: (view, pos, event) => {
|
|
79
|
-
var _a;
|
|
80
|
-
const attrs = this.editor.getAttributes('link');
|
|
81
|
-
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
|
82
|
-
if (link && attrs.href) {
|
|
83
|
-
window.open(attrs.href, attrs.target);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
},
|
|
88
|
-
},
|
|
203
|
+
plugins.push(clickHandler({
|
|
204
|
+
type: this.type,
|
|
89
205
|
}));
|
|
90
206
|
}
|
|
91
207
|
if (this.options.linkOnPaste) {
|
|
92
|
-
plugins.push(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
handlePaste: (view, event, slice) => {
|
|
96
|
-
const { state } = view;
|
|
97
|
-
const { selection } = state;
|
|
98
|
-
const { empty } = selection;
|
|
99
|
-
if (empty) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
let textContent = '';
|
|
103
|
-
slice.content.forEach(node => {
|
|
104
|
-
textContent += node.textContent;
|
|
105
|
-
});
|
|
106
|
-
const link = linkifyjs.find(textContent)
|
|
107
|
-
.find(item => item.isLink && item.value === textContent);
|
|
108
|
-
if (!textContent || !link) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
this.editor.commands.setMark(this.type, {
|
|
112
|
-
href: link.href,
|
|
113
|
-
});
|
|
114
|
-
return true;
|
|
115
|
-
},
|
|
116
|
-
},
|
|
208
|
+
plugins.push(pasteHandler({
|
|
209
|
+
editor: this.editor,
|
|
210
|
+
type: this.type,
|
|
117
211
|
}));
|
|
118
212
|
}
|
|
119
213
|
return plugins;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tiptap-extension-link.umd.js","sources":["../src/link.ts"],"sourcesContent":["import {\n Mark,\n markPasteRule,\n mergeAttributes,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { find } from 'linkifyjs'\n\nexport interface LinkOptions {\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n inclusive: false,\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark('link', attributes)\n },\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark('link', attributes, { extendEmptyMarkRange: true })\n },\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark('link', { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.openOnClick) {\n plugins.push(\n new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = this.editor.getAttributes('link')\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n }),\n )\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(\n 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)\n .find(item => item.isLink && item.value === textContent)\n\n if (!textContent || !link) {\n return false\n }\n\n this.editor.commands.setMark(this.type, {\n href: link.href,\n })\n\n return true\n },\n },\n }),\n )\n }\n\n return plugins\n },\n})\n"],"names":["Mark","mergeAttributes","markPasteRule","find","Plugin","PluginKey"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tiptap-extension-link.umd.js","sources":["../src/helpers/autolink.ts","../src/helpers/clickHandler.ts","../src/helpers/pasteHandler.ts","../src/link.ts"],"sourcesContent":["import {\n getMarksBetween,\n findChildrenInRange,\n combineTransactionSteps,\n getChangedRanges,\n} from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find, test } from 'linkifyjs'\n\ntype AutolinkOptions = {\n type: MarkType,\n}\n\nexport default function autolink(options: AutolinkOptions): Plugin {\n return new Plugin({\n key: new PluginKey('autolink'),\n appendTransaction: (transactions, oldState, newState) => {\n const docChanges = transactions.some(transaction => transaction.docChanged)\n && !oldState.doc.eq(newState.doc)\n\n if (!docChanges) {\n return\n }\n\n const { tr } = newState\n const transform = combineTransactionSteps(oldState.doc, transactions)\n const { mapping } = transform\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ oldRange, newRange }) => {\n // at first we check if we have to remove links\n getMarksBetween(oldRange.from, oldRange.to, oldState.doc)\n .filter(item => item.mark.type === options.type)\n .forEach(oldMark => {\n const newFrom = mapping.map(oldMark.from)\n const newTo = mapping.map(oldMark.to)\n const newMarks = getMarksBetween(newFrom, newTo, newState.doc)\n .filter(item => item.mark.type === options.type)\n\n if (!newMarks.length) {\n return\n }\n\n const newMark = newMarks[0]\n const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to)\n const newLinkText = newState.doc.textBetween(newMark.from, newMark.to)\n const wasLink = test(oldLinkText)\n const isLink = test(newLinkText)\n\n // remove only the link, if it was a link before too\n // because we don’t want to remove links that were set manually\n if (wasLink && !isLink) {\n tr.removeMark(newMark.from, newMark.to, options.type)\n }\n })\n\n // now let’s see if we can add new links\n findChildrenInRange(newState.doc, newRange, node => node.isTextblock)\n .forEach(textBlock => {\n find(textBlock.node.textContent)\n .filter(link => link.isLink)\n // calculate link position\n .map(link => ({\n ...link,\n from: textBlock.pos + link.start + 1,\n to: textBlock.pos + link.end + 1,\n }))\n // check if link is within the changed range\n .filter(link => {\n const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to\n const toIsInRange = newRange.to >= link.from && newRange.to <= link.to\n\n return fromIsInRange || toIsInRange\n })\n // add link mark\n .forEach(link => {\n tr.addMark(link.from, link.to, options.type.create({\n href: link.href,\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 { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\n\ntype ClickHandlerOptions = {\n type: MarkType,\n}\n\nexport default function clickHandler(options: ClickHandlerOptions): Plugin {\n return new Plugin({\n key: new PluginKey('handleClickLink'),\n props: {\n handleClick: (view, pos, event) => {\n const attrs = getAttributes(view.state, options.type.name)\n const link = (event.target as HTMLElement)?.closest('a')\n\n if (link && attrs.href) {\n window.open(attrs.href, attrs.target)\n\n return true\n }\n\n return false\n },\n },\n })\n}\n","import { Editor } from '@tiptap/core'\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { MarkType } from 'prosemirror-model'\nimport { find } from 'linkifyjs'\n\ntype PasteHandlerOptions = {\n editor: Editor,\n type: MarkType,\n}\n\nexport default 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).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 { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'\nimport { find } from 'linkifyjs'\nimport autolink from './helpers/autolink'\nimport clickHandler from './helpers/clickHandler'\nimport pasteHandler from './helpers/pasteHandler'\n\nexport interface LinkOptions {\n /**\n * If enabled, it adds links as you type.\n */\n autolink: boolean,\n /**\n * If enabled, links will be opened on click.\n */\n openOnClick: boolean,\n /**\n * Adds a link to the current selection if the pasted content only contains an url.\n */\n linkOnPaste: boolean,\n /**\n * A list of HTML attributes to be rendered.\n */\n HTMLAttributes: Record<string, any>,\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n link: {\n /**\n * Set a link mark\n */\n setLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Toggle a link mark\n */\n toggleLink: (attributes: { href: string, target?: string }) => ReturnType,\n /**\n * Unset a link mark\n */\n unsetLink: () => ReturnType,\n }\n }\n}\n\nexport const Link = Mark.create<LinkOptions>({\n name: 'link',\n\n priority: 1000,\n\n keepOnSplit: false,\n\n inclusive() {\n return this.options.autolink\n },\n\n addOptions() {\n return {\n openOnClick: true,\n linkOnPaste: true,\n autolink: true,\n HTMLAttributes: {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n },\n }\n },\n\n addAttributes() {\n return {\n href: {\n default: null,\n },\n target: {\n default: this.options.HTMLAttributes.target,\n },\n }\n },\n\n parseHTML() {\n return [\n { tag: 'a[href]' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\n 'a',\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),\n 0,\n ]\n },\n\n addCommands() {\n return {\n setLink: attributes => ({ commands }) => {\n return commands.setMark(this.name, attributes)\n },\n\n toggleLink: attributes => ({ commands }) => {\n return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })\n },\n\n unsetLink: () => ({ commands }) => {\n return commands.unsetMark(this.name, { extendEmptyMarkRange: true })\n },\n }\n },\n\n addPasteRules() {\n return [\n markPasteRule({\n find: text => find(text)\n .filter(link => link.isLink)\n .map(link => ({\n text: link.value,\n index: link.start,\n data: link,\n })),\n type: this.type,\n getAttributes: match => ({\n href: match.data?.href,\n }),\n }),\n ]\n },\n\n addProseMirrorPlugins() {\n const plugins = []\n\n if (this.options.autolink) {\n plugins.push(autolink({\n type: this.type,\n }))\n }\n\n if (this.options.openOnClick) {\n plugins.push(clickHandler({\n type: this.type,\n }))\n }\n\n if (this.options.linkOnPaste) {\n plugins.push(pasteHandler({\n editor: this.editor,\n type: this.type,\n }))\n }\n\n return plugins\n },\n})\n"],"names":["Plugin","PluginKey","combineTransactionSteps","getChangedRanges","getMarksBetween","test","findChildrenInRange","find","getAttributes","Mark","mergeAttributes","markPasteRule"],"mappings":"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"}
|
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": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.31",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"@tiptap/core": "^2.0.0-beta.1"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"linkifyjs": "^3.0.
|
|
27
|
+
"linkifyjs": "^3.0.4",
|
|
28
|
+
"prosemirror-model": "^1.15.0",
|
|
28
29
|
"prosemirror-state": "^1.3.4"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
@@ -32,5 +33,5 @@
|
|
|
32
33
|
"url": "https://github.com/ueberdosis/tiptap",
|
|
33
34
|
"directory": "packages/extension-link"
|
|
34
35
|
},
|
|
35
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "4e1a50250bc5d3ef916326e5bed30c93448284b3"
|
|
36
37
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMarksBetween,
|
|
3
|
+
findChildrenInRange,
|
|
4
|
+
combineTransactionSteps,
|
|
5
|
+
getChangedRanges,
|
|
6
|
+
} from '@tiptap/core'
|
|
7
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
8
|
+
import { MarkType } from 'prosemirror-model'
|
|
9
|
+
import { find, test } from 'linkifyjs'
|
|
10
|
+
|
|
11
|
+
type AutolinkOptions = {
|
|
12
|
+
type: MarkType,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function autolink(options: AutolinkOptions): Plugin {
|
|
16
|
+
return new Plugin({
|
|
17
|
+
key: new PluginKey('autolink'),
|
|
18
|
+
appendTransaction: (transactions, oldState, newState) => {
|
|
19
|
+
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
20
|
+
&& !oldState.doc.eq(newState.doc)
|
|
21
|
+
|
|
22
|
+
if (!docChanges) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { tr } = newState
|
|
27
|
+
const transform = combineTransactionSteps(oldState.doc, transactions)
|
|
28
|
+
const { mapping } = transform
|
|
29
|
+
const changes = getChangedRanges(transform)
|
|
30
|
+
|
|
31
|
+
changes.forEach(({ oldRange, newRange }) => {
|
|
32
|
+
// at first we check if we have to remove links
|
|
33
|
+
getMarksBetween(oldRange.from, oldRange.to, oldState.doc)
|
|
34
|
+
.filter(item => item.mark.type === options.type)
|
|
35
|
+
.forEach(oldMark => {
|
|
36
|
+
const newFrom = mapping.map(oldMark.from)
|
|
37
|
+
const newTo = mapping.map(oldMark.to)
|
|
38
|
+
const newMarks = getMarksBetween(newFrom, newTo, newState.doc)
|
|
39
|
+
.filter(item => item.mark.type === options.type)
|
|
40
|
+
|
|
41
|
+
if (!newMarks.length) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const newMark = newMarks[0]
|
|
46
|
+
const oldLinkText = oldState.doc.textBetween(oldMark.from, oldMark.to)
|
|
47
|
+
const newLinkText = newState.doc.textBetween(newMark.from, newMark.to)
|
|
48
|
+
const wasLink = test(oldLinkText)
|
|
49
|
+
const isLink = test(newLinkText)
|
|
50
|
+
|
|
51
|
+
// remove only the link, if it was a link before too
|
|
52
|
+
// because we don’t want to remove links that were set manually
|
|
53
|
+
if (wasLink && !isLink) {
|
|
54
|
+
tr.removeMark(newMark.from, newMark.to, options.type)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// now let’s see if we can add new links
|
|
59
|
+
findChildrenInRange(newState.doc, newRange, node => node.isTextblock)
|
|
60
|
+
.forEach(textBlock => {
|
|
61
|
+
find(textBlock.node.textContent)
|
|
62
|
+
.filter(link => link.isLink)
|
|
63
|
+
// calculate link position
|
|
64
|
+
.map(link => ({
|
|
65
|
+
...link,
|
|
66
|
+
from: textBlock.pos + link.start + 1,
|
|
67
|
+
to: textBlock.pos + link.end + 1,
|
|
68
|
+
}))
|
|
69
|
+
// check if link is within the changed range
|
|
70
|
+
.filter(link => {
|
|
71
|
+
const fromIsInRange = newRange.from >= link.from && newRange.from <= link.to
|
|
72
|
+
const toIsInRange = newRange.to >= link.from && newRange.to <= link.to
|
|
73
|
+
|
|
74
|
+
return fromIsInRange || toIsInRange
|
|
75
|
+
})
|
|
76
|
+
// add link mark
|
|
77
|
+
.forEach(link => {
|
|
78
|
+
tr.addMark(link.from, link.to, options.type.create({
|
|
79
|
+
href: link.href,
|
|
80
|
+
}))
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if (!tr.steps.length) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return tr
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getAttributes } from '@tiptap/core'
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
3
|
+
import { MarkType } from 'prosemirror-model'
|
|
4
|
+
|
|
5
|
+
type ClickHandlerOptions = {
|
|
6
|
+
type: MarkType,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function clickHandler(options: ClickHandlerOptions): Plugin {
|
|
10
|
+
return new Plugin({
|
|
11
|
+
key: new PluginKey('handleClickLink'),
|
|
12
|
+
props: {
|
|
13
|
+
handleClick: (view, pos, event) => {
|
|
14
|
+
const attrs = getAttributes(view.state, options.type.name)
|
|
15
|
+
const link = (event.target as HTMLElement)?.closest('a')
|
|
16
|
+
|
|
17
|
+
if (link && attrs.href) {
|
|
18
|
+
window.open(attrs.href, attrs.target)
|
|
19
|
+
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core'
|
|
2
|
+
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
3
|
+
import { MarkType } from 'prosemirror-model'
|
|
4
|
+
import { find } from 'linkifyjs'
|
|
5
|
+
|
|
6
|
+
type PasteHandlerOptions = {
|
|
7
|
+
editor: Editor,
|
|
8
|
+
type: MarkType,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function pasteHandler(options: PasteHandlerOptions): Plugin {
|
|
12
|
+
return new Plugin({
|
|
13
|
+
key: new PluginKey('handlePasteLink'),
|
|
14
|
+
props: {
|
|
15
|
+
handlePaste: (view, event, slice) => {
|
|
16
|
+
const { state } = view
|
|
17
|
+
const { selection } = state
|
|
18
|
+
const { empty } = selection
|
|
19
|
+
|
|
20
|
+
if (empty) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let textContent = ''
|
|
25
|
+
|
|
26
|
+
slice.content.forEach(node => {
|
|
27
|
+
textContent += node.textContent
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const link = find(textContent).find(item => item.isLink && item.value === textContent)
|
|
31
|
+
|
|
32
|
+
if (!textContent || !link) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
options.editor.commands.setMark(options.type, {
|
|
37
|
+
href: link.href,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return true
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
}
|
package/src/link.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Mark,
|
|
3
|
-
markPasteRule,
|
|
4
|
-
mergeAttributes,
|
|
5
|
-
} from '@tiptap/core'
|
|
6
|
-
import { Plugin, PluginKey } from 'prosemirror-state'
|
|
1
|
+
import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
|
|
7
2
|
import { find } from 'linkifyjs'
|
|
3
|
+
import autolink from './helpers/autolink'
|
|
4
|
+
import clickHandler from './helpers/clickHandler'
|
|
5
|
+
import pasteHandler from './helpers/pasteHandler'
|
|
8
6
|
|
|
9
7
|
export interface LinkOptions {
|
|
8
|
+
/**
|
|
9
|
+
* If enabled, it adds links as you type.
|
|
10
|
+
*/
|
|
11
|
+
autolink: boolean,
|
|
10
12
|
/**
|
|
11
13
|
* If enabled, links will be opened on click.
|
|
12
14
|
*/
|
|
@@ -45,12 +47,17 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
45
47
|
|
|
46
48
|
priority: 1000,
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
keepOnSplit: false,
|
|
51
|
+
|
|
52
|
+
inclusive() {
|
|
53
|
+
return this.options.autolink
|
|
54
|
+
},
|
|
49
55
|
|
|
50
56
|
addOptions() {
|
|
51
57
|
return {
|
|
52
58
|
openOnClick: true,
|
|
53
59
|
linkOnPaste: true,
|
|
60
|
+
autolink: true,
|
|
54
61
|
HTMLAttributes: {
|
|
55
62
|
target: '_blank',
|
|
56
63
|
rel: 'noopener noreferrer nofollow',
|
|
@@ -76,19 +83,25 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
76
83
|
},
|
|
77
84
|
|
|
78
85
|
renderHTML({ HTMLAttributes }) {
|
|
79
|
-
return [
|
|
86
|
+
return [
|
|
87
|
+
'a',
|
|
88
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
89
|
+
0,
|
|
90
|
+
]
|
|
80
91
|
},
|
|
81
92
|
|
|
82
93
|
addCommands() {
|
|
83
94
|
return {
|
|
84
95
|
setLink: attributes => ({ commands }) => {
|
|
85
|
-
return commands.setMark(
|
|
96
|
+
return commands.setMark(this.name, attributes)
|
|
86
97
|
},
|
|
98
|
+
|
|
87
99
|
toggleLink: attributes => ({ commands }) => {
|
|
88
|
-
return commands.toggleMark(
|
|
100
|
+
return commands.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
|
|
89
101
|
},
|
|
102
|
+
|
|
90
103
|
unsetLink: () => ({ commands }) => {
|
|
91
|
-
return commands.unsetMark(
|
|
104
|
+
return commands.unsetMark(this.name, { extendEmptyMarkRange: true })
|
|
92
105
|
},
|
|
93
106
|
}
|
|
94
107
|
},
|
|
@@ -114,64 +127,23 @@ export const Link = Mark.create<LinkOptions>({
|
|
|
114
127
|
addProseMirrorPlugins() {
|
|
115
128
|
const plugins = []
|
|
116
129
|
|
|
130
|
+
if (this.options.autolink) {
|
|
131
|
+
plugins.push(autolink({
|
|
132
|
+
type: this.type,
|
|
133
|
+
}))
|
|
134
|
+
}
|
|
135
|
+
|
|
117
136
|
if (this.options.openOnClick) {
|
|
118
|
-
plugins.push(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
props: {
|
|
122
|
-
handleClick: (view, pos, event) => {
|
|
123
|
-
const attrs = this.editor.getAttributes('link')
|
|
124
|
-
const link = (event.target as HTMLElement)?.closest('a')
|
|
125
|
-
|
|
126
|
-
if (link && attrs.href) {
|
|
127
|
-
window.open(attrs.href, attrs.target)
|
|
128
|
-
|
|
129
|
-
return true
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return false
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
+
plugins.push(clickHandler({
|
|
138
|
+
type: this.type,
|
|
139
|
+
}))
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
if (this.options.linkOnPaste) {
|
|
140
|
-
plugins.push(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
handlePaste: (view, event, slice) => {
|
|
145
|
-
const { state } = view
|
|
146
|
-
const { selection } = state
|
|
147
|
-
const { empty } = selection
|
|
148
|
-
|
|
149
|
-
if (empty) {
|
|
150
|
-
return false
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
let textContent = ''
|
|
154
|
-
|
|
155
|
-
slice.content.forEach(node => {
|
|
156
|
-
textContent += node.textContent
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const link = find(textContent)
|
|
160
|
-
.find(item => item.isLink && item.value === textContent)
|
|
161
|
-
|
|
162
|
-
if (!textContent || !link) {
|
|
163
|
-
return false
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this.editor.commands.setMark(this.type, {
|
|
167
|
-
href: link.href,
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
return true
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
)
|
|
143
|
+
plugins.push(pasteHandler({
|
|
144
|
+
editor: this.editor,
|
|
145
|
+
type: this.type,
|
|
146
|
+
}))
|
|
175
147
|
}
|
|
176
148
|
|
|
177
149
|
return plugins
|