@atlaskit/editor-plugin-card 16.10.2 → 16.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/cardPlugin.js +50 -33
  3. package/dist/cjs/pm-plugins/doc.js +2 -2
  4. package/dist/cjs/ui/HyperlinkToolbarAppearance.js +37 -37
  5. package/dist/cjs/ui/HyperlinkToolbarAppearanceDropdown.js +37 -37
  6. package/dist/cjs/ui/PasteDisplayAsMenu.compiled.css +31 -0
  7. package/dist/cjs/ui/PasteDisplayAsMenu.js +368 -0
  8. package/dist/cjs/ui/currentPastedSmartLink.js +52 -0
  9. package/dist/cjs/ui/pasteDisplayAsUtils.js +30 -0
  10. package/dist/cjs/ui/useFetchDatasourceDataInfo.js +13 -13
  11. package/dist/cjs/ui/useFetchDatasourceInfo.js +12 -12
  12. package/dist/es2019/cardPlugin.js +17 -2
  13. package/dist/es2019/pm-plugins/doc.js +2 -2
  14. package/dist/es2019/ui/PasteDisplayAsMenu.compiled.css +31 -0
  15. package/dist/es2019/ui/PasteDisplayAsMenu.js +366 -0
  16. package/dist/es2019/ui/currentPastedSmartLink.js +46 -0
  17. package/dist/es2019/ui/pasteDisplayAsUtils.js +24 -0
  18. package/dist/esm/cardPlugin.js +50 -33
  19. package/dist/esm/pm-plugins/doc.js +2 -2
  20. package/dist/esm/ui/HyperlinkToolbarAppearance.js +36 -36
  21. package/dist/esm/ui/HyperlinkToolbarAppearanceDropdown.js +36 -36
  22. package/dist/esm/ui/PasteDisplayAsMenu.compiled.css +31 -0
  23. package/dist/esm/ui/PasteDisplayAsMenu.js +359 -0
  24. package/dist/esm/ui/currentPastedSmartLink.js +46 -0
  25. package/dist/esm/ui/pasteDisplayAsUtils.js +24 -0
  26. package/dist/esm/ui/useFetchDatasourceDataInfo.js +13 -13
  27. package/dist/esm/ui/useFetchDatasourceInfo.js +12 -12
  28. package/dist/types/cardPluginType.d.ts +3 -1
  29. package/dist/types/types/index.d.ts +1 -0
  30. package/dist/types/ui/PasteDisplayAsMenu.d.ts +25 -0
  31. package/dist/types/ui/currentPastedSmartLink.d.ts +2 -0
  32. package/dist/types/ui/pasteDisplayAsUtils.d.ts +7 -0
  33. package/dist/types-ts4.5/cardPluginType.d.ts +3 -1
  34. package/dist/types-ts4.5/types/index.d.ts +1 -0
  35. package/dist/types-ts4.5/ui/PasteDisplayAsMenu.d.ts +25 -0
  36. package/dist/types-ts4.5/ui/currentPastedSmartLink.d.ts +2 -0
  37. package/dist/types-ts4.5/ui/pasteDisplayAsUtils.d.ts +12 -0
  38. package/package.json +11 -7
@@ -0,0 +1,31 @@
1
+
2
+ ._2rko1qi0{border-radius:var(--ds-radius-medium,6px)}._189ee4h9{border-width:var(--ds-border-width,1px)}
3
+ ._1dqonqa1{border-style:solid}
4
+ ._1h6d1l7x{border-color:var(--ds-border,#0b120e24)}
5
+ ._1h6dbk0g{border-color:var(--ds-border-disabled,#0515240f)}
6
+ ._1h6dq98m{border-color:var(--ds-border-selected,#1868db)}
7
+ ._18u0u2gc{margin-left:var(--ds-space-100,8px)}
8
+ ._19bv1b66{padding-left:var(--ds-space-050,4px)}
9
+ ._19pku2gc{margin-top:var(--ds-space-100,8px)}
10
+ ._1bah1b1v{justify-content:space-around}
11
+ ._1bah1h6o{justify-content:center}
12
+ ._1bsb1osq{width:100%}
13
+ ._1e0c1txw{display:flex}
14
+ ._2hwxu2gc{margin-right:var(--ds-space-100,8px)}
15
+ ._4cvr1h6o{align-items:center}
16
+ ._4t3izwfg{height:2pc}
17
+ ._bfhk15s3{background-color:var(--ds-background-selected,#e9f2fe)}
18
+ ._bfhk187o{background-color:var(--ds-background-disabled,#0515240f)}
19
+ ._bfhkhfxm{background-color:var(--ds-surface-sunken,#f8f8f8)}
20
+ ._bfhksm61{background-color:var(--ds-background-neutral-subtle,#00000000)}
21
+ ._bfhkvuon{background-color:var(--ds-surface,#fff)}
22
+ ._ca0q1b66{padding-top:var(--ds-space-050,4px)}
23
+ ._n3td1b66{padding-bottom:var(--ds-space-050,4px)}
24
+ ._otyru2gc{margin-bottom:var(--ds-space-100,8px)}
25
+ ._syaz1gmx{color:var(--ds-text-disabled,#080f214a)}
26
+ ._syazi7uo{color:var(--ds-text,#292a2e)}
27
+ ._u5f31b66{padding-right:var(--ds-space-050,4px)}
28
+ ._irr31dpa:hover{background-color:var(--ds-background-neutral-subtle-hovered,#0515240f)}
29
+ ._irr3ufnl:hover{background-color:var(--ds-background-selected-hovered,#cfe1fd)}
30
+ ._1di6fcek:active{background-color:var(--ds-background-neutral-subtle-pressed,#0b120e24)}
31
+ ._1di6nozp:active{background-color:var(--ds-background-selected-pressed,#8fb8f6)}
@@ -0,0 +1,366 @@
1
+ /* PasteDisplayAsMenu.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import "./PasteDisplayAsMenu.compiled.css";
3
+ import { ax, ix } from "@compiled/react/runtime";
4
+ import React, { useCallback, useEffect, useRef } from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import { cx } from '@atlaskit/css';
7
+ import { appearancePropsMap } from '@atlaskit/editor-common/card';
8
+ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
9
+ import { getActiveLinkMark } from '@atlaskit/editor-common/link';
10
+ import { PASTE_MENU, useEditorToolbar } from '@atlaskit/editor-common/toolbar';
11
+ import { isSupportedInParent } from '@atlaskit/editor-common/utils';
12
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
13
+ import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
14
+ import { ToolbarDropdownItemSection, useToolbarDropdownMenu } from '@atlaskit/editor-toolbar';
15
+ import MinusIcon from '@atlaskit/icon/core/minus';
16
+ import SmartLinkCardIcon from '@atlaskit/icon/core/smart-link-card';
17
+ import SmartLinkEmbedIcon from '@atlaskit/icon/core/smart-link-embed';
18
+ import SmartLinkInlineIcon from '@atlaskit/icon/core/smart-link-inline';
19
+ import { useSmartCardContext } from '@atlaskit/link-provider';
20
+ import { Box, Flex, Pressable } from '@atlaskit/primitives/compiled';
21
+ import { changeSelectedCardToLink, setSelectedCardAppearance } from '../pm-plugins/doc';
22
+ import { getSingleSmartLinkUrlFromSlice } from './currentPastedSmartLink';
23
+ import { getCardAtPasteRange } from './pasteDisplayAsUtils';
24
+ export const SMART_LINK_DISPLAY_AS_PASTE_MENU_SECTION_KEY = 'smart-link-display-as-paste-menu-section';
25
+
26
+ // `pasteOptionsToolbarPlugin` is intentionally NOT declared in `CardPluginDependencies`
27
+ // because doing so introduces a runtime package cycle
28
+ // (editor-plugin-card -> editor-plugin-paste-options-toolbar -> editor-plugin-paste
29
+ // -> editor-plugin-card) AND a TS2719 "two different types with this name exist" error
30
+ // at downstream consumers (e.g. Jira's `EditorAfterBanner.tsx`). We instead augment the
31
+ // `api` shape locally at the call sites that need to read the plugin's shared state.
32
+
33
+ const styles = {
34
+ appearanceBox: "_2rko1qi0 _bfhkhfxm _19pku2gc _2hwxu2gc _otyru2gc _18u0u2gc _ca0q1b66 _u5f31b66 _n3td1b66 _19bv1b66",
35
+ iconWrapper: "_bfhkvuon _1e0c1txw _1bsb1osq _1bah1b1v",
36
+ iconButton: "_2rko1qi0 _1h6d1l7x _1dqonqa1 _189ee4h9 _4cvr1h6o _syazi7uo _1e0c1txw _4t3izwfg _1bah1h6o _1bsb1osq _bfhksm61 _irr31dpa _1di6fcek",
37
+ iconButtonSelected: "_2rko1qi0 _1h6dq98m _1dqonqa1 _189ee4h9 _4cvr1h6o _bfhk15s3 _syazi7uo _1e0c1txw _4t3izwfg _1bah1h6o _1bsb1osq _irr3ufnl _1di6nozp",
38
+ iconButtonDisabled: "_2rko1qi0 _1h6dbk0g _1dqonqa1 _189ee4h9 _4cvr1h6o _bfhk187o _syaz1gmx _1e0c1txw _4t3izwfg _1bah1h6o _1bsb1osq"
39
+ };
40
+ const AppearanceOptionIconButton = ({
41
+ appearance,
42
+ currentAppearance,
43
+ isDisabled,
44
+ label,
45
+ Icon,
46
+ onClick
47
+ }) => {
48
+ return /*#__PURE__*/React.createElement(Box, {
49
+ xcss: styles.iconWrapper
50
+ }, /*#__PURE__*/React.createElement(Pressable, {
51
+ xcss: cx(styles.iconButton, isDisabled && styles.iconButtonDisabled, !isDisabled && currentAppearance === appearance && styles.iconButtonSelected),
52
+ "aria-label": label,
53
+ "aria-pressed": currentAppearance === appearance,
54
+ isDisabled: isDisabled,
55
+ onClick: onClick
56
+ }, /*#__PURE__*/React.createElement(Icon, {
57
+ label: label
58
+ })));
59
+ };
60
+ const InlineAppearanceIconButton = ({
61
+ currentAppearance,
62
+ isDisabled,
63
+ label,
64
+ onClick
65
+ }) => /*#__PURE__*/React.createElement(AppearanceOptionIconButton, {
66
+ appearance: "inline",
67
+ currentAppearance: currentAppearance,
68
+ isDisabled: isDisabled,
69
+ label: label,
70
+ Icon: SmartLinkInlineIcon,
71
+ onClick: onClick
72
+ });
73
+ const BlockAppearanceIconButton = ({
74
+ currentAppearance,
75
+ isDisabled,
76
+ label,
77
+ onClick
78
+ }) => /*#__PURE__*/React.createElement(AppearanceOptionIconButton, {
79
+ appearance: "block",
80
+ currentAppearance: currentAppearance,
81
+ isDisabled: isDisabled,
82
+ label: label,
83
+ Icon: SmartLinkCardIcon,
84
+ onClick: onClick
85
+ });
86
+ const EmbedAppearanceIconButton = ({
87
+ currentAppearance,
88
+ isDisabled,
89
+ label,
90
+ onClick
91
+ }) => /*#__PURE__*/React.createElement(AppearanceOptionIconButton, {
92
+ appearance: "embed",
93
+ currentAppearance: currentAppearance,
94
+ isDisabled: isDisabled,
95
+ label: label,
96
+ Icon: SmartLinkEmbedIcon,
97
+ onClick: onClick
98
+ });
99
+ const getCurrentPastedSlice = api => {
100
+ var _apiWithPaste$paste, _pasteState$lastConte;
101
+ const apiWithPaste = api;
102
+ const pasteState = apiWithPaste === null || apiWithPaste === void 0 ? void 0 : (_apiWithPaste$paste = apiWithPaste.paste) === null || _apiWithPaste$paste === void 0 ? void 0 : _apiWithPaste$paste.sharedState.currentState();
103
+ const slice = pasteState === null || pasteState === void 0 ? void 0 : (_pasteState$lastConte = pasteState.lastContentPasted) === null || _pasteState$lastConte === void 0 ? void 0 : _pasteState$lastConte.pastedSlice;
104
+ return slice;
105
+ };
106
+ const getCardUrlAtPasteRange = ({
107
+ editorView,
108
+ pasteStartPos,
109
+ pasteEndPos
110
+ }) => {
111
+ var _editorView$state$doc, _maybeAttrs$url, _maybeAttrs$data;
112
+ const cardAtPasteRange = getCardAtPasteRange(editorView.state, pasteStartPos, pasteEndPos);
113
+ const maybeAttrs = cardAtPasteRange ? (_editorView$state$doc = editorView.state.doc.nodeAt(cardAtPasteRange.pos)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.attrs : undefined;
114
+ const maybeUrl = (_maybeAttrs$url = maybeAttrs === null || maybeAttrs === void 0 ? void 0 : maybeAttrs.url) !== null && _maybeAttrs$url !== void 0 ? _maybeAttrs$url : maybeAttrs === null || maybeAttrs === void 0 ? void 0 : (_maybeAttrs$data = maybeAttrs.data) === null || _maybeAttrs$data === void 0 ? void 0 : _maybeAttrs$data.url;
115
+ return typeof maybeUrl === 'string' ? maybeUrl : undefined;
116
+ };
117
+ export const setAppearanceSelection = ({
118
+ editorView,
119
+ pasteStartPos,
120
+ pasteEndPos,
121
+ targetPos
122
+ }) => {
123
+ const {
124
+ state,
125
+ dispatch
126
+ } = editorView;
127
+ const maxPos = state.doc.content.size;
128
+ const clampedStart = Math.max(0, Math.min(pasteStartPos, maxPos));
129
+ const clampedEnd = Math.max(0, Math.min(pasteEndPos, maxPos));
130
+ const from = Math.min(clampedStart, clampedEnd);
131
+ const to = Math.max(clampedStart, clampedEnd);
132
+ const isTargetPosInBounds = targetPos !== undefined && targetPos >= 0 && targetPos <= state.doc.content.size;
133
+ try {
134
+ const nextSelection = isTargetPosInBounds && targetPos !== undefined ? NodeSelection.create(state.doc, targetPos) : TextSelection.create(state.doc, from, to);
135
+ const tr = state.tr.setSelection(nextSelection);
136
+ dispatch(tr);
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ };
142
+ export const getFirstLinkRangeInSelection = editorView => {
143
+ const {
144
+ state
145
+ } = editorView;
146
+ const linkMarkType = state.schema.marks.link;
147
+ if (!linkMarkType) {
148
+ return;
149
+ }
150
+ let firstLinkRange;
151
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
152
+ if (firstLinkRange || !node.isText) {
153
+ return;
154
+ }
155
+ if (linkMarkType.isInSet(node.marks)) {
156
+ firstLinkRange = {
157
+ from: pos,
158
+ to: pos + node.nodeSize
159
+ };
160
+ }
161
+ });
162
+ return firstLinkRange;
163
+ };
164
+ export const normalizeSelectionToLinkRangeForUrlAppearance = ({
165
+ editorView,
166
+ targetPos
167
+ }) => {
168
+ if (targetPos !== undefined || getActiveLinkMark(editorView.state)) {
169
+ return;
170
+ }
171
+ const firstLinkRange = getFirstLinkRangeInSelection(editorView);
172
+ if (!firstLinkRange) {
173
+ return;
174
+ }
175
+ const linkRangeSelectionTr = editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, firstLinkRange.from, firstLinkRange.to));
176
+ editorView.dispatch(linkRangeSelectionTr);
177
+ };
178
+ const PasteDisplayAsMenuHorizontalView = ({
179
+ api,
180
+ allowBlockCards,
181
+ allowEmbeds
182
+ }) => {
183
+ var _smartCardContext$val, _smartCardContext$val2, _smartCardContext$val3, _getCardAtPasteRange$, _getCardAtPasteRange, _smartCardContext$val4;
184
+ const intl = useIntl();
185
+ const {
186
+ editorView
187
+ } = useEditorToolbar();
188
+ const toolbarDropdownMenu = useToolbarDropdownMenu();
189
+ const smartCardContext = useSmartCardContext();
190
+ const frameRef = useRef(null);
191
+ const isApplyingRef = useRef(false);
192
+ const apiWithPasteOptionsToolbar = api;
193
+ const pasteRange = useSharedPluginStateWithSelector(apiWithPasteOptionsToolbar, ['pasteOptionsToolbarPlugin'], states => {
194
+ const pluginState = states.pasteOptionsToolbarPluginState;
195
+ return pluginState ? {
196
+ pasteEndPos: pluginState.pasteEndPos,
197
+ pasteStartPos: pluginState.pasteStartPos
198
+ } : undefined;
199
+ });
200
+ // Subscribe to card state so the menu re-renders when resolved card metadata updates.
201
+ useSharedPluginStateWithSelector(api, ['card'], states => {
202
+ return states.cardState;
203
+ });
204
+ const pastedLinkUrlFromSlice = getSingleSmartLinkUrlFromSlice(getCurrentPastedSlice(api));
205
+ const pastedLinkUrlFromCard = editorView && pasteRange ? getCardUrlAtPasteRange({
206
+ editorView,
207
+ pasteStartPos: pasteRange.pasteStartPos,
208
+ pasteEndPos: pasteRange.pasteEndPos
209
+ }) : undefined;
210
+ const pastedLinkUrl = pastedLinkUrlFromSlice !== null && pastedLinkUrlFromSlice !== void 0 ? pastedLinkUrlFromSlice : pastedLinkUrlFromCard;
211
+ const pastedLinkUrlState = pastedLinkUrl ? (_smartCardContext$val = smartCardContext.value) === null || _smartCardContext$val === void 0 ? void 0 : (_smartCardContext$val2 = _smartCardContext$val.store) === null || _smartCardContext$val2 === void 0 ? void 0 : (_smartCardContext$val3 = _smartCardContext$val2.getState()) === null || _smartCardContext$val3 === void 0 ? void 0 : _smartCardContext$val3[pastedLinkUrl] : undefined;
212
+ const hasResolvedSmartLinkData = Boolean(pastedLinkUrlState === null || pastedLinkUrlState === void 0 ? void 0 : pastedLinkUrlState.details);
213
+ const currentAppearance = editorView && pasteRange ? (_getCardAtPasteRange$ = (_getCardAtPasteRange = getCardAtPasteRange(editorView.state, pasteRange.pasteStartPos, pasteRange.pasteEndPos)) === null || _getCardAtPasteRange === void 0 ? void 0 : _getCardAtPasteRange.appearance) !== null && _getCardAtPasteRange$ !== void 0 ? _getCardAtPasteRange$ : 'url' : undefined;
214
+ const handleClick = useCallback(appearance => () => {
215
+ if (!editorView || !pasteRange || !pastedLinkUrl || !currentAppearance || isApplyingRef.current) {
216
+ return;
217
+ }
218
+ isApplyingRef.current = true;
219
+ const {
220
+ state,
221
+ dispatch
222
+ } = editorView;
223
+ const {
224
+ pasteStartPos,
225
+ pasteEndPos
226
+ } = pasteRange;
227
+ const cardAtPasteRange = getCardAtPasteRange(state, pasteStartPos, pasteEndPos);
228
+ if (appearance === 'url') {
229
+ if (cardAtPasteRange) {
230
+ var _state$doc$nodeAt, _api$analytics;
231
+ changeSelectedCardToLink(pastedLinkUrl, pastedLinkUrl, true, (_state$doc$nodeAt = state.doc.nodeAt(cardAtPasteRange.pos)) !== null && _state$doc$nodeAt !== void 0 ? _state$doc$nodeAt : undefined, cardAtPasteRange.pos, api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)(state, dispatch, editorView);
232
+ }
233
+ toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
234
+ isApplyingRef.current = false;
235
+ return;
236
+ }
237
+ const targetPos = cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.pos;
238
+ if (targetPos !== undefined && targetPos < pasteStartPos) {
239
+ isApplyingRef.current = false;
240
+ return;
241
+ }
242
+ const didApplySelection = setAppearanceSelection({
243
+ editorView,
244
+ pasteStartPos,
245
+ pasteEndPos,
246
+ targetPos
247
+ });
248
+ if (!didApplySelection) {
249
+ toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
250
+ isApplyingRef.current = false;
251
+ return;
252
+ }
253
+ normalizeSelectionToLinkRangeForUrlAppearance({
254
+ editorView,
255
+ targetPos
256
+ });
257
+ frameRef.current = requestAnimationFrame(() => {
258
+ var _api$analytics2;
259
+ frameRef.current = null;
260
+ setSelectedCardAppearance(appearance, api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions)(editorView.state, editorView.dispatch, editorView);
261
+ toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
262
+ isApplyingRef.current = false;
263
+ });
264
+ }, [api, currentAppearance, editorView, pasteRange, pastedLinkUrl, toolbarDropdownMenu]);
265
+ useEffect(() => {
266
+ return () => {
267
+ if (frameRef.current !== null) {
268
+ cancelAnimationFrame(frameRef.current);
269
+ }
270
+ isApplyingRef.current = false;
271
+ };
272
+ }, []);
273
+ if (!editorView || !pasteRange || !pastedLinkUrl || !currentAppearance) {
274
+ return null;
275
+ }
276
+ const preview = allowEmbeds && pastedLinkUrl && ((_smartCardContext$val4 = smartCardContext.value) === null || _smartCardContext$val4 === void 0 ? void 0 : _smartCardContext$val4.extractors.getPreview(pastedLinkUrl, 'web'));
277
+ const blockCardNodeType = editorView.state.schema.nodes.blockCard;
278
+ const embedCardNodeType = editorView.state.schema.nodes.embedCard;
279
+ const isBlockSupportedFromAppearanceContext = allowBlockCards && blockCardNodeType && isSupportedInParent(editorView.state, Fragment.from(blockCardNodeType.createChecked({})), currentAppearance === 'url' || currentAppearance === 'inline' ? undefined : currentAppearance);
280
+ const isEmbedSupportedFromAppearanceContext = allowEmbeds && preview && embedCardNodeType && isSupportedInParent(editorView.state, Fragment.from(embedCardNodeType.createChecked({})), currentAppearance === 'url' || currentAppearance === 'inline' ? undefined : currentAppearance);
281
+ const isSmartLinkConvertible = hasResolvedSmartLinkData;
282
+ const isBlockSupportedFromSelection = allowBlockCards && blockCardNodeType && isSupportedInParent(editorView.state, Fragment.from(blockCardNodeType.createChecked({})), undefined);
283
+ const isEmbedSupportedFromSelection = allowEmbeds && preview && embedCardNodeType && isSupportedInParent(editorView.state, Fragment.from(embedCardNodeType.createChecked({})), undefined);
284
+ const isBlockSupported = Boolean(isBlockSupportedFromAppearanceContext || isBlockSupportedFromSelection);
285
+ const isEmbedSupported = Boolean(isEmbedSupportedFromAppearanceContext || isEmbedSupportedFromSelection);
286
+ return /*#__PURE__*/React.createElement(Flex, {
287
+ xcss: styles.appearanceBox,
288
+ gap: "space.050"
289
+ }, /*#__PURE__*/React.createElement(AppearanceOptionIconButton, {
290
+ appearance: "url",
291
+ currentAppearance: currentAppearance,
292
+ isDisabled: false,
293
+ label: intl.formatMessage(appearancePropsMap.url.title),
294
+ Icon: MinusIcon,
295
+ onClick: handleClick('url')
296
+ }), /*#__PURE__*/React.createElement(InlineAppearanceIconButton, {
297
+ currentAppearance: currentAppearance,
298
+ isDisabled: !isSmartLinkConvertible,
299
+ label: intl.formatMessage(appearancePropsMap.inline.title),
300
+ onClick: handleClick('inline')
301
+ }), /*#__PURE__*/React.createElement(BlockAppearanceIconButton, {
302
+ currentAppearance: currentAppearance,
303
+ isDisabled: !isSmartLinkConvertible || !isBlockSupported,
304
+ label: intl.formatMessage(appearancePropsMap.block.title),
305
+ onClick: handleClick('block')
306
+ }), /*#__PURE__*/React.createElement(EmbedAppearanceIconButton, {
307
+ currentAppearance: currentAppearance,
308
+ isDisabled: !isEmbedSupported,
309
+ label: intl.formatMessage(appearancePropsMap.embed.title),
310
+ onClick: handleClick('embed')
311
+ }));
312
+ };
313
+ const PasteDisplayAsMenuSection = ({
314
+ api,
315
+ allowBlockCards,
316
+ allowEmbeds
317
+ }) => {
318
+ const intl = useIntl();
319
+ return /*#__PURE__*/React.createElement(ToolbarDropdownItemSection, {
320
+ title: intl.formatMessage({
321
+ defaultMessage: 'Display as',
322
+ description: 'Section title for Smart Link display options in the paste actions menu.',
323
+ id: 'fabric.editor.pasteDisplayAsMenu.displayAs'
324
+ })
325
+ }, /*#__PURE__*/React.createElement(PasteDisplayAsMenuHorizontalView, {
326
+ api: api,
327
+ allowBlockCards: allowBlockCards,
328
+ allowEmbeds: allowEmbeds
329
+ }));
330
+ };
331
+ export const getPasteDisplayAsMenuComponents = ({
332
+ api,
333
+ allowBlockCards,
334
+ allowEmbeds,
335
+ getEditorView
336
+ }) => [{
337
+ type: 'menu-section',
338
+ key: SMART_LINK_DISPLAY_AS_PASTE_MENU_SECTION_KEY,
339
+ parents: [{
340
+ type: PASTE_MENU.type,
341
+ key: PASTE_MENU.key,
342
+ rank: 60
343
+ }],
344
+ isHidden: () => {
345
+ var _apiWithPasteOptionsT;
346
+ const apiWithPasteOptionsToolbar = api;
347
+ const pasteRange = apiWithPasteOptionsToolbar === null || apiWithPasteOptionsToolbar === void 0 ? void 0 : (_apiWithPasteOptionsT = apiWithPasteOptionsToolbar.pasteOptionsToolbarPlugin) === null || _apiWithPasteOptionsT === void 0 ? void 0 : _apiWithPasteOptionsT.sharedState.currentState();
348
+ const editorView = getEditorView();
349
+ if (!editorView || !pasteRange) {
350
+ return true;
351
+ }
352
+ const pastedSlice = getCurrentPastedSlice(api);
353
+ const urlFromSlice = getSingleSmartLinkUrlFromSlice(pastedSlice);
354
+ const urlFromCard = getCardUrlAtPasteRange({
355
+ editorView,
356
+ pasteStartPos: pasteRange.pasteStartPos,
357
+ pasteEndPos: pasteRange.pasteEndPos
358
+ });
359
+ return !urlFromSlice && !urlFromCard;
360
+ },
361
+ component: () => /*#__PURE__*/React.createElement(PasteDisplayAsMenuSection, {
362
+ api: api,
363
+ allowBlockCards: allowBlockCards,
364
+ allowEmbeds: allowEmbeds
365
+ })
366
+ }];
@@ -0,0 +1,46 @@
1
+ const getUrlFromTextLinkNode = node => {
2
+ var _node$marks$0$attrs;
3
+ if (!node.isText || node.marks.length !== 1 || node.marks[0].type.name !== 'link') {
4
+ return undefined;
5
+ }
6
+ const href = (_node$marks$0$attrs = node.marks[0].attrs) === null || _node$marks$0$attrs === void 0 ? void 0 : _node$marks$0$attrs.href;
7
+ return typeof href === 'string' && node.text === href ? href : undefined;
8
+ };
9
+ const getUrlFromInlineCardNode = node => {
10
+ var _node$attrs;
11
+ if (node.type.name !== 'inlineCard') {
12
+ return undefined;
13
+ }
14
+ const url = (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.url;
15
+ return typeof url === 'string' ? url : undefined;
16
+ };
17
+ const significantChildren = fragment => {
18
+ const children = [];
19
+ fragment.forEach(child => {
20
+ var _child$text;
21
+ if (child.isText && ((_child$text = child.text) === null || _child$text === void 0 ? void 0 : _child$text.trim()) === '') {
22
+ return;
23
+ }
24
+ children.push(child);
25
+ });
26
+ return children;
27
+ };
28
+ export const getSingleSmartLinkUrlFromSlice = slice => {
29
+ var _getUrlFromTextLinkNo, _getUrlFromTextLinkNo2;
30
+ if (!slice || slice.content.childCount !== 1) {
31
+ return undefined;
32
+ }
33
+ const topNode = slice.content.child(0);
34
+ const topNodeUrl = (_getUrlFromTextLinkNo = getUrlFromTextLinkNode(topNode)) !== null && _getUrlFromTextLinkNo !== void 0 ? _getUrlFromTextLinkNo : getUrlFromInlineCardNode(topNode);
35
+ if (topNodeUrl) {
36
+ return topNodeUrl;
37
+ }
38
+ if (topNode.type.name !== 'paragraph') {
39
+ return undefined;
40
+ }
41
+ const children = significantChildren(topNode.content);
42
+ if (children.length !== 1) {
43
+ return undefined;
44
+ }
45
+ return (_getUrlFromTextLinkNo2 = getUrlFromTextLinkNode(children[0])) !== null && _getUrlFromTextLinkNo2 !== void 0 ? _getUrlFromTextLinkNo2 : getUrlFromInlineCardNode(children[0]);
46
+ };
@@ -0,0 +1,24 @@
1
+ export const DISPLAY_AS_OPTIONS = ['url', 'inline', 'block', 'embed'];
2
+ export const getCardAtPasteRange = (state, pasteStartPos, pasteEndPos) => {
3
+ let result;
4
+ const docContentSize = state.doc.content.size;
5
+ const clampedStart = Math.max(0, Math.min(pasteStartPos, docContentSize));
6
+ const clampedEnd = Math.max(0, Math.min(pasteEndPos, docContentSize));
7
+ const from = Math.min(clampedStart, clampedEnd);
8
+ const to = Math.max(clampedStart, clampedEnd);
9
+ try {
10
+ state.doc.nodesBetween(from, to, (node, pos) => {
11
+ if (node.type.name === 'inlineCard' || node.type.name === 'blockCard' || node.type.name === 'embedCard') {
12
+ result = {
13
+ appearance: node.type.name === 'inlineCard' ? 'inline' : node.type.name === 'blockCard' ? 'block' : 'embed',
14
+ pos
15
+ };
16
+ return false;
17
+ }
18
+ return true;
19
+ });
20
+ } catch {
21
+ return;
22
+ }
23
+ return result;
24
+ };
@@ -10,6 +10,7 @@ import { IconDatasourceAssetsObjects, IconDatasourceConfluenceSearch, IconDataso
10
10
  import { canRenderDatasource } from '@atlaskit/editor-common/utils';
11
11
  import { ASSETS_LIST_OF_LINKS_DATASOURCE_ID, CONFLUENCE_SEARCH_DATASOURCE_ID } from '@atlaskit/link-datasource';
12
12
  import { fg } from '@atlaskit/platform-feature-flags';
13
+ import { expValNoExposure } from '@atlaskit/tmp-editor-statsig/expVal';
13
14
  import { blockCardSpecWithFixedToDOM } from './nodeviews/toDOM-fixes/blockCard';
14
15
  import { embedCardSpecWithFixedToDOM } from './nodeviews/toDOM-fixes/embedCard';
15
16
  import { inlineCardSpecWithFixedToDOM } from './nodeviews/toDOM-fixes/inlineCard';
@@ -26,6 +27,7 @@ import { EditorSmartCardEvents } from './ui/EditorSmartCardEvents';
26
27
  // Ignored via go/ees005
27
28
  // eslint-disable-next-line import/no-named-as-default
28
29
  import LayoutButton from './ui/LayoutButton';
30
+ import { getPasteDisplayAsMenuComponents } from './ui/PasteDisplayAsMenu';
29
31
  import { floatingToolbar, getEndingToolbarItems, getStartingToolbarItems } from './ui/toolbar';
30
32
  export var cardPlugin = function cardPlugin(_ref) {
31
33
  var _api$base, _options$lpLinkPicker;
@@ -35,6 +37,20 @@ export var cardPlugin = function cardPlugin(_ref) {
35
37
  var previousCardProvider;
36
38
  var cardPluginEvents = createEventsQueue();
37
39
  var instanceEmbedCardTransformers = options.embedCardTransformers;
40
+ var editorViewForPasteMenu;
41
+ var pasteMenuVariant = expValNoExposure('platform_editor_paste_actions_menu_v2', 'variant', 'control');
42
+ var shouldRegisterPasteDisplayAsMenu = options.enablePasteDisplayAsMenu && ['hasSpellingAndGrammar', 'hasAltAiActions'].includes(pasteMenuVariant);
43
+ if (shouldRegisterPasteDisplayAsMenu) {
44
+ var _api$uiControlRegistr, _options$allowBlockCa;
45
+ api === null || api === void 0 || (_api$uiControlRegistr = api.uiControlRegistry) === null || _api$uiControlRegistr === void 0 || _api$uiControlRegistr.actions.register(getPasteDisplayAsMenuComponents({
46
+ api: api,
47
+ allowBlockCards: options.onlyInlineCards ? false : (_options$allowBlockCa = options.allowBlockCards) !== null && _options$allowBlockCa !== void 0 ? _options$allowBlockCa : true,
48
+ allowEmbeds: options.onlyInlineCards ? false : options.allowEmbeds,
49
+ getEditorView: function getEditorView() {
50
+ return editorViewForPasteMenu;
51
+ }
52
+ }));
53
+ }
38
54
  api === null || api === void 0 || (_api$base = api.base) === null || _api$base === void 0 || _api$base.actions.registerMarks(function (_ref2) {
39
55
  var tr = _ref2.tr,
40
56
  node = _ref2.node,
@@ -80,10 +96,10 @@ export var cardPlugin = function cardPlugin(_ref) {
80
96
  return nodes;
81
97
  },
82
98
  pmPlugins: function pmPlugins() {
83
- var _options$allowBlockCa, _options$allowResizin, _options$useAlternati, _options$allowWrappin, _options$allowAlignme, _options$allowDatasou, _options$showUpgradeD;
99
+ var _options$allowBlockCa2, _options$allowResizin, _options$useAlternati, _options$allowWrappin, _options$allowAlignme, _options$allowDatasou, _options$showUpgradeD;
84
100
  // onlyInlineCards forces block/embed off regardless of caller-passed flags,
85
101
  // keeping the schema gate (in nodes()) and the runtime gate in sync.
86
- var allowBlockCards = options.onlyInlineCards ? false : (_options$allowBlockCa = options.allowBlockCards) !== null && _options$allowBlockCa !== void 0 ? _options$allowBlockCa : true;
102
+ var allowBlockCards = options.onlyInlineCards ? false : (_options$allowBlockCa2 = options.allowBlockCards) !== null && _options$allowBlockCa2 !== void 0 ? _options$allowBlockCa2 : true;
87
103
  var allowEmbeds = options.onlyInlineCards ? false : options.allowEmbeds;
88
104
  var allowResizing = (_options$allowResizin = options.allowResizing) !== null && _options$allowResizin !== void 0 ? _options$allowResizin : true;
89
105
  var useAlternativePreloader = (_options$useAlternati = options.useAlternativePreloader) !== null && _options$useAlternati !== void 0 ? _options$useAlternati : true;
@@ -123,6 +139,7 @@ export var cardPlugin = function cardPlugin(_ref) {
123
139
  if (!editorView) {
124
140
  return null;
125
141
  }
142
+ editorViewForPasteMenu = editorView;
126
143
  var breakoutEnabled = options.editorAppearance === 'full-page';
127
144
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(EditorSmartCardEvents, {
128
145
  editorView: editorView
@@ -158,25 +175,25 @@ export var cardPlugin = function cardPlugin(_ref) {
158
175
  var _setProvider2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(providerPromise) {
159
176
  var _api$core$actions$exe;
160
177
  var provider;
161
- return _regeneratorRuntime.wrap(function _callee$(_context) {
178
+ return _regeneratorRuntime.wrap(function (_context) {
162
179
  while (1) switch (_context.prev = _context.next) {
163
180
  case 0:
164
- _context.next = 2;
181
+ _context.next = 1;
165
182
  return providerPromise;
166
- case 2:
183
+ case 1:
167
184
  provider = _context.sent;
168
185
  if (!(previousCardProvider === provider || (options === null || options === void 0 ? void 0 : options.provider) === providerPromise)) {
169
- _context.next = 5;
186
+ _context.next = 2;
170
187
  break;
171
188
  }
172
189
  return _context.abrupt("return", false);
173
- case 5:
190
+ case 2:
174
191
  previousCardProvider = provider;
175
192
  return _context.abrupt("return", (_api$core$actions$exe = api === null || api === void 0 ? void 0 : api.core.actions.execute(function (_ref6) {
176
193
  var tr = _ref6.tr;
177
194
  return _setProvider(provider)(tr);
178
195
  })) !== null && _api$core$actions$exe !== void 0 ? _api$core$actions$exe : false);
179
- case 7:
196
+ case 3:
180
197
  case "end":
181
198
  return _context.stop();
182
199
  }
@@ -195,20 +212,20 @@ export var cardPlugin = function cardPlugin(_ref) {
195
212
  },
196
213
  resolveShortLinkUrl: function () {
197
214
  var _resolveShortLinkUrl = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(url) {
198
- var provider, clientHolder, client, response, data, urlField, _linkObj$Id, linkObj, id;
199
- return _regeneratorRuntime.wrap(function _callee2$(_context2) {
215
+ var provider, clientHolder, client, response, data, urlField, _linkObj$Id, linkObj, id, _t;
216
+ return _regeneratorRuntime.wrap(function (_context2) {
200
217
  while (1) switch (_context2.prev = _context2.next) {
201
218
  case 0:
202
219
  if (options.provider) {
203
- _context2.next = 2;
220
+ _context2.next = 1;
204
221
  break;
205
222
  }
206
223
  return _context2.abrupt("return", undefined);
207
- case 2:
208
- _context2.prev = 2;
209
- _context2.next = 5;
224
+ case 1:
225
+ _context2.prev = 1;
226
+ _context2.next = 2;
210
227
  return options.provider;
211
- case 5:
228
+ case 2:
212
229
  provider = _context2.sent;
213
230
  // EditorCardProvider holds a CardClient instance internally. We access it
214
231
  // directly to call fetchData, which gives us the raw ORS JSON-LD response
@@ -218,14 +235,14 @@ export var cardPlugin = function cardPlugin(_ref) {
218
235
  clientHolder = provider;
219
236
  client = clientHolder.cardClient;
220
237
  if (client) {
221
- _context2.next = 10;
238
+ _context2.next = 3;
222
239
  break;
223
240
  }
224
241
  return _context2.abrupt("return", undefined);
225
- case 10:
226
- _context2.next = 12;
242
+ case 3:
243
+ _context2.next = 4;
227
244
  return client.fetchData(url);
228
- case 12:
245
+ case 4:
229
246
  response = _context2.sent;
230
247
  // ORS returns a JsonLd.Response whose data union includes BaseData,
231
248
  // BaseCollectionData, and BaseCollectionPage. We only care about the
@@ -233,43 +250,43 @@ export var cardPlugin = function cardPlugin(_ref) {
233
250
  // to a minimal shape rather than accessing through the full union type.
234
251
  data = response === null || response === void 0 ? void 0 : response.data;
235
252
  if (data) {
236
- _context2.next = 16;
253
+ _context2.next = 5;
237
254
  break;
238
255
  }
239
256
  return _context2.abrupt("return", undefined);
240
- case 16:
257
+ case 5:
241
258
  urlField = data.url; // Property<string | Link> can be a string, a Link object, or an array.
242
259
  // Short-link responses always return a plain string URL.
243
260
  if (!(typeof urlField === 'string' && urlField !== url)) {
244
- _context2.next = 19;
261
+ _context2.next = 6;
245
262
  break;
246
263
  }
247
264
  return _context2.abrupt("return", urlField);
248
- case 19:
265
+ case 6:
249
266
  if (!(urlField && _typeof(urlField) === 'object' && !Array.isArray(urlField))) {
250
- _context2.next = 24;
267
+ _context2.next = 7;
251
268
  break;
252
269
  }
253
270
  linkObj = urlField;
254
271
  id = (_linkObj$Id = linkObj['@id']) !== null && _linkObj$Id !== void 0 ? _linkObj$Id : linkObj['href'];
255
272
  if (!(typeof id === 'string' && id !== url)) {
256
- _context2.next = 24;
273
+ _context2.next = 7;
257
274
  break;
258
275
  }
259
276
  return _context2.abrupt("return", id);
260
- case 24:
261
- _context2.next = 28;
277
+ case 7:
278
+ _context2.next = 9;
262
279
  break;
263
- case 26:
264
- _context2.prev = 26;
265
- _context2.t0 = _context2["catch"](2);
266
- case 28:
280
+ case 8:
281
+ _context2.prev = 8;
282
+ _t = _context2["catch"](1);
283
+ case 9:
267
284
  return _context2.abrupt("return", undefined);
268
- case 29:
285
+ case 10:
269
286
  case "end":
270
287
  return _context2.stop();
271
288
  }
272
- }, _callee2, null, [[2, 26]]);
289
+ }, _callee2, null, [[1, 8]]);
273
290
  }));
274
291
  function resolveShortLinkUrl(_x2) {
275
292
  return _resolveShortLinkUrl.apply(this, arguments);