@atlaskit/editor-plugin-hyperlink 0.0.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.
- package/.eslintrc.js +6 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +13 -0
- package/README.md +7 -0
- package/dist/cjs/Toolbar.js +280 -0
- package/dist/cjs/commands.js +242 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/plugin.js +122 -0
- package/dist/cjs/pm-plugins/fake-curor-for-toolbar-plugin-key.js +9 -0
- package/dist/cjs/pm-plugins/fake-cursor-for-toolbar.js +68 -0
- package/dist/cjs/pm-plugins/input-rule.js +95 -0
- package/dist/cjs/pm-plugins/keymap.js +75 -0
- package/dist/cjs/pm-plugins/main.js +257 -0
- package/dist/cjs/pm-plugins/toolbar-buttons.js +43 -0
- package/dist/cjs/version.json +5 -0
- package/dist/es2019/Toolbar.js +260 -0
- package/dist/es2019/commands.js +225 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/plugin.js +106 -0
- package/dist/es2019/pm-plugins/fake-curor-for-toolbar-plugin-key.js +2 -0
- package/dist/es2019/pm-plugins/fake-cursor-for-toolbar.js +63 -0
- package/dist/es2019/pm-plugins/input-rule.js +80 -0
- package/dist/es2019/pm-plugins/keymap.js +65 -0
- package/dist/es2019/pm-plugins/main.js +261 -0
- package/dist/es2019/pm-plugins/toolbar-buttons.js +39 -0
- package/dist/es2019/version.json +5 -0
- package/dist/esm/Toolbar.js +271 -0
- package/dist/esm/commands.js +222 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/plugin.js +114 -0
- package/dist/esm/pm-plugins/fake-curor-for-toolbar-plugin-key.js +2 -0
- package/dist/esm/pm-plugins/fake-cursor-for-toolbar.js +61 -0
- package/dist/esm/pm-plugins/input-rule.js +85 -0
- package/dist/esm/pm-plugins/keymap.js +67 -0
- package/dist/esm/pm-plugins/main.js +248 -0
- package/dist/esm/pm-plugins/toolbar-buttons.js +34 -0
- package/dist/esm/version.json +5 -0
- package/dist/types/Toolbar.d.ts +8 -0
- package/dist/types/commands.d.ts +20 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/plugin.d.ts +38 -0
- package/dist/types/pm-plugins/fake-curor-for-toolbar-plugin-key.d.ts +2 -0
- package/dist/types/pm-plugins/fake-cursor-for-toolbar.d.ts +3 -0
- package/dist/types/pm-plugins/input-rule.d.ts +8 -0
- package/dist/types/pm-plugins/keymap.d.ts +4 -0
- package/dist/types/pm-plugins/main.d.ts +7 -0
- package/dist/types/pm-plugins/toolbar-buttons.d.ts +21 -0
- package/dist/types-ts4.5/Toolbar.d.ts +8 -0
- package/dist/types-ts4.5/commands.d.ts +20 -0
- package/dist/types-ts4.5/index.d.ts +3 -0
- package/dist/types-ts4.5/plugin.d.ts +38 -0
- package/dist/types-ts4.5/pm-plugins/fake-curor-for-toolbar-plugin-key.d.ts +2 -0
- package/dist/types-ts4.5/pm-plugins/fake-cursor-for-toolbar.d.ts +3 -0
- package/dist/types-ts4.5/pm-plugins/input-rule.d.ts +8 -0
- package/dist/types-ts4.5/pm-plugins/keymap.d.ts +4 -0
- package/dist/types-ts4.5/pm-plugins/main.d.ts +7 -0
- package/dist/types-ts4.5/pm-plugins/toolbar-buttons.d.ts +21 -0
- package/package.json +107 -0
- package/tmp/api-report-tmp.d.ts +68 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { isSafeUrl } from '@atlaskit/adf-schema';
|
|
3
|
+
import { ACTION, ACTION_SUBJECT_ID, buildOpenedSettingsPayload, buildVisitedLinkPayload, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { commandWithMetadata } from '@atlaskit/editor-common/card';
|
|
5
|
+
import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
|
|
6
|
+
import { HyperlinkAddToolbar } from '@atlaskit/editor-common/link';
|
|
7
|
+
import { linkMessages, linkToolbarMessages as linkToolbarCommonMessages } from '@atlaskit/editor-common/messages';
|
|
8
|
+
import { LINKPICKER_HEIGHT_IN_PX, RECENT_SEARCH_HEIGHT_IN_PX, RECENT_SEARCH_WIDTH_IN_PX } from '@atlaskit/editor-common/ui';
|
|
9
|
+
import { normalizeUrl } from '@atlaskit/editor-common/utils';
|
|
10
|
+
import CogIcon from '@atlaskit/icon/glyph/editor/settings';
|
|
11
|
+
import UnlinkIcon from '@atlaskit/icon/glyph/editor/unlink';
|
|
12
|
+
import OpenIcon from '@atlaskit/icon/glyph/shortcut';
|
|
13
|
+
import { editInsertedLink, insertLinkWithAnalytics, onClickAwayCallback, onEscapeCallback, removeLink, updateLink } from './commands';
|
|
14
|
+
import { stateKey } from './pm-plugins/main';
|
|
15
|
+
import { toolbarKey } from './pm-plugins/toolbar-buttons';
|
|
16
|
+
/* type guard for edit links */
|
|
17
|
+
function isEditLink(linkMark) {
|
|
18
|
+
return linkMark.pos !== undefined;
|
|
19
|
+
}
|
|
20
|
+
const dispatchAnalytics = (dispatch, state, analyticsBuilder, editorAnalyticsApi) => {
|
|
21
|
+
if (dispatch) {
|
|
22
|
+
const {
|
|
23
|
+
tr
|
|
24
|
+
} = state;
|
|
25
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent(analyticsBuilder(ACTION_SUBJECT_ID.HYPERLINK))(tr);
|
|
26
|
+
dispatch(tr);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const visitHyperlink = editorAnalyticsApi => (state, dispatch) => {
|
|
30
|
+
dispatchAnalytics(dispatch, state, buildVisitedLinkPayload, editorAnalyticsApi);
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
const openLinkSettings = editorAnalyticsApi => (state, dispatch) => {
|
|
34
|
+
dispatchAnalytics(dispatch, state, buildOpenedSettingsPayload, editorAnalyticsApi);
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
function getLinkText(activeLinkMark, state) {
|
|
38
|
+
if (!activeLinkMark.node) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const textToUrl = normalizeUrl(activeLinkMark.node.text);
|
|
42
|
+
const linkMark = activeLinkMark.node.marks.find(mark => mark.type === state.schema.marks.link);
|
|
43
|
+
const linkHref = linkMark && linkMark.attrs.href;
|
|
44
|
+
if (textToUrl === linkHref) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return activeLinkMark.node.text;
|
|
48
|
+
}
|
|
49
|
+
export function HyperlinkAddToolbarWithState({
|
|
50
|
+
linkPickerOptions = {},
|
|
51
|
+
onSubmit,
|
|
52
|
+
displayText,
|
|
53
|
+
displayUrl,
|
|
54
|
+
providerFactory,
|
|
55
|
+
view,
|
|
56
|
+
onCancel,
|
|
57
|
+
invokeMethod,
|
|
58
|
+
featureFlags,
|
|
59
|
+
onClose,
|
|
60
|
+
onEscapeCallback,
|
|
61
|
+
onClickAwayCallback,
|
|
62
|
+
pluginInjectionApi
|
|
63
|
+
}) {
|
|
64
|
+
const {
|
|
65
|
+
hyperlinkState
|
|
66
|
+
} = useSharedPluginState(pluginInjectionApi, ['hyperlink']);
|
|
67
|
+
return /*#__PURE__*/React.createElement(HyperlinkAddToolbar, {
|
|
68
|
+
linkPickerOptions: linkPickerOptions,
|
|
69
|
+
onSubmit: onSubmit,
|
|
70
|
+
displayText: displayText,
|
|
71
|
+
displayUrl: displayUrl,
|
|
72
|
+
providerFactory: providerFactory,
|
|
73
|
+
view: view,
|
|
74
|
+
onCancel: onCancel,
|
|
75
|
+
invokeMethod: invokeMethod,
|
|
76
|
+
featureFlags: featureFlags,
|
|
77
|
+
onClose: onClose,
|
|
78
|
+
onEscapeCallback: onEscapeCallback,
|
|
79
|
+
onClickAwayCallback: onClickAwayCallback,
|
|
80
|
+
hyperlinkPluginState: hyperlinkState
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const getSettingsButtonGroup = (state, intl, featureFlags, editorAnalyticsApi) => {
|
|
84
|
+
const {
|
|
85
|
+
floatingToolbarLinkSettingsButton
|
|
86
|
+
} = featureFlags;
|
|
87
|
+
return floatingToolbarLinkSettingsButton === 'true' ? [{
|
|
88
|
+
type: 'separator'
|
|
89
|
+
}, {
|
|
90
|
+
id: 'editor.link.settings',
|
|
91
|
+
type: 'button',
|
|
92
|
+
icon: CogIcon,
|
|
93
|
+
title: intl.formatMessage(linkToolbarCommonMessages.settingsLink),
|
|
94
|
+
onClick: openLinkSettings(editorAnalyticsApi),
|
|
95
|
+
href: 'https://id.atlassian.com/manage-profile/link-preferences',
|
|
96
|
+
target: '_blank'
|
|
97
|
+
}] : [];
|
|
98
|
+
};
|
|
99
|
+
export const getToolbarConfig = (options, featureFlags, pluginInjectionApi) => (state, intl, providerFactory) => {
|
|
100
|
+
var _pluginInjectionApi$d;
|
|
101
|
+
const {
|
|
102
|
+
formatMessage
|
|
103
|
+
} = intl;
|
|
104
|
+
const linkState = stateKey.getState(state);
|
|
105
|
+
const editorAnalyticsApi = pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$d = pluginInjectionApi.dependencies.analytics) === null || _pluginInjectionApi$d === void 0 ? void 0 : _pluginInjectionApi$d.actions;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Enable focus trap only if feature flag is enabled AND for the new version of the picker
|
|
109
|
+
*/
|
|
110
|
+
const {
|
|
111
|
+
lpLinkPicker,
|
|
112
|
+
lpLinkPickerFocusTrap,
|
|
113
|
+
preventPopupOverflow
|
|
114
|
+
} = featureFlags;
|
|
115
|
+
const shouldEnableFocusTrap = Boolean(lpLinkPicker && lpLinkPickerFocusTrap);
|
|
116
|
+
if (linkState && linkState.activeLinkMark) {
|
|
117
|
+
const {
|
|
118
|
+
activeLinkMark
|
|
119
|
+
} = linkState;
|
|
120
|
+
const hyperLinkToolbar = {
|
|
121
|
+
title: 'Hyperlink floating controls',
|
|
122
|
+
nodeType: [state.schema.nodes.text, state.schema.nodes.paragraph, state.schema.nodes.heading, state.schema.nodes.taskItem, state.schema.nodes.decisionItem, state.schema.nodes.caption].filter(nodeType => !!nodeType),
|
|
123
|
+
// Use only the node types existing in the schema ED-6745
|
|
124
|
+
align: 'left',
|
|
125
|
+
className: activeLinkMark.type.match('INSERT|EDIT_INSERTED') ? 'hyperlink-floating-toolbar' : ''
|
|
126
|
+
};
|
|
127
|
+
switch (activeLinkMark.type) {
|
|
128
|
+
case 'EDIT':
|
|
129
|
+
{
|
|
130
|
+
var _toolbarKey$getState$, _toolbarKey$getState;
|
|
131
|
+
const {
|
|
132
|
+
pos,
|
|
133
|
+
node
|
|
134
|
+
} = activeLinkMark;
|
|
135
|
+
const linkMark = node.marks.filter(mark => mark.type === state.schema.marks.link);
|
|
136
|
+
const link = linkMark[0] && linkMark[0].attrs.href;
|
|
137
|
+
const isValidUrl = isSafeUrl(link);
|
|
138
|
+
const labelOpenLink = formatMessage(isValidUrl ? linkMessages.openLink : linkToolbarCommonMessages.unableToOpenLink);
|
|
139
|
+
// TODO: ED-14403 investigate why these are not translating?
|
|
140
|
+
const labelUnlink = formatMessage(linkToolbarCommonMessages.unlink);
|
|
141
|
+
const editLink = formatMessage(linkToolbarCommonMessages.editLink);
|
|
142
|
+
let metadata = {
|
|
143
|
+
url: link,
|
|
144
|
+
title: ''
|
|
145
|
+
};
|
|
146
|
+
if (activeLinkMark.node.text) {
|
|
147
|
+
metadata.title = activeLinkMark.node.text;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
...hyperLinkToolbar,
|
|
151
|
+
height: 32,
|
|
152
|
+
width: 250,
|
|
153
|
+
items: [...((_toolbarKey$getState$ = (_toolbarKey$getState = toolbarKey.getState(state)) === null || _toolbarKey$getState === void 0 ? void 0 : _toolbarKey$getState.items(state, intl, providerFactory, link)) !== null && _toolbarKey$getState$ !== void 0 ? _toolbarKey$getState$ : []), {
|
|
154
|
+
id: 'editor.link.edit',
|
|
155
|
+
type: 'button',
|
|
156
|
+
onClick: editInsertedLink(editorAnalyticsApi),
|
|
157
|
+
selected: false,
|
|
158
|
+
title: editLink,
|
|
159
|
+
showTitle: true,
|
|
160
|
+
metadata: metadata
|
|
161
|
+
}, {
|
|
162
|
+
type: 'separator'
|
|
163
|
+
}, {
|
|
164
|
+
id: 'editor.link.openLink',
|
|
165
|
+
type: 'button',
|
|
166
|
+
disabled: !isValidUrl,
|
|
167
|
+
target: '_blank',
|
|
168
|
+
href: isValidUrl ? link : undefined,
|
|
169
|
+
onClick: visitHyperlink(editorAnalyticsApi),
|
|
170
|
+
selected: false,
|
|
171
|
+
title: labelOpenLink,
|
|
172
|
+
icon: OpenIcon,
|
|
173
|
+
className: 'hyperlink-open-link',
|
|
174
|
+
metadata: metadata,
|
|
175
|
+
tabIndex: null
|
|
176
|
+
}, {
|
|
177
|
+
type: 'separator'
|
|
178
|
+
}, {
|
|
179
|
+
id: 'editor.link.unlink',
|
|
180
|
+
type: 'button',
|
|
181
|
+
onClick: commandWithMetadata(removeLink(pos, editorAnalyticsApi), {
|
|
182
|
+
inputMethod: INPUT_METHOD.FLOATING_TB
|
|
183
|
+
}),
|
|
184
|
+
selected: false,
|
|
185
|
+
title: labelUnlink,
|
|
186
|
+
icon: UnlinkIcon,
|
|
187
|
+
tabIndex: null
|
|
188
|
+
}, {
|
|
189
|
+
type: 'copy-button',
|
|
190
|
+
items: [{
|
|
191
|
+
type: 'separator'
|
|
192
|
+
}, {
|
|
193
|
+
state,
|
|
194
|
+
formatMessage: formatMessage,
|
|
195
|
+
markType: state.schema.marks.link
|
|
196
|
+
}]
|
|
197
|
+
}, ...getSettingsButtonGroup(state, intl, featureFlags, editorAnalyticsApi)],
|
|
198
|
+
scrollable: true
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
case 'EDIT_INSERTED':
|
|
202
|
+
case 'INSERT':
|
|
203
|
+
{
|
|
204
|
+
let link;
|
|
205
|
+
if (isEditLink(activeLinkMark) && activeLinkMark.node) {
|
|
206
|
+
const linkMark = activeLinkMark.node.marks.filter(mark => mark.type === state.schema.marks.link);
|
|
207
|
+
link = linkMark[0] && linkMark[0].attrs.href;
|
|
208
|
+
}
|
|
209
|
+
const displayText = isEditLink(activeLinkMark) ? getLinkText(activeLinkMark, state) : linkState.activeText;
|
|
210
|
+
const popupHeight = lpLinkPicker ? LINKPICKER_HEIGHT_IN_PX : RECENT_SEARCH_HEIGHT_IN_PX;
|
|
211
|
+
return {
|
|
212
|
+
...hyperLinkToolbar,
|
|
213
|
+
preventPopupOverflow,
|
|
214
|
+
height: popupHeight,
|
|
215
|
+
width: RECENT_SEARCH_WIDTH_IN_PX,
|
|
216
|
+
focusTrap: shouldEnableFocusTrap,
|
|
217
|
+
items: [{
|
|
218
|
+
type: 'custom',
|
|
219
|
+
fallback: [],
|
|
220
|
+
disableArrowNavigation: true,
|
|
221
|
+
render: (view, idx) => {
|
|
222
|
+
if (!view) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return /*#__PURE__*/React.createElement(HyperlinkAddToolbarWithState, {
|
|
226
|
+
pluginInjectionApi: pluginInjectionApi,
|
|
227
|
+
view: view,
|
|
228
|
+
key: idx,
|
|
229
|
+
linkPickerOptions: options === null || options === void 0 ? void 0 : options.linkPicker,
|
|
230
|
+
featureFlags: featureFlags,
|
|
231
|
+
displayUrl: link,
|
|
232
|
+
displayText: displayText || '',
|
|
233
|
+
providerFactory: providerFactory,
|
|
234
|
+
onCancel: () => view.focus(),
|
|
235
|
+
onClose: lpLinkPickerFocusTrap ? () => view.focus() : undefined,
|
|
236
|
+
onEscapeCallback: onEscapeCallback,
|
|
237
|
+
onClickAwayCallback: onClickAwayCallback,
|
|
238
|
+
onSubmit: (href, title = '', displayText, inputMethod, analytic) => {
|
|
239
|
+
var _options$cardOptions;
|
|
240
|
+
const isEdit = isEditLink(activeLinkMark);
|
|
241
|
+
const action = isEdit ? ACTION.UPDATED : ACTION.INSERTED;
|
|
242
|
+
const command = isEdit ? commandWithMetadata(updateLink(href, displayText || title, activeLinkMark.pos), {
|
|
243
|
+
action,
|
|
244
|
+
inputMethod,
|
|
245
|
+
sourceEvent: analytic
|
|
246
|
+
}) : insertLinkWithAnalytics(inputMethod, activeLinkMark.from, activeLinkMark.to, href, editorAnalyticsApi, title, displayText, !!(options !== null && options !== void 0 && (_options$cardOptions = options.cardOptions) !== null && _options$cardOptions !== void 0 && _options$cardOptions.provider), analytic);
|
|
247
|
+
command(view.state, view.dispatch, view);
|
|
248
|
+
if (!lpLinkPickerFocusTrap) {
|
|
249
|
+
view.focus();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { Selection } from 'prosemirror-state';
|
|
2
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, buildEditLinkPayload, EVENT_TYPE, INPUT_METHOD, unlinkPayload } from '@atlaskit/editor-common/analytics';
|
|
3
|
+
import { addLinkMetadata, commandWithMetadata } from '@atlaskit/editor-common/card';
|
|
4
|
+
import { withAnalytics } from '@atlaskit/editor-common/editor-analytics';
|
|
5
|
+
import { isTextAtPos, LinkAction } from '@atlaskit/editor-common/link';
|
|
6
|
+
import { filterCommands as filter, getLinkCreationAnalyticsEvent, normalizeUrl } from '@atlaskit/editor-common/utils';
|
|
7
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
8
|
+
import { stateKey } from './pm-plugins/main';
|
|
9
|
+
import { toolbarKey } from './pm-plugins/toolbar-buttons';
|
|
10
|
+
export function setLinkHref(href, pos, editorAnalyticsApi, to, isTabPressed) {
|
|
11
|
+
return filter(isTextAtPos(pos), (state, dispatch) => {
|
|
12
|
+
const $pos = state.doc.resolve(pos);
|
|
13
|
+
const node = state.doc.nodeAt(pos);
|
|
14
|
+
const linkMark = state.schema.marks.link;
|
|
15
|
+
const mark = linkMark.isInSet(node.marks);
|
|
16
|
+
const url = normalizeUrl(href);
|
|
17
|
+
if (mark && mark.attrs.href === url) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const rightBound = to && pos !== to ? to : pos - $pos.textOffset + node.nodeSize;
|
|
21
|
+
const tr = state.tr.removeMark(pos, rightBound, linkMark);
|
|
22
|
+
if (href.trim()) {
|
|
23
|
+
tr.addMark(pos, rightBound, linkMark.create({
|
|
24
|
+
...(mark && mark.attrs || {}),
|
|
25
|
+
href: url
|
|
26
|
+
}));
|
|
27
|
+
} else {
|
|
28
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent(unlinkPayload(ACTION_SUBJECT_ID.HYPERLINK))(tr);
|
|
29
|
+
}
|
|
30
|
+
if (!isTabPressed) {
|
|
31
|
+
tr.setMeta(stateKey, {
|
|
32
|
+
type: LinkAction.HIDE_TOOLBAR
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (dispatch) {
|
|
36
|
+
dispatch(tr);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function updateLink(href, text, pos, to) {
|
|
42
|
+
return (state, dispatch) => {
|
|
43
|
+
const $pos = state.doc.resolve(pos);
|
|
44
|
+
const node = state.doc.nodeAt(pos);
|
|
45
|
+
if (!node) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const url = normalizeUrl(href);
|
|
49
|
+
const mark = state.schema.marks.link.isInSet(node.marks);
|
|
50
|
+
const linkMark = state.schema.marks.link;
|
|
51
|
+
const rightBound = to && pos !== to ? to : pos - $pos.textOffset + node.nodeSize;
|
|
52
|
+
const tr = state.tr;
|
|
53
|
+
if (!url && text) {
|
|
54
|
+
tr.removeMark(pos, rightBound, linkMark);
|
|
55
|
+
tr.insertText(text, pos, rightBound);
|
|
56
|
+
} else if (!url) {
|
|
57
|
+
return false;
|
|
58
|
+
} else {
|
|
59
|
+
tr.insertText(text, pos, rightBound);
|
|
60
|
+
// Casting to LinkAttributes to prevent wrong attributes been passed (Example ED-7951)
|
|
61
|
+
const linkAttrs = {
|
|
62
|
+
...(mark && mark.attrs || {}),
|
|
63
|
+
href: url
|
|
64
|
+
};
|
|
65
|
+
tr.addMark(pos, pos + text.length, linkMark.create(linkAttrs));
|
|
66
|
+
tr.setMeta(stateKey, {
|
|
67
|
+
type: LinkAction.HIDE_TOOLBAR
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (dispatch) {
|
|
71
|
+
dispatch(tr);
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function insertLink(from, to, incomingHref, incomingTitle, displayText, source, sourceEvent) {
|
|
77
|
+
return (state, dispatch) => {
|
|
78
|
+
const link = state.schema.marks.link;
|
|
79
|
+
const {
|
|
80
|
+
tr
|
|
81
|
+
} = state;
|
|
82
|
+
if (incomingHref.trim()) {
|
|
83
|
+
var _stateKey$getState;
|
|
84
|
+
const normalizedUrl = normalizeUrl(incomingHref);
|
|
85
|
+
// NB: in this context, `currentText` represents text which has been
|
|
86
|
+
// highlighted in the Editor, upon which a link is is being added.
|
|
87
|
+
const currentText = (_stateKey$getState = stateKey.getState(state)) === null || _stateKey$getState === void 0 ? void 0 : _stateKey$getState.activeText;
|
|
88
|
+
let markEnd = to;
|
|
89
|
+
const text = displayText || incomingTitle || incomingHref;
|
|
90
|
+
if (!displayText || displayText !== currentText) {
|
|
91
|
+
tr.insertText(text, from, to);
|
|
92
|
+
if (!isTextAtPos(from)(state)) {
|
|
93
|
+
markEnd = from + text.length + 1;
|
|
94
|
+
} else {
|
|
95
|
+
markEnd = from + text.length;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
tr.addMark(from, markEnd, link.create({
|
|
99
|
+
href: normalizedUrl
|
|
100
|
+
}));
|
|
101
|
+
tr.setSelection(Selection.near(tr.doc.resolve(markEnd)));
|
|
102
|
+
if (!displayText || displayText === incomingHref) {
|
|
103
|
+
var _toolbarKey$getState;
|
|
104
|
+
const queueCardsFromChangedTr = (_toolbarKey$getState = toolbarKey.getState(state)) === null || _toolbarKey$getState === void 0 ? void 0 : _toolbarKey$getState.onInsertLinkCallback;
|
|
105
|
+
if (queueCardsFromChangedTr) {
|
|
106
|
+
queueCardsFromChangedTr === null || queueCardsFromChangedTr === void 0 ? void 0 : queueCardsFromChangedTr(state, tr, source, ACTION.INSERTED, false, sourceEvent);
|
|
107
|
+
} else {
|
|
108
|
+
addLinkMetadata(state.selection, tr, {
|
|
109
|
+
action: ACTION.INSERTED,
|
|
110
|
+
inputMethod: source,
|
|
111
|
+
sourceEvent
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} else if (getBooleanFF('platform.linking-platform.editor.fix-link-insert-analytics')) {
|
|
115
|
+
/**
|
|
116
|
+
* Add link metadata because queue cards would have otherwise handled this for us
|
|
117
|
+
*/
|
|
118
|
+
addLinkMetadata(state.selection, tr, {
|
|
119
|
+
action: ACTION.INSERTED,
|
|
120
|
+
inputMethod: source,
|
|
121
|
+
sourceEvent
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
tr.setMeta(stateKey, {
|
|
125
|
+
type: LinkAction.HIDE_TOOLBAR
|
|
126
|
+
});
|
|
127
|
+
if (dispatch) {
|
|
128
|
+
dispatch(tr);
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
tr.setMeta(stateKey, {
|
|
133
|
+
type: LinkAction.HIDE_TOOLBAR
|
|
134
|
+
});
|
|
135
|
+
if (dispatch) {
|
|
136
|
+
dispatch(tr);
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export const insertLinkWithAnalytics = (inputMethod, from, to, href, editorAnalyticsApi, title, displayText, cardsAvailable = false, sourceEvent = undefined) => {
|
|
142
|
+
// If smart cards are available, we send analytics for hyperlinks when a smart link is rejected.
|
|
143
|
+
if (cardsAvailable && !title && !displayText) {
|
|
144
|
+
return insertLink(from, to, href, title, displayText, inputMethod, sourceEvent);
|
|
145
|
+
}
|
|
146
|
+
return withAnalytics(editorAnalyticsApi, getLinkCreationAnalyticsEvent(inputMethod, href))(insertLink(from, to, href, title, displayText, inputMethod, sourceEvent));
|
|
147
|
+
};
|
|
148
|
+
export const insertLinkWithAnalyticsMobileNative = (inputMethod, from, to, href, editorAnalyticsApi, title, displayText) => {
|
|
149
|
+
return withAnalytics(editorAnalyticsApi, getLinkCreationAnalyticsEvent(inputMethod, href))(insertLink(from, to, href, title, displayText, inputMethod));
|
|
150
|
+
};
|
|
151
|
+
export function removeLink(pos, editorAnalyticsApi) {
|
|
152
|
+
return commandWithMetadata(setLinkHref('', pos, editorAnalyticsApi), {
|
|
153
|
+
action: ACTION.UNLINK
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export function editInsertedLink(editorAnalyticsApi) {
|
|
157
|
+
return (state, dispatch) => {
|
|
158
|
+
if (dispatch) {
|
|
159
|
+
const {
|
|
160
|
+
tr
|
|
161
|
+
} = state;
|
|
162
|
+
tr.setMeta(stateKey, {
|
|
163
|
+
type: LinkAction.EDIT_INSERTED_TOOLBAR,
|
|
164
|
+
inputMethod: INPUT_METHOD.FLOATING_TB
|
|
165
|
+
});
|
|
166
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent(buildEditLinkPayload(ACTION_SUBJECT_ID.HYPERLINK))(tr);
|
|
167
|
+
dispatch(tr);
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function showLinkToolbar(inputMethod, editorAnalyticsApi) {
|
|
173
|
+
return function (state, dispatch) {
|
|
174
|
+
if (dispatch) {
|
|
175
|
+
const tr = state.tr.setMeta(stateKey, {
|
|
176
|
+
type: LinkAction.SHOW_INSERT_TOOLBAR,
|
|
177
|
+
inputMethod
|
|
178
|
+
});
|
|
179
|
+
editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent({
|
|
180
|
+
action: ACTION.INVOKED,
|
|
181
|
+
actionSubject: ACTION_SUBJECT.TYPEAHEAD,
|
|
182
|
+
actionSubjectId: ACTION_SUBJECT_ID.TYPEAHEAD_LINK,
|
|
183
|
+
attributes: {
|
|
184
|
+
inputMethod
|
|
185
|
+
},
|
|
186
|
+
eventType: EVENT_TYPE.UI
|
|
187
|
+
})(tr);
|
|
188
|
+
dispatch(tr);
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function hideLinkToolbar() {
|
|
194
|
+
return function (state, dispatch) {
|
|
195
|
+
if (dispatch) {
|
|
196
|
+
dispatch(hideLinkToolbarSetMeta(state.tr));
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export const hideLinkToolbarSetMeta = tr => {
|
|
202
|
+
return tr.setMeta(stateKey, {
|
|
203
|
+
type: LinkAction.HIDE_TOOLBAR
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
export const onEscapeCallback = (state, dispatch) => {
|
|
207
|
+
var _toolbarKey$getState2, _toolbarKey$getState3;
|
|
208
|
+
const {
|
|
209
|
+
tr
|
|
210
|
+
} = state;
|
|
211
|
+
hideLinkToolbarSetMeta(tr);
|
|
212
|
+
(_toolbarKey$getState2 = toolbarKey.getState(state)) === null || _toolbarKey$getState2 === void 0 ? void 0 : (_toolbarKey$getState3 = _toolbarKey$getState2.onEscapeCallback) === null || _toolbarKey$getState3 === void 0 ? void 0 : _toolbarKey$getState3.call(_toolbarKey$getState2, tr);
|
|
213
|
+
if (dispatch) {
|
|
214
|
+
dispatch(tr);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
};
|
|
219
|
+
export const onClickAwayCallback = (state, dispatch) => {
|
|
220
|
+
if (dispatch) {
|
|
221
|
+
hideLinkToolbar()(state, dispatch);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { hyperlinkPlugin } from './plugin';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { link } from '@atlaskit/adf-schema';
|
|
3
|
+
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics';
|
|
4
|
+
import { addLink, tooltip } from '@atlaskit/editor-common/keymaps';
|
|
5
|
+
import { LinkAction } from '@atlaskit/editor-common/link';
|
|
6
|
+
import { toolbarInsertBlockMessages as messages } from '@atlaskit/editor-common/messages';
|
|
7
|
+
import { IconLink } from '@atlaskit/editor-common/quick-insert';
|
|
8
|
+
import { hideLinkToolbarSetMeta, showLinkToolbar } from './commands';
|
|
9
|
+
import fakeCursorToolbarPlugin from './pm-plugins/fake-cursor-for-toolbar';
|
|
10
|
+
import { createInputRulePlugin } from './pm-plugins/input-rule';
|
|
11
|
+
import { createKeymapPlugin } from './pm-plugins/keymap';
|
|
12
|
+
import { plugin, stateKey } from './pm-plugins/main';
|
|
13
|
+
import { prependToolbarButtons, toolbarButtonsPlugin } from './pm-plugins/toolbar-buttons';
|
|
14
|
+
import { getToolbarConfig } from './Toolbar';
|
|
15
|
+
export const hyperlinkPlugin = (options = {}, api) => {
|
|
16
|
+
var _api$dependencies, _api$dependencies$fea;
|
|
17
|
+
const featureFlags = (api === null || api === void 0 ? void 0 : (_api$dependencies = api.dependencies) === null || _api$dependencies === void 0 ? void 0 : (_api$dependencies$fea = _api$dependencies.featureFlags) === null || _api$dependencies$fea === void 0 ? void 0 : _api$dependencies$fea.sharedState.currentState()) || {};
|
|
18
|
+
return {
|
|
19
|
+
name: 'hyperlink',
|
|
20
|
+
marks() {
|
|
21
|
+
return [{
|
|
22
|
+
name: 'link',
|
|
23
|
+
mark: link
|
|
24
|
+
}];
|
|
25
|
+
},
|
|
26
|
+
actions: {
|
|
27
|
+
prependToolbarButtons,
|
|
28
|
+
showLinkToolbar: (inputMethod = INPUT_METHOD.TOOLBAR) => {
|
|
29
|
+
var _api$dependencies$ana;
|
|
30
|
+
return showLinkToolbar(inputMethod, api === null || api === void 0 ? void 0 : (_api$dependencies$ana = api.dependencies.analytics) === null || _api$dependencies$ana === void 0 ? void 0 : _api$dependencies$ana.actions);
|
|
31
|
+
},
|
|
32
|
+
hideLinkToolbar: hideLinkToolbarSetMeta
|
|
33
|
+
},
|
|
34
|
+
getSharedState(editorState) {
|
|
35
|
+
if (!editorState) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return stateKey.getState(editorState);
|
|
39
|
+
},
|
|
40
|
+
pmPlugins() {
|
|
41
|
+
var _options$cardOptions;
|
|
42
|
+
// Skip analytics if card provider is available, as they will be
|
|
43
|
+
// sent on handleRejected upon attempting to resolve smart link.
|
|
44
|
+
const skipAnalytics = !!(options !== null && options !== void 0 && (_options$cardOptions = options.cardOptions) !== null && _options$cardOptions !== void 0 && _options$cardOptions.provider);
|
|
45
|
+
return [{
|
|
46
|
+
name: 'hyperlink',
|
|
47
|
+
plugin: ({
|
|
48
|
+
dispatch
|
|
49
|
+
}) => plugin(dispatch, options === null || options === void 0 ? void 0 : options.editorAppearance)
|
|
50
|
+
}, {
|
|
51
|
+
name: 'fakeCursorToolbarPlugin',
|
|
52
|
+
plugin: () => fakeCursorToolbarPlugin
|
|
53
|
+
}, {
|
|
54
|
+
name: 'hyperlinkInputRule',
|
|
55
|
+
plugin: ({
|
|
56
|
+
schema,
|
|
57
|
+
featureFlags
|
|
58
|
+
}) => {
|
|
59
|
+
var _api$dependencies$ana2;
|
|
60
|
+
return createInputRulePlugin(schema, skipAnalytics, featureFlags, api === null || api === void 0 ? void 0 : (_api$dependencies$ana2 = api.dependencies.analytics) === null || _api$dependencies$ana2 === void 0 ? void 0 : _api$dependencies$ana2.actions);
|
|
61
|
+
}
|
|
62
|
+
}, {
|
|
63
|
+
name: 'hyperlinkKeymap',
|
|
64
|
+
plugin: () => {
|
|
65
|
+
var _api$dependencies$ana3;
|
|
66
|
+
return createKeymapPlugin(skipAnalytics, api === null || api === void 0 ? void 0 : (_api$dependencies$ana3 = api.dependencies.analytics) === null || _api$dependencies$ana3 === void 0 ? void 0 : _api$dependencies$ana3.actions);
|
|
67
|
+
}
|
|
68
|
+
}, {
|
|
69
|
+
name: 'hyperlinkToolbarButtons',
|
|
70
|
+
plugin: toolbarButtonsPlugin
|
|
71
|
+
}];
|
|
72
|
+
},
|
|
73
|
+
pluginsOptions: {
|
|
74
|
+
quickInsert: ({
|
|
75
|
+
formatMessage
|
|
76
|
+
}) => [{
|
|
77
|
+
id: 'hyperlink',
|
|
78
|
+
title: formatMessage(messages.link),
|
|
79
|
+
description: formatMessage(messages.linkDescription),
|
|
80
|
+
keywords: ['hyperlink', 'url'],
|
|
81
|
+
priority: 1200,
|
|
82
|
+
keyshortcut: tooltip(addLink),
|
|
83
|
+
icon: () => /*#__PURE__*/React.createElement(IconLink, null),
|
|
84
|
+
action(insert, state) {
|
|
85
|
+
var _api$dependencies2, _api$dependencies2$an, _api$dependencies2$an2, _api$dependencies2$an3;
|
|
86
|
+
const tr = insert(undefined);
|
|
87
|
+
tr.setMeta(stateKey, {
|
|
88
|
+
type: LinkAction.SHOW_INSERT_TOOLBAR,
|
|
89
|
+
inputMethod: INPUT_METHOD.QUICK_INSERT
|
|
90
|
+
});
|
|
91
|
+
const analyticsAttached = api === null || api === void 0 ? void 0 : (_api$dependencies2 = api.dependencies) === null || _api$dependencies2 === void 0 ? void 0 : (_api$dependencies2$an = _api$dependencies2.analytics) === null || _api$dependencies2$an === void 0 ? void 0 : (_api$dependencies2$an2 = _api$dependencies2$an.actions) === null || _api$dependencies2$an2 === void 0 ? void 0 : (_api$dependencies2$an3 = _api$dependencies2$an2.attachAnalyticsEvent) === null || _api$dependencies2$an3 === void 0 ? void 0 : _api$dependencies2$an3.call(_api$dependencies2$an2, {
|
|
92
|
+
action: ACTION.INVOKED,
|
|
93
|
+
actionSubject: ACTION_SUBJECT.TYPEAHEAD,
|
|
94
|
+
actionSubjectId: ACTION_SUBJECT_ID.TYPEAHEAD_LINK,
|
|
95
|
+
attributes: {
|
|
96
|
+
inputMethod: INPUT_METHOD.QUICK_INSERT
|
|
97
|
+
},
|
|
98
|
+
eventType: EVENT_TYPE.UI
|
|
99
|
+
})(tr);
|
|
100
|
+
return analyticsAttached !== false ? tr : false;
|
|
101
|
+
}
|
|
102
|
+
}],
|
|
103
|
+
floatingToolbar: getToolbarConfig(options, featureFlags, api)
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
2
|
+
import { InsertStatus } from '@atlaskit/editor-common/link';
|
|
3
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
4
|
+
import { fakeCursorForToolbarPluginKey } from './fake-curor-for-toolbar-plugin-key';
|
|
5
|
+
import { stateKey as hyperlinkStateKey } from './main';
|
|
6
|
+
const createTextCursor = pos => {
|
|
7
|
+
const node = document.createElement('div');
|
|
8
|
+
node.className = 'ProseMirror-fake-text-cursor';
|
|
9
|
+
return Decoration.widget(pos, node, {
|
|
10
|
+
key: 'hyperlink-text-cursor'
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const createTextSelection = (from, to) => Decoration.inline(from, to, {
|
|
14
|
+
class: 'ProseMirror-fake-text-selection'
|
|
15
|
+
});
|
|
16
|
+
const getInsertLinkToolbarState = editorState => {
|
|
17
|
+
const state = hyperlinkStateKey.getState(editorState);
|
|
18
|
+
if (state && state.activeLinkMark) {
|
|
19
|
+
if (state.activeLinkMark.type === InsertStatus.INSERT_LINK_TOOLBAR) {
|
|
20
|
+
return state.activeLinkMark;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
const fakeCursorToolbarPlugin = new SafePlugin({
|
|
26
|
+
key: fakeCursorForToolbarPluginKey,
|
|
27
|
+
state: {
|
|
28
|
+
init() {
|
|
29
|
+
return DecorationSet.empty;
|
|
30
|
+
},
|
|
31
|
+
apply(tr, pluginState, oldState, newState) {
|
|
32
|
+
const oldInsertToolbarState = getInsertLinkToolbarState(oldState);
|
|
33
|
+
const insertToolbarState = getInsertLinkToolbarState(newState);
|
|
34
|
+
// Map DecorationSet if it still refers to the same position in the document
|
|
35
|
+
if (oldInsertToolbarState && insertToolbarState) {
|
|
36
|
+
const {
|
|
37
|
+
from,
|
|
38
|
+
to
|
|
39
|
+
} = insertToolbarState;
|
|
40
|
+
const oldFrom = tr.mapping.map(oldInsertToolbarState.from);
|
|
41
|
+
const oldTo = tr.mapping.map(oldInsertToolbarState.to);
|
|
42
|
+
if (oldFrom === from && oldTo === to) {
|
|
43
|
+
return pluginState.map(tr.mapping, tr.doc);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Update DecorationSet if new insert toolbar, or if we have moved to a different position in the doc
|
|
47
|
+
if (insertToolbarState) {
|
|
48
|
+
const {
|
|
49
|
+
from,
|
|
50
|
+
to
|
|
51
|
+
} = insertToolbarState;
|
|
52
|
+
return DecorationSet.create(tr.doc, [from === to ? createTextCursor(from) : createTextSelection(from, to)]);
|
|
53
|
+
}
|
|
54
|
+
return DecorationSet.empty;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
props: {
|
|
58
|
+
decorations(state) {
|
|
59
|
+
return fakeCursorForToolbarPluginKey.getState(state);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
export default fakeCursorToolbarPlugin;
|