@atlaskit/editor-plugin-card 18.3.1 → 18.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-card
2
2
 
3
+ ## 18.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`448792b5143ac`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/448792b5143ac) -
8
+ Show the correct Display as option for a pasted link. A 1P link that resolves to an embed is
9
+ inserted as a native embed, which the paste menu now reports as an embed and can switch to a link,
10
+ inline card or card. The pasted link's URL is also used to pick the card the menu describes, so a
11
+ neighbouring card is no longer reported as the one that was pasted.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+
17
+ ## 18.3.2
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+
3
23
  ## 18.3.1
4
24
 
5
25
  ### Patch Changes
@@ -31,6 +31,7 @@ var _compiled = require("@atlaskit/primitives/compiled");
31
31
  var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
32
32
  var _doc = require("../pm-plugins/doc");
33
33
  var _currentPastedSmartLink = require("./currentPastedSmartLink");
34
+ var _nativeEmbedNode = require("./nativeEmbedNode");
34
35
  var _pasteDisplayAsUtils = require("./pasteDisplayAsUtils");
35
36
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t2 in e) "default" !== _t2 && {}.hasOwnProperty.call(e, _t2) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t2)) && (i.get || i.set) ? o(f, _t2, i) : f[_t2] = e[_t2]); return f; })(e, t); }
36
37
  var SMART_LINK_DISPLAY_AS_PASTE_MENU_SECTION_KEY = exports.SMART_LINK_DISPLAY_AS_PASTE_MENU_SECTION_KEY = 'smart-link-display-as-paste-menu-section';
@@ -123,12 +124,22 @@ var getCurrentPastedSlice = function getCurrentPastedSlice(api) {
123
124
  return slice;
124
125
  };
125
126
  var getCardUrlAtPasteRange = function getCardUrlAtPasteRange(_ref5) {
126
- var _editorView$state$doc, _maybeAttrs$url, _maybeAttrs$data;
127
+ var _maybeAttrs$url, _maybeAttrs$data;
127
128
  var editorView = _ref5.editorView,
128
129
  pasteStartPos = _ref5.pasteStartPos,
129
130
  pasteEndPos = _ref5.pasteEndPos;
130
131
  var cardAtPasteRange = (0, _pasteDisplayAsUtils.getCardAtPasteRange)(editorView.state, pasteStartPos, pasteEndPos);
131
- var maybeAttrs = cardAtPasteRange ? (_editorView$state$doc = editorView.state.doc.nodeAt(cardAtPasteRange.pos)) === null || _editorView$state$doc === void 0 ? void 0 : _editorView$state$doc.attrs : undefined;
132
+ if (!cardAtPasteRange) {
133
+ return undefined;
134
+ }
135
+ var node = editorView.state.doc.nodeAt(cardAtPasteRange.pos);
136
+ if (!node) {
137
+ return undefined;
138
+ }
139
+ if (cardAtPasteRange.isNativeEmbed) {
140
+ return (0, _nativeEmbedNode.getNativeEmbedUrl)(node);
141
+ }
142
+ var maybeAttrs = node.attrs;
132
143
  var maybeUrl = (_maybeAttrs$url = maybeAttrs === null || maybeAttrs === void 0 ? void 0 : maybeAttrs.url) !== null && _maybeAttrs$url !== void 0 ? _maybeAttrs$url : maybeAttrs === null || maybeAttrs === void 0 || (_maybeAttrs$data = maybeAttrs.data) === null || _maybeAttrs$data === void 0 ? void 0 : _maybeAttrs$data.url;
133
144
  return typeof maybeUrl === 'string' ? maybeUrl : undefined;
134
145
  };
@@ -188,7 +199,7 @@ var normalizeSelectionToLinkRangeForUrlAppearance = exports.normalizeSelectionTo
188
199
  editorView.dispatch(linkRangeSelectionTr);
189
200
  };
190
201
  var PasteDisplayAsMenuHorizontalView = function PasteDisplayAsMenuHorizontalView(_ref8) {
191
- var _smartCardContext$val, _getCardAtPasteRange$, _getCardAtPasteRange, _smartCardContext$val2;
202
+ var _smartCardContext$val, _cardAtPasteRange$app, _smartCardContext$val2;
192
203
  var api = _ref8.api,
193
204
  allowBlockCards = _ref8.allowBlockCards,
194
205
  allowEmbeds = _ref8.allowEmbeds;
@@ -223,7 +234,11 @@ var PasteDisplayAsMenuHorizontalView = function PasteDisplayAsMenuHorizontalView
223
234
  var pastedLinkUrl = pastedLinkUrlFromSlice !== null && pastedLinkUrlFromSlice !== void 0 ? pastedLinkUrlFromSlice : pastedLinkUrlFromCard;
224
235
  var pastedLinkUrlState = pastedLinkUrl ? (_smartCardContext$val = smartCardContext.value) === null || _smartCardContext$val === void 0 || (_smartCardContext$val = _smartCardContext$val.store) === null || _smartCardContext$val === void 0 || (_smartCardContext$val = _smartCardContext$val.getState()) === null || _smartCardContext$val === void 0 ? void 0 : _smartCardContext$val[pastedLinkUrl] : undefined;
225
236
  var hasResolvedSmartLinkData = Boolean(pastedLinkUrlState === null || pastedLinkUrlState === void 0 ? void 0 : pastedLinkUrlState.details);
226
- var currentAppearance = editorView && pasteRange ? (_getCardAtPasteRange$ = (_getCardAtPasteRange = (0, _pasteDisplayAsUtils.getCardAtPasteRange)(editorView.state, pasteRange.pasteStartPos, pasteRange.pasteEndPos)) === null || _getCardAtPasteRange === void 0 ? void 0 : _getCardAtPasteRange.appearance) !== null && _getCardAtPasteRange$ !== void 0 ? _getCardAtPasteRange$ : 'url' : undefined;
237
+ var cardAtPasteRange = editorView && pasteRange ? (0, _pasteDisplayAsUtils.getCardAtPasteRange)(editorView.state, pasteRange.pasteStartPos, pasteRange.pasteEndPos, pastedLinkUrl) : undefined;
238
+ var currentAppearance = editorView && pasteRange ? (_cardAtPasteRange$app = cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.appearance) !== null && _cardAtPasteRange$app !== void 0 ? _cardAtPasteRange$app : 'url' : undefined;
239
+ // A 1P link that resolves to an embed is inserted as a native embed, not an embedCard.
240
+ // It displays as an embed, but the card commands cannot change its appearance.
241
+ var isNativeEmbedPaste = Boolean(cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.isNativeEmbed);
227
242
  var handleClick = (0, _react.useCallback)(function (appearance) {
228
243
  return function () {
229
244
  var _state$doc$nodeAt2, _targetNodeAttrs$url, _targetNodeAttrs$data;
@@ -235,17 +250,35 @@ var PasteDisplayAsMenuHorizontalView = function PasteDisplayAsMenuHorizontalView
235
250
  dispatch = editorView.dispatch;
236
251
  var pasteStartPos = pasteRange.pasteStartPos,
237
252
  pasteEndPos = pasteRange.pasteEndPos;
238
- var cardAtPasteRange = (0, _pasteDisplayAsUtils.getCardAtPasteRange)(state, pasteStartPos, pasteEndPos);
253
+ var cardAtRange = (0, _pasteDisplayAsUtils.getCardAtPasteRange)(state, pasteStartPos, pasteEndPos, pastedLinkUrl);
254
+ if (cardAtRange !== null && cardAtRange !== void 0 && cardAtRange.isNativeEmbed) {
255
+ var nativeEmbedNode = state.doc.nodeAt(cardAtRange.pos);
256
+ // 'embed' is what a native embed already displays as, so there is nothing to apply.
257
+ if (nativeEmbedNode && appearance !== 'embed') {
258
+ var _api$analytics;
259
+ (0, _nativeEmbedNode.changeNativeEmbedAppearance)({
260
+ editorView: editorView,
261
+ pos: cardAtRange.pos,
262
+ node: nativeEmbedNode,
263
+ appearance: appearance,
264
+ url: pastedLinkUrl,
265
+ editorAnalyticsApi: api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions
266
+ });
267
+ }
268
+ toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 || toolbarDropdownMenu.closeMenu(null);
269
+ isApplyingRef.current = false;
270
+ return;
271
+ }
239
272
  if (appearance === 'url') {
240
- if (cardAtPasteRange) {
241
- var _state$doc$nodeAt, _api$analytics;
242
- (0, _doc.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 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)(state, dispatch, editorView);
273
+ if (cardAtRange) {
274
+ var _state$doc$nodeAt, _api$analytics2;
275
+ (0, _doc.changeSelectedCardToLink)(pastedLinkUrl, pastedLinkUrl, true, (_state$doc$nodeAt = state.doc.nodeAt(cardAtRange.pos)) !== null && _state$doc$nodeAt !== void 0 ? _state$doc$nodeAt : undefined, cardAtRange.pos, api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions)(state, dispatch, editorView);
243
276
  }
244
277
  toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 || toolbarDropdownMenu.closeMenu(null);
245
278
  isApplyingRef.current = false;
246
279
  return;
247
280
  }
248
- var targetPos = cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.pos;
281
+ var targetPos = cardAtRange === null || cardAtRange === void 0 ? void 0 : cardAtRange.pos;
249
282
  var targetNodeAttrs = targetPos === undefined ? undefined : (_state$doc$nodeAt2 = state.doc.nodeAt(targetPos)) === null || _state$doc$nodeAt2 === void 0 ? void 0 : _state$doc$nodeAt2.attrs;
250
283
  var targetNodeUrl = (_targetNodeAttrs$url = targetNodeAttrs === null || targetNodeAttrs === void 0 ? void 0 : targetNodeAttrs.url) !== null && _targetNodeAttrs$url !== void 0 ? _targetNodeAttrs$url : targetNodeAttrs === null || targetNodeAttrs === void 0 || (_targetNodeAttrs$data = targetNodeAttrs.data) === null || _targetNodeAttrs$data === void 0 ? void 0 : _targetNodeAttrs$data.url;
251
284
  var isRecoveredAdjacentPastedCard = (0, _expValEqualsNoExposure.expValEqualsNoExposure)('confluence_editor_paste_3p_link_actions_menu', 'isEnabled', true) && targetPos === pasteStartPos - 1 && pasteStartPos === pasteEndPos && targetNodeUrl === pastedLinkUrl;
@@ -269,9 +302,9 @@ var PasteDisplayAsMenuHorizontalView = function PasteDisplayAsMenuHorizontalView
269
302
  targetPos: targetPos
270
303
  });
271
304
  frameRef.current = requestAnimationFrame(function () {
272
- var _api$analytics2;
305
+ var _api$analytics3;
273
306
  frameRef.current = null;
274
- (0, _doc.setSelectedCardAppearance)(appearance, api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions)(editorView.state, editorView.dispatch, editorView);
307
+ (0, _doc.setSelectedCardAppearance)(appearance, api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions)(editorView.state, editorView.dispatch, editorView);
275
308
  toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 || toolbarDropdownMenu.closeMenu(null);
276
309
  isApplyingRef.current = false;
277
310
  });
@@ -296,26 +329,39 @@ var PasteDisplayAsMenuHorizontalView = function PasteDisplayAsMenuHorizontalView
296
329
  var isSmartLinkConvertible = hasResolvedSmartLinkData;
297
330
  var isBlockSupportedFromSelection = allowBlockCards && blockCardNodeType && (0, _utils.isSupportedInParent)(editorView.state, _model.Fragment.from(blockCardNodeType.createChecked({})), undefined);
298
331
  var isEmbedSupportedFromSelection = allowEmbeds && preview && embedCardNodeType && (0, _utils.isSupportedInParent)(editorView.state, _model.Fragment.from(embedCardNodeType.createChecked({})), undefined);
299
- var isBlockSupported = Boolean(isBlockSupportedFromAppearanceContext || isBlockSupportedFromSelection);
300
- var isEmbedSupported = Boolean(isEmbedSupportedFromAppearanceContext || isEmbedSupportedFromSelection);
332
+ // A native embed is replaced in place, so support depends on what its own position
333
+ // accepts rather than on the current selection or on an embed preview being available.
334
+ var nativeEmbedPos = isNativeEmbedPaste ? cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.pos : undefined;
335
+ var isSupportedForNativeEmbed = function isSupportedForNativeEmbed(appearance) {
336
+ return nativeEmbedPos !== undefined && (0, _nativeEmbedNode.isNativeEmbedAppearanceSupported)({
337
+ editorView: editorView,
338
+ pos: nativeEmbedPos,
339
+ appearance: appearance
340
+ });
341
+ };
342
+ var isInlineSupported = isNativeEmbedPaste ? isSupportedForNativeEmbed('inline') : true;
343
+ var isBlockSupported = isNativeEmbedPaste ? allowBlockCards && isSupportedForNativeEmbed('block') : Boolean(isBlockSupportedFromAppearanceContext || isBlockSupportedFromSelection);
344
+ var isEmbedSupported =
345
+ // Whatever was pasted already displays this way, so the option must stay selectable.
346
+ currentAppearance === 'embed' || Boolean(isEmbedSupportedFromAppearanceContext || isEmbedSupportedFromSelection);
301
347
  return /*#__PURE__*/_react.default.createElement(_compiled.Flex, {
302
348
  xcss: styles.appearanceBox,
303
349
  gap: "space.050"
304
350
  }, /*#__PURE__*/_react.default.createElement(AppearanceOptionIconButton, {
305
351
  appearance: "url",
306
352
  currentAppearance: currentAppearance,
307
- isDisabled: false,
353
+ isDisabled: isNativeEmbedPaste && !isSupportedForNativeEmbed('url'),
308
354
  label: intl.formatMessage(_card.appearancePropsMap.url.title),
309
355
  Icon: _minus.default,
310
356
  onClick: handleClick('url')
311
357
  }), /*#__PURE__*/_react.default.createElement(InlineAppearanceIconButton, {
312
358
  currentAppearance: currentAppearance,
313
- isDisabled: !isSmartLinkConvertible,
359
+ isDisabled: isNativeEmbedPaste ? !isInlineSupported : !isSmartLinkConvertible,
314
360
  label: intl.formatMessage(_card.appearancePropsMap.inline.title),
315
361
  onClick: handleClick('inline')
316
362
  }), /*#__PURE__*/_react.default.createElement(BlockAppearanceIconButton, {
317
363
  currentAppearance: currentAppearance,
318
- isDisabled: !isSmartLinkConvertible || !isBlockSupported,
364
+ isDisabled: !isBlockSupported || !isNativeEmbedPaste && !isSmartLinkConvertible,
319
365
  label: intl.formatMessage(_card.appearancePropsMap.block.title),
320
366
  onClick: handleClick('block')
321
367
  }), /*#__PURE__*/_react.default.createElement(EmbedAppearanceIconButton, {
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.isNativeEmbedNode = exports.isNativeEmbedAppearanceSupported = exports.getNativeEmbedUrl = exports.changeNativeEmbedAppearance = void 0;
8
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
+ var _url = require("@atlaskit/adf-schema/url");
10
+ var _analytics = require("@atlaskit/editor-common/analytics");
11
+ var _extensions = require("@atlaskit/editor-common/extensions");
12
+ /**
13
+ * A native embed is an `extension` node that `editor-plugin-native-embeds` builds from
14
+ * embedCard ADF (via the `embedCardNodeTransformer` registered on the card plugin).
15
+ * A 1P link that resolves to an embed appearance — a whiteboard, page, slide or
16
+ * database — therefore lands in the document as one of these rather than as an
17
+ * `embedCard`, so card node types alone are not enough to describe what was pasted.
18
+ *
19
+ * `editor-plugin-native-embeds` owns the equivalent predicate, but the card plugin
20
+ * cannot depend on it: that would close the cycle
21
+ * editor-plugin-card -> editor-plugin-native-embeds -> editor-plugin-card.
22
+ */
23
+ var isNativeEmbedNode = exports.isNativeEmbedNode = function isNativeEmbedNode(node) {
24
+ var _node$attrs, _node$attrs2;
25
+ return (node === null || node === void 0 ? void 0 : node.type.name) === 'extension' && ((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionType) === _extensions.NATIVE_EMBED_EXTENSION_TYPE && typeof ((_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.extensionKey) === 'string' && node.attrs.extensionKey.includes(_extensions.NATIVE_EMBED_EXTENSION_KEY);
26
+ };
27
+
28
+ /**
29
+ * Reads the embedded URL from a native embed node. The URL is stored in
30
+ * `parameters.macroParams`, where values may be wrapped (`{ value }`) or bare, and is
31
+ * mirrored in `parameters.macroMetadata` so it survives a revert to a card.
32
+ */
33
+ var getNativeEmbedUrl = exports.getNativeEmbedUrl = function getNativeEmbedUrl(node) {
34
+ var _node$attrs3, _parameters$macroPara, _ref, _parameters$macroMeta;
35
+ var parameters = (_node$attrs3 = node.attrs) === null || _node$attrs3 === void 0 ? void 0 : _node$attrs3.parameters;
36
+ var macroParamUrl = parameters === null || parameters === void 0 || (_parameters$macroPara = parameters.macroParams) === null || _parameters$macroPara === void 0 ? void 0 : _parameters$macroPara.url;
37
+ var url = (_ref = macroParamUrl && (0, _typeof2.default)(macroParamUrl) === 'object' && 'value' in macroParamUrl ? macroParamUrl.value : macroParamUrl) !== null && _ref !== void 0 ? _ref : parameters === null || parameters === void 0 || (_parameters$macroMeta = parameters.macroMetadata) === null || _parameters$macroMeta === void 0 ? void 0 : _parameters$macroMeta.url;
38
+ return typeof url === 'string' ? url : undefined;
39
+ };
40
+ var canReplaceNodeWith = function canReplaceNodeWith(view, pos, nodeType) {
41
+ if (!nodeType) {
42
+ return false;
43
+ }
44
+ try {
45
+ var $pos = view.state.doc.resolve(pos);
46
+ var index = $pos.index();
47
+ return $pos.parent.canReplaceWith(index, index + 1, nodeType);
48
+ } catch (_unused) {
49
+ return false;
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Whether a native embed at `pos` can be replaced by the given appearance. `url` and
55
+ * `inline` both become a paragraph, `block` becomes a blockCard.
56
+ */
57
+ var isNativeEmbedAppearanceSupported = exports.isNativeEmbedAppearanceSupported = function isNativeEmbedAppearanceSupported(_ref2) {
58
+ var editorView = _ref2.editorView,
59
+ pos = _ref2.pos,
60
+ appearance = _ref2.appearance;
61
+ var _editorView$state$sch = editorView.state.schema.nodes,
62
+ paragraph = _editorView$state$sch.paragraph,
63
+ inlineCard = _editorView$state$sch.inlineCard,
64
+ blockCard = _editorView$state$sch.blockCard;
65
+ if (appearance === 'block') {
66
+ return canReplaceNodeWith(editorView, pos, blockCard);
67
+ }
68
+ if (appearance === 'inline' && !inlineCard) {
69
+ return false;
70
+ }
71
+ if (appearance === 'url' && !editorView.state.schema.marks.link) {
72
+ return false;
73
+ }
74
+ return canReplaceNodeWith(editorView, pos, paragraph);
75
+ };
76
+
77
+ /**
78
+ * Replaces a native embed with a link, an inline card or a block card. Mirrors
79
+ * `setNativeEmbedAppearance` in `editor-plugin-native-embeds`, which cannot be reused
80
+ * here because of the package cycle described on `isNativeEmbedNode`.
81
+ */
82
+ var changeNativeEmbedAppearance = exports.changeNativeEmbedAppearance = function changeNativeEmbedAppearance(_ref3) {
83
+ var editorView = _ref3.editorView,
84
+ pos = _ref3.pos,
85
+ node = _ref3.node,
86
+ appearance = _ref3.appearance,
87
+ url = _ref3.url,
88
+ editorAnalyticsApi = _ref3.editorAnalyticsApi;
89
+ var state = editorView.state,
90
+ dispatch = editorView.dispatch;
91
+ var _state$schema$nodes = state.schema.nodes,
92
+ paragraph = _state$schema$nodes.paragraph,
93
+ inlineCard = _state$schema$nodes.inlineCard,
94
+ blockCard = _state$schema$nodes.blockCard;
95
+ var link = state.schema.marks.link;
96
+ if (!(0, _url.isSafeUrl)(url)) {
97
+ return false;
98
+ }
99
+ var content;
100
+ if (appearance === 'url' && link && paragraph) {
101
+ content = paragraph.create(null, state.schema.text(url, [link.create({
102
+ href: url
103
+ })]));
104
+ } else if (appearance === 'inline' && inlineCard && paragraph) {
105
+ content = paragraph.create(null, inlineCard.create({
106
+ url: url
107
+ }));
108
+ } else if (appearance === 'block' && blockCard) {
109
+ content = blockCard.create({
110
+ url: url
111
+ });
112
+ }
113
+ if (!content) {
114
+ return false;
115
+ }
116
+ try {
117
+ var tr = state.tr.replaceWith(pos, pos + node.nodeSize, content).scrollIntoView();
118
+ editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent({
119
+ action: _analytics.ACTION.CHANGED_TYPE,
120
+ actionSubject: _analytics.ACTION_SUBJECT.NATIVE_EMBED,
121
+ eventType: _analytics.EVENT_TYPE.TRACK,
122
+ attributes: {
123
+ newType: appearance,
124
+ previousType: 'nativeEmbed'
125
+ }
126
+ })(tr);
127
+ dispatch(tr);
128
+ return true;
129
+ } catch (_unused2) {
130
+ return false;
131
+ }
132
+ };
@@ -1,45 +1,108 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
7
  exports.getCardAtPasteRange = exports.DISPLAY_AS_OPTIONS = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _nativeEmbedNode = require("./nativeEmbedNode");
7
11
  var DISPLAY_AS_OPTIONS = exports.DISPLAY_AS_OPTIONS = ['url', 'inline', 'block', 'embed'];
8
- var getCardAtPasteRange = exports.getCardAtPasteRange = function getCardAtPasteRange(state, pasteStartPos, pasteEndPos) {
9
- var result;
12
+ var cardAtPos = function cardAtPos(node, pos) {
13
+ switch (node === null || node === void 0 ? void 0 : node.type.name) {
14
+ case 'inlineCard':
15
+ return {
16
+ appearance: 'inline',
17
+ pos: pos
18
+ };
19
+ case 'blockCard':
20
+ return {
21
+ appearance: 'block',
22
+ pos: pos
23
+ };
24
+ case 'embedCard':
25
+ return {
26
+ appearance: 'embed',
27
+ pos: pos
28
+ };
29
+ default:
30
+ return (0, _nativeEmbedNode.isNativeEmbedNode)(node) ? {
31
+ appearance: 'embed',
32
+ isNativeEmbed: true,
33
+ pos: pos
34
+ } : undefined;
35
+ }
36
+ };
37
+ var getNodeUrl = function getNodeUrl(node) {
38
+ var _attrs$url, _attrs$data;
39
+ if ((0, _nativeEmbedNode.isNativeEmbedNode)(node)) {
40
+ return (0, _nativeEmbedNode.getNativeEmbedUrl)(node);
41
+ }
42
+ var attrs = node.attrs;
43
+ var url = (_attrs$url = attrs === null || attrs === void 0 ? void 0 : attrs.url) !== null && _attrs$url !== void 0 ? _attrs$url : attrs === null || attrs === void 0 || (_attrs$data = attrs.data) === null || _attrs$data === void 0 ? void 0 : _attrs$data.url;
44
+ return typeof url === 'string' ? url : undefined;
45
+ };
46
+ /**
47
+ * Finds the card that a paste produced.
48
+ *
49
+ * The paste range is recorded when the paste happens and then mapped through every later
50
+ * transaction, so by the time the link has resolved into a card the range can point just
51
+ * past that card. `pastedUrl` resolves the ambiguity: when it is known, a candidate
52
+ * holding that URL wins over one that merely sits in the range, which stops a pre-existing
53
+ * neighbouring card from being reported as the pasted one.
54
+ */
55
+ var getCardAtPasteRange = exports.getCardAtPasteRange = function getCardAtPasteRange(state, pasteStartPos, pasteEndPos, pastedUrl) {
10
56
  var docContentSize = state.doc.content.size;
11
57
  var clampedStart = Math.max(0, Math.min(pasteStartPos, docContentSize));
12
58
  var clampedEnd = Math.max(0, Math.min(pasteEndPos, docContentSize));
13
59
  var from = Math.min(clampedStart, clampedEnd);
14
60
  var to = Math.max(clampedStart, clampedEnd);
15
61
  try {
16
- if (from === to) {
17
- for (var _i = 0, _arr = [from - 1, from]; _i < _arr.length; _i++) {
18
- var pos = _arr[_i];
19
- if (pos < 0 || pos >= docContentSize) {
20
- continue;
21
- }
22
- var node = state.doc.nodeAt(pos);
23
- if ((node === null || node === void 0 ? void 0 : node.type.name) === 'inlineCard' || (node === null || node === void 0 ? void 0 : node.type.name) === 'blockCard' || (node === null || node === void 0 ? void 0 : node.type.name) === 'embedCard') {
24
- return {
25
- appearance: node.type.name === 'inlineCard' ? 'inline' : node.type.name === 'blockCard' ? 'block' : 'embed',
26
- pos: pos
27
- };
28
- }
62
+ var _inRange;
63
+ var candidateAt = function candidateAt(pos) {
64
+ if (pos < 0 || pos >= docContentSize) {
65
+ return undefined;
29
66
  }
30
- }
67
+ var node = state.doc.nodeAt(pos);
68
+ var card = cardAtPos(node, pos);
69
+ return node && card ? {
70
+ card: card,
71
+ url: getNodeUrl(node)
72
+ } : undefined;
73
+ };
74
+ var adjacent = [candidateAt(from - 1), candidateAt(from)].filter(function (candidate) {
75
+ return Boolean(candidate);
76
+ });
77
+ var inRange = [];
31
78
  state.doc.nodesBetween(from, to, function (node, pos) {
32
- if (node.type.name === 'inlineCard' || node.type.name === 'blockCard' || node.type.name === 'embedCard') {
33
- result = {
34
- appearance: node.type.name === 'inlineCard' ? 'inline' : node.type.name === 'blockCard' ? 'block' : 'embed',
35
- pos: pos
36
- };
79
+ var card = cardAtPos(node, pos);
80
+ if (card) {
81
+ inRange.push({
82
+ card: card,
83
+ url: getNodeUrl(node)
84
+ });
37
85
  return false;
38
86
  }
39
87
  return true;
40
88
  });
89
+ if (pastedUrl) {
90
+ var match = [].concat((0, _toConsumableArray2.default)(adjacent), inRange).find(function (candidate) {
91
+ return candidate.url === pastedUrl;
92
+ });
93
+ if (match) {
94
+ return match.card;
95
+ }
96
+ }
97
+ if (from === to) {
98
+ var _adjacent = (0, _slicedToArray2.default)(adjacent, 1),
99
+ firstAdjacent = _adjacent[0];
100
+ if (firstAdjacent) {
101
+ return firstAdjacent.card;
102
+ }
103
+ }
104
+ return (_inRange = inRange[inRange.length - 1]) === null || _inRange === void 0 ? void 0 : _inRange.card;
41
105
  } catch (_unused) {
42
- return;
106
+ return undefined;
43
107
  }
44
- return result;
45
108
  };
@@ -21,6 +21,7 @@ import { Box, Flex, Pressable } from '@atlaskit/primitives/compiled';
21
21
  import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
22
22
  import { changeSelectedCardToLink, setSelectedCardAppearance } from '../pm-plugins/doc';
23
23
  import { getSingleSmartLinkUrlFromSlice } from './currentPastedSmartLink';
24
+ import { changeNativeEmbedAppearance, getNativeEmbedUrl, isNativeEmbedAppearanceSupported } from './nativeEmbedNode';
24
25
  import { getCardAtPasteRange } from './pasteDisplayAsUtils';
25
26
  export const SMART_LINK_DISPLAY_AS_PASTE_MENU_SECTION_KEY = 'smart-link-display-as-paste-menu-section';
26
27
 
@@ -114,9 +115,19 @@ const getCardUrlAtPasteRange = ({
114
115
  pasteStartPos,
115
116
  pasteEndPos
116
117
  }) => {
117
- var _editorView$state$doc, _maybeAttrs$url, _maybeAttrs$data;
118
+ var _maybeAttrs$url, _maybeAttrs$data;
118
119
  const cardAtPasteRange = getCardAtPasteRange(editorView.state, pasteStartPos, pasteEndPos);
119
- 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;
120
+ if (!cardAtPasteRange) {
121
+ return undefined;
122
+ }
123
+ const node = editorView.state.doc.nodeAt(cardAtPasteRange.pos);
124
+ if (!node) {
125
+ return undefined;
126
+ }
127
+ if (cardAtPasteRange.isNativeEmbed) {
128
+ return getNativeEmbedUrl(node);
129
+ }
130
+ const maybeAttrs = node.attrs;
120
131
  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;
121
132
  return typeof maybeUrl === 'string' ? maybeUrl : undefined;
122
133
  };
@@ -186,7 +197,7 @@ const PasteDisplayAsMenuHorizontalView = ({
186
197
  allowBlockCards,
187
198
  allowEmbeds
188
199
  }) => {
189
- var _smartCardContext$val, _smartCardContext$val2, _smartCardContext$val3, _getCardAtPasteRange$, _getCardAtPasteRange, _smartCardContext$val4;
200
+ var _smartCardContext$val, _smartCardContext$val2, _smartCardContext$val3, _cardAtPasteRange$app, _smartCardContext$val4;
190
201
  const intl = useIntl();
191
202
  const {
192
203
  editorView
@@ -219,7 +230,11 @@ const PasteDisplayAsMenuHorizontalView = ({
219
230
  const pastedLinkUrl = pastedLinkUrlFromSlice !== null && pastedLinkUrlFromSlice !== void 0 ? pastedLinkUrlFromSlice : pastedLinkUrlFromCard;
220
231
  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;
221
232
  const hasResolvedSmartLinkData = Boolean(pastedLinkUrlState === null || pastedLinkUrlState === void 0 ? void 0 : pastedLinkUrlState.details);
222
- 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;
233
+ const cardAtPasteRange = editorView && pasteRange ? getCardAtPasteRange(editorView.state, pasteRange.pasteStartPos, pasteRange.pasteEndPos, pastedLinkUrl) : undefined;
234
+ const currentAppearance = editorView && pasteRange ? (_cardAtPasteRange$app = cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.appearance) !== null && _cardAtPasteRange$app !== void 0 ? _cardAtPasteRange$app : 'url' : undefined;
235
+ // A 1P link that resolves to an embed is inserted as a native embed, not an embedCard.
236
+ // It displays as an embed, but the card commands cannot change its appearance.
237
+ const isNativeEmbedPaste = Boolean(cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.isNativeEmbed);
223
238
  const handleClick = useCallback(appearance => () => {
224
239
  var _state$doc$nodeAt2, _targetNodeAttrs$url, _targetNodeAttrs$data;
225
240
  if (!editorView || !pasteRange || !pastedLinkUrl || !currentAppearance || isApplyingRef.current) {
@@ -234,17 +249,35 @@ const PasteDisplayAsMenuHorizontalView = ({
234
249
  pasteStartPos,
235
250
  pasteEndPos
236
251
  } = pasteRange;
237
- const cardAtPasteRange = getCardAtPasteRange(state, pasteStartPos, pasteEndPos);
252
+ const cardAtRange = getCardAtPasteRange(state, pasteStartPos, pasteEndPos, pastedLinkUrl);
253
+ if (cardAtRange !== null && cardAtRange !== void 0 && cardAtRange.isNativeEmbed) {
254
+ const nativeEmbedNode = state.doc.nodeAt(cardAtRange.pos);
255
+ // 'embed' is what a native embed already displays as, so there is nothing to apply.
256
+ if (nativeEmbedNode && appearance !== 'embed') {
257
+ var _api$analytics;
258
+ changeNativeEmbedAppearance({
259
+ editorView,
260
+ pos: cardAtRange.pos,
261
+ node: nativeEmbedNode,
262
+ appearance,
263
+ url: pastedLinkUrl,
264
+ editorAnalyticsApi: api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions
265
+ });
266
+ }
267
+ toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
268
+ isApplyingRef.current = false;
269
+ return;
270
+ }
238
271
  if (appearance === 'url') {
239
- if (cardAtPasteRange) {
240
- var _state$doc$nodeAt, _api$analytics;
241
- 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);
272
+ if (cardAtRange) {
273
+ var _state$doc$nodeAt, _api$analytics2;
274
+ changeSelectedCardToLink(pastedLinkUrl, pastedLinkUrl, true, (_state$doc$nodeAt = state.doc.nodeAt(cardAtRange.pos)) !== null && _state$doc$nodeAt !== void 0 ? _state$doc$nodeAt : undefined, cardAtRange.pos, api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions)(state, dispatch, editorView);
242
275
  }
243
276
  toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
244
277
  isApplyingRef.current = false;
245
278
  return;
246
279
  }
247
- const targetPos = cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.pos;
280
+ const targetPos = cardAtRange === null || cardAtRange === void 0 ? void 0 : cardAtRange.pos;
248
281
  const targetNodeAttrs = targetPos === undefined ? undefined : (_state$doc$nodeAt2 = state.doc.nodeAt(targetPos)) === null || _state$doc$nodeAt2 === void 0 ? void 0 : _state$doc$nodeAt2.attrs;
249
282
  const targetNodeUrl = (_targetNodeAttrs$url = targetNodeAttrs === null || targetNodeAttrs === void 0 ? void 0 : targetNodeAttrs.url) !== null && _targetNodeAttrs$url !== void 0 ? _targetNodeAttrs$url : targetNodeAttrs === null || targetNodeAttrs === void 0 ? void 0 : (_targetNodeAttrs$data = targetNodeAttrs.data) === null || _targetNodeAttrs$data === void 0 ? void 0 : _targetNodeAttrs$data.url;
250
283
  const isRecoveredAdjacentPastedCard = expValEqualsNoExposure('confluence_editor_paste_3p_link_actions_menu', 'isEnabled', true) && targetPos === pasteStartPos - 1 && pasteStartPos === pasteEndPos && targetNodeUrl === pastedLinkUrl;
@@ -268,9 +301,9 @@ const PasteDisplayAsMenuHorizontalView = ({
268
301
  targetPos
269
302
  });
270
303
  frameRef.current = requestAnimationFrame(() => {
271
- var _api$analytics2;
304
+ var _api$analytics3;
272
305
  frameRef.current = null;
273
- 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);
306
+ setSelectedCardAppearance(appearance, api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions)(editorView.state, editorView.dispatch, editorView);
274
307
  toolbarDropdownMenu === null || toolbarDropdownMenu === void 0 ? void 0 : toolbarDropdownMenu.closeMenu(null);
275
308
  isApplyingRef.current = false;
276
309
  });
@@ -294,26 +327,37 @@ const PasteDisplayAsMenuHorizontalView = ({
294
327
  const isSmartLinkConvertible = hasResolvedSmartLinkData;
295
328
  const isBlockSupportedFromSelection = allowBlockCards && blockCardNodeType && isSupportedInParent(editorView.state, Fragment.from(blockCardNodeType.createChecked({})), undefined);
296
329
  const isEmbedSupportedFromSelection = allowEmbeds && preview && embedCardNodeType && isSupportedInParent(editorView.state, Fragment.from(embedCardNodeType.createChecked({})), undefined);
297
- const isBlockSupported = Boolean(isBlockSupportedFromAppearanceContext || isBlockSupportedFromSelection);
298
- const isEmbedSupported = Boolean(isEmbedSupportedFromAppearanceContext || isEmbedSupportedFromSelection);
330
+ // A native embed is replaced in place, so support depends on what its own position
331
+ // accepts rather than on the current selection or on an embed preview being available.
332
+ const nativeEmbedPos = isNativeEmbedPaste ? cardAtPasteRange === null || cardAtPasteRange === void 0 ? void 0 : cardAtPasteRange.pos : undefined;
333
+ const isSupportedForNativeEmbed = appearance => nativeEmbedPos !== undefined && isNativeEmbedAppearanceSupported({
334
+ editorView,
335
+ pos: nativeEmbedPos,
336
+ appearance
337
+ });
338
+ const isInlineSupported = isNativeEmbedPaste ? isSupportedForNativeEmbed('inline') : true;
339
+ const isBlockSupported = isNativeEmbedPaste ? allowBlockCards && isSupportedForNativeEmbed('block') : Boolean(isBlockSupportedFromAppearanceContext || isBlockSupportedFromSelection);
340
+ const isEmbedSupported =
341
+ // Whatever was pasted already displays this way, so the option must stay selectable.
342
+ currentAppearance === 'embed' || Boolean(isEmbedSupportedFromAppearanceContext || isEmbedSupportedFromSelection);
299
343
  return /*#__PURE__*/React.createElement(Flex, {
300
344
  xcss: styles.appearanceBox,
301
345
  gap: "space.050"
302
346
  }, /*#__PURE__*/React.createElement(AppearanceOptionIconButton, {
303
347
  appearance: "url",
304
348
  currentAppearance: currentAppearance,
305
- isDisabled: false,
349
+ isDisabled: isNativeEmbedPaste && !isSupportedForNativeEmbed('url'),
306
350
  label: intl.formatMessage(appearancePropsMap.url.title),
307
351
  Icon: MinusIcon,
308
352
  onClick: handleClick('url')
309
353
  }), /*#__PURE__*/React.createElement(InlineAppearanceIconButton, {
310
354
  currentAppearance: currentAppearance,
311
- isDisabled: !isSmartLinkConvertible,
355
+ isDisabled: isNativeEmbedPaste ? !isInlineSupported : !isSmartLinkConvertible,
312
356
  label: intl.formatMessage(appearancePropsMap.inline.title),
313
357
  onClick: handleClick('inline')
314
358
  }), /*#__PURE__*/React.createElement(BlockAppearanceIconButton, {
315
359
  currentAppearance: currentAppearance,
316
- isDisabled: !isSmartLinkConvertible || !isBlockSupported,
360
+ isDisabled: !isBlockSupported || !isNativeEmbedPaste && !isSmartLinkConvertible,
317
361
  label: intl.formatMessage(appearancePropsMap.block.title),
318
362
  onClick: handleClick('block')
319
363
  }), /*#__PURE__*/React.createElement(EmbedAppearanceIconButton, {