@atlaskit/editor-plugin-card 16.10.3 → 16.11.1

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 (30) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/cardPlugin.js +19 -2
  3. package/dist/cjs/pm-plugins/doc.js +2 -2
  4. package/dist/cjs/ui/PasteDisplayAsMenu.compiled.css +31 -0
  5. package/dist/cjs/ui/PasteDisplayAsMenu.js +368 -0
  6. package/dist/cjs/ui/currentPastedSmartLink.js +52 -0
  7. package/dist/cjs/ui/pasteDisplayAsUtils.js +30 -0
  8. package/dist/es2019/cardPlugin.js +17 -2
  9. package/dist/es2019/pm-plugins/doc.js +2 -2
  10. package/dist/es2019/ui/PasteDisplayAsMenu.compiled.css +31 -0
  11. package/dist/es2019/ui/PasteDisplayAsMenu.js +366 -0
  12. package/dist/es2019/ui/currentPastedSmartLink.js +46 -0
  13. package/dist/es2019/ui/pasteDisplayAsUtils.js +24 -0
  14. package/dist/esm/cardPlugin.js +19 -2
  15. package/dist/esm/pm-plugins/doc.js +2 -2
  16. package/dist/esm/ui/PasteDisplayAsMenu.compiled.css +31 -0
  17. package/dist/esm/ui/PasteDisplayAsMenu.js +359 -0
  18. package/dist/esm/ui/currentPastedSmartLink.js +46 -0
  19. package/dist/esm/ui/pasteDisplayAsUtils.js +24 -0
  20. package/dist/types/cardPluginType.d.ts +3 -1
  21. package/dist/types/types/index.d.ts +1 -0
  22. package/dist/types/ui/PasteDisplayAsMenu.d.ts +25 -0
  23. package/dist/types/ui/currentPastedSmartLink.d.ts +2 -0
  24. package/dist/types/ui/pasteDisplayAsUtils.d.ts +7 -0
  25. package/dist/types-ts4.5/cardPluginType.d.ts +3 -1
  26. package/dist/types-ts4.5/types/index.d.ts +1 -0
  27. package/dist/types-ts4.5/ui/PasteDisplayAsMenu.d.ts +25 -0
  28. package/dist/types-ts4.5/ui/currentPastedSmartLink.d.ts +2 -0
  29. package/dist/types-ts4.5/ui/pasteDisplayAsUtils.d.ts +12 -0
  30. package/package.json +9 -5
@@ -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
@@ -377,7 +377,7 @@ export var changeSelectedCardToLink = function changeSelectedCardToLink(text, hr
377
377
  return function (state, dispatch) {
378
378
  var selectedNode = state.selection instanceof NodeSelection ? state.selection.node : undefined;
379
379
  var tr;
380
- if (node && pos) {
380
+ if (node && pos !== undefined) {
381
381
  tr = cardNodeToLinkWithTransaction(state, text, href, node, pos);
382
382
  } else {
383
383
  tr = cardToLinkWithTransaction(state, text, href);
@@ -405,7 +405,7 @@ export var changeSelectedCardToLink = function changeSelectedCardToLink(text, hr
405
405
  export var changeSelectedCardToLinkFallback = function changeSelectedCardToLinkFallback(text, href, sendAnalytics, node, pos, editorAnalyticsApi) {
406
406
  return function (state, dispatch) {
407
407
  var tr;
408
- if (node && pos) {
408
+ if (node && pos !== undefined) {
409
409
  tr = cardNodeToLinkWithTransaction(state, text, href, node, pos);
410
410
  } else {
411
411
  tr = cardToLinkWithTransaction(state, text, href);
@@ -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)}