@atlaskit/editor-plugin-paste 31.1.8 → 31.2.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,19 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 31.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`960570244e4c8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/960570244e4c8) -
8
+ Keep the text of a partially copied panel in the destination table cell, table header or expand
9
+ instead of inserting a new panel after it, behind platform_editor_paste_panel_c1_into_container
10
+
11
+ ### Patch Changes
12
+
13
+ - [`4f6171751461e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4f6171751461e) -
14
+ Clean up experiment `platform_editor_paste_autoconvert_robustness`
15
+ - Updated dependencies
16
+
3
17
  ## 31.1.8
4
18
 
5
19
  ### Patch Changes
@@ -381,6 +381,9 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
381
381
  var _view$state$selection, _view$state$selection2;
382
382
  var destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
383
383
  slice = (0, _handlers.applyContainerNodeTransformToSlice)(slice, schema, destinationParentType);
384
+ if ((0, _fg.fg)('platform_editor_paste_panel_c1_into_container')) {
385
+ slice = (0, _handlers.downgradePanelC1ForDestination)(slice, schema, view.state.selection.$from);
386
+ }
384
387
  }
385
388
  slice = (0, _handleVSCodeBlock.handleVSCodeBlock)({
386
389
  state: state,
@@ -8,6 +8,7 @@ exports.applyContainerNodeTransformToSlice = applyContainerNodeTransformToSlice;
8
8
  exports.checkIfSelectionInNestedList = checkIfSelectionInNestedList;
9
9
  exports.checkTaskListInList = checkTaskListInList;
10
10
  exports.doesSelectionWhichStartsOrEndsInListContainEntireList = void 0;
11
+ exports.downgradePanelC1ForDestination = downgradePanelC1ForDestination;
11
12
  exports.flattenNestedListInSlice = flattenNestedListInSlice;
12
13
  exports.handleCodeBlock = handleCodeBlock;
13
14
  exports.handleExpandPaste = handleExpandPaste;
@@ -52,7 +53,6 @@ var _model = require("@atlaskit/editor-prosemirror/model");
52
53
  var _state = require("@atlaskit/editor-prosemirror/state");
53
54
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
54
55
  var _replaceTable = require("@atlaskit/editor-tables/utils/replace-table");
55
- var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
56
56
  var _fg = require("@atlaskit/platform-feature-flags/fg");
57
57
  var _closeHistory = require("@atlaskit/prosemirror-history/closeHistory");
58
58
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
@@ -618,6 +618,15 @@ function handlePastePreservingMarks(slice, queueCardsFromChangedTr) {
618
618
  function getSmartLinkAdf(_x, _x2, _x3) {
619
619
  return _getSmartLinkAdf.apply(this, arguments);
620
620
  }
621
+ /**
622
+ * Inserts the pasted content and then replaces it with `macro` as a separate transaction.
623
+ *
624
+ * The range the macro replaces is derived from the steps that were actually applied
625
+ * rather than from `slice.size`. `slice.size` is the slice's own measure and does not
626
+ * account for ProseMirror's fitting behaviour in `replaceRange`/`replaceSelection`, so
627
+ * the two can disagree — and when they do the macro would be inserted without removing
628
+ * the text it should replace, leaving both the pasted text and the macro in the document.
629
+ */
621
630
  function _getSmartLinkAdf() {
622
631
  _getSmartLinkAdf = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(text, type, cardOptions) {
623
632
  var provider;
@@ -646,46 +655,7 @@ function _getSmartLinkAdf() {
646
655
  }));
647
656
  return _getSmartLinkAdf.apply(this, arguments);
648
657
  }
649
- function insertAutoMacroOld(slice, macro, view, from, to) {
650
- if (view) {
651
- // insert the text or linkified/md-converted clipboard data
652
- var selection = view.state.tr.selection;
653
- var tr;
654
- var before;
655
- if (typeof from === 'number' && typeof to === 'number') {
656
- tr = view.state.tr.replaceRange(from, to, slice);
657
- before = tr.mapping.map(from, -1);
658
- } else {
659
- tr = view.state.tr.replaceSelection(slice);
660
- before = tr.mapping.map(selection.from, -1);
661
- }
662
- view.dispatch(tr);
663
-
664
- // replace the text with the macro as a separate transaction
665
- // so the autoconversion generates 2 undo steps
666
- var macroTr = (0, _closeHistory.closeHistory)(view.state.tr).replaceRangeWith(before, before + slice.size, macro).scrollIntoView();
667
- (0, _card.addLinkMetadata)(view.state.selection, macroTr, {
668
- inputMethod: _enums.INPUT_METHOD.CLIPBOARD,
669
- cardAction: 'AUTO_CONVERT'
670
- });
671
- view.dispatch(macroTr);
672
- return true;
673
- }
674
- return false;
675
- }
676
-
677
- /**
678
- * Replacement for `insertAutoMacroOld` behind
679
- * `platform_editor_paste_autoconvert_robustness`.
680
- *
681
- * Identical in shape, except that the range the macro replaces is derived from the steps
682
- * that were actually applied rather than from `slice.size`. `slice.size` is the slice's
683
- * own measure and does not account for ProseMirror's fitting behaviour in
684
- * `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
685
- * is inserted without removing the text it should replace, leaving both the pasted text
686
- * and the macro in the document.
687
- */
688
- function insertAutoMacroNew(slice, macro, view, from, to) {
658
+ function insertAutoMacro(slice, macro, view, from, to) {
689
659
  if (!view) {
690
660
  return false;
691
661
  }
@@ -715,9 +685,6 @@ function insertAutoMacroNew(slice, macro, view, from, to) {
715
685
  view.dispatch(macroTr);
716
686
  return true;
717
687
  }
718
- function insertAutoMacro(slice, macro, view, from, to) {
719
- return (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
720
- }
721
688
 
722
689
  /**
723
690
  * Locates the range currently occupied by a link that we pasted earlier.
@@ -868,45 +835,27 @@ function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, runMacroAu
868
835
  var trackingId = (0, _uuid.v4)();
869
836
  var trackingFrom = "handleMacroAutoConvert-from-".concat(trackingId);
870
837
  var trackingTo = "handleMacroAutoConvert-to-".concat(trackingId);
871
- if ((0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_paste_autoconvert_robustness')) {
872
- // Insert the pasted content straight away. The smart link provider is only
873
- // consulted to decide whether the inserted link should later be swapped for
874
- // a macro, so it does not need to gate the insert — otherwise every
875
- // autoconvertible paste appears to do nothing until a network round trip
876
- // completes.
877
- var insertedRange;
878
- handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
879
- insertedRange = range;
880
- })(state, dispatch);
881
- if (!insertedRange) {
882
- return true;
883
- }
884
- (0, _commands.startTrackingPastedMacroPositions)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
885
- getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
886
- // Smart links could not resolve the URL, so fall back to the macro.
887
- // we use view.state rather than state because state becomes a stale
888
- // state reference after getSmartLinkAdf's async work
889
- var _getPastePluginState = (0, _pluginFactory.getPluginState)(view.state),
890
- pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
891
- replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
892
- }).finally(function () {
893
- (0, _commands.stopTrackingPastedMacroPositions)([trackingFrom, trackingTo])(view.state, dispatch);
894
- });
838
+
839
+ // Insert the pasted content straight away. The smart link provider is only
840
+ // consulted to decide whether the inserted link should later be swapped for
841
+ // a macro, so it does not need to gate the insert — otherwise every
842
+ // autoconvertible paste appears to do nothing until a network round trip
843
+ // completes.
844
+ var insertedRange;
845
+ handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
846
+ insertedRange = range;
847
+ })(state, dispatch);
848
+ if (!insertedRange) {
895
849
  return true;
896
850
  }
897
- (0, _commands.startTrackingPastedMacroPositions)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, trackingFrom, state.selection.from), trackingTo, state.selection.to))(state, dispatch);
898
- getSmartLinkAdf(text, 'inline', cardsOptions).then(function () {
851
+ (0, _commands.startTrackingPastedMacroPositions)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
852
+ getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
853
+ // Smart links could not resolve the URL, so fall back to the macro.
899
854
  // we use view.state rather than state because state becomes a stale
900
855
  // state reference after getSmartLinkAdf's async work
901
- var _getPastePluginState2 = (0, _pluginFactory.getPluginState)(view.state),
902
- pastedMacroPositions = _getPastePluginState2.pastedMacroPositions;
903
- if (dispatch) {
904
- handleMarkdown(slice, queueCardsFromChangedTr, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo])(view.state, dispatch);
905
- }
906
- }).catch(function () {
907
- var _getPastePluginState3 = (0, _pluginFactory.getPluginState)(view.state),
908
- pastedMacroPositions = _getPastePluginState3.pastedMacroPositions;
909
- insertAutoMacro(slice, macro, view, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo]);
856
+ var _getPastePluginState = (0, _pluginFactory.getPluginState)(view.state),
857
+ pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
858
+ replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
910
859
  }).finally(function () {
911
860
  (0, _commands.stopTrackingPastedMacroPositions)([trackingFrom, trackingTo])(view.state, dispatch);
912
861
  });
@@ -1212,8 +1161,7 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
1212
1161
  }
1213
1162
 
1214
1163
  /**
1215
- * Variant of `handleMarkdown` used by macro auto-conversion behind
1216
- * `platform_editor_paste_autoconvert_robustness`.
1164
+ * Variant of `handleMarkdown` used by macro auto-conversion.
1217
1165
  *
1218
1166
  * Differs in two ways: the inserted range is derived from the steps that were applied
1219
1167
  * rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
@@ -1694,6 +1642,63 @@ function applyContainerNodeTransformToSlice(slice, schema, destinationParentType
1694
1642
  }
1695
1643
  }
1696
1644
 
1645
+ /**
1646
+ * Finds the type of the nearest ancestor of the paste position that can hold the panel in either
1647
+ * of its forms. That is the node a block-level panel would be placed into, which is not always the
1648
+ * direct parent of the cursor's textblock (e.g. a blockquote inside a table cell).
1649
+ */
1650
+ function findNearestPanelHolderType($from, panelAsC1, panelAsPlain) {
1651
+ var panelAsC1Content = _model.Fragment.from(panelAsC1);
1652
+ var panelAsPlainContent = _model.Fragment.from(panelAsPlain);
1653
+ for (var depth = $from.depth; depth >= 0; depth--) {
1654
+ var ancestorType = $from.node(depth).type;
1655
+ if (ancestorType.validContent(panelAsC1Content) || ancestorType.validContent(panelAsPlainContent)) {
1656
+ return ancestorType;
1657
+ }
1658
+ }
1659
+ return undefined;
1660
+ }
1661
+
1662
+ /**
1663
+ * Turns top-level `panel_c1` children of the slice back into `panel` when the nearest ancestor of
1664
+ * the paste position that can hold a panel accepts `panel` but not `panel_c1`. Only panels whose
1665
+ * content is valid for a plain `panel` are changed, that is content without the containers only
1666
+ * `panel_c1` allows: tables (`platform_editor_nest_table_in_panel`), and expands, nested panels and
1667
+ * other containers when `platform_editor_nest_container_in_panel` is on.
1668
+ *
1669
+ * A partial copy from a top-level panel is re-wrapped as `panel_c1` from the clipboard context, and
1670
+ * a wrapper the destination cannot hold makes the paste insert the panel after the container (e.g.
1671
+ * below a table) instead of pasting the text where the cursor is.
1672
+ */
1673
+ function downgradePanelC1ForDestination(slice, schema, $from) {
1674
+ var _schema$nodes4 = schema.nodes,
1675
+ panel = _schema$nodes4.panel,
1676
+ panel_c1 = _schema$nodes4.panel_c1;
1677
+ if (!panel || !panel_c1) {
1678
+ return slice;
1679
+ }
1680
+ var isDowngraded = false;
1681
+ var children = [];
1682
+ slice.content.forEach(function (child) {
1683
+ if (child.type !== panel_c1 || !panel.validContent(child.content)) {
1684
+ children.push(child);
1685
+ return;
1686
+ }
1687
+ var plainPanel = panel.create(child.attrs, child.content, child.marks.filter(function (mark) {
1688
+ return panel.allowsMarkType(mark.type);
1689
+ }));
1690
+ var holderType = findNearestPanelHolderType($from, child, plainPanel);
1691
+ var holderOnlyAcceptsPlainPanel = !!holderType && !holderType.validContent(_model.Fragment.from(child)) && holderType.validContent(_model.Fragment.from(plainPanel));
1692
+ if (holderOnlyAcceptsPlainPanel) {
1693
+ isDowngraded = true;
1694
+ children.push(plainPanel);
1695
+ return;
1696
+ }
1697
+ children.push(child);
1698
+ });
1699
+ return isDowngraded ? new _model.Slice(_model.Fragment.fromArray(children), slice.openStart, slice.openEnd) : slice;
1700
+ }
1701
+
1697
1702
  /*
1698
1703
  * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1699
1704
  * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
@@ -41,7 +41,7 @@ import { createPluginState, pluginKey as stateKey } from './plugin-factory';
41
41
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
42
42
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
43
43
  import { getMarkdownSliceViaGfm } from './util/get-markdown-slice';
44
- import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
44
+ import { applyContainerNodeTransformToSlice, downgradePanelC1ForDestination, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
45
45
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
46
46
  import { handleSyncBlocksPaste } from './util/sync-block';
47
47
  import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
@@ -340,6 +340,9 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
340
340
  var _view$state$selection, _view$state$selection2;
341
341
  const destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
342
342
  slice = applyContainerNodeTransformToSlice(slice, schema, destinationParentType);
343
+ if (fg('platform_editor_paste_panel_c1_into_container')) {
344
+ slice = downgradePanelC1ForDestination(slice, schema, view.state.selection.$from);
345
+ }
343
346
  }
344
347
  slice = handleVSCodeBlock({
345
348
  state,
@@ -18,7 +18,6 @@ import { Fragment, Node as PMNode, Slice } from '@atlaskit/editor-prosemirror/mo
18
18
  import { AllSelection, NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
19
19
  import { canInsert, contains, findParentNodeOfType, findParentNodeOfTypeClosestToPos, hasParentNode, hasParentNodeOfType, safeInsert } from '@atlaskit/editor-prosemirror/utils';
20
20
  import { replaceSelectedTable } from '@atlaskit/editor-tables/utils/replace-table';
21
- import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
22
21
  import { fg } from '@atlaskit/platform-feature-flags/fg';
23
22
  import { closeHistory } from '@atlaskit/prosemirror-history/closeHistory';
24
23
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
@@ -612,46 +611,17 @@ async function getSmartLinkAdf(text, type, cardOptions) {
612
611
  const provider = await cardOptions.provider;
613
612
  return await provider.resolve(text, type);
614
613
  }
615
- function insertAutoMacroOld(slice, macro, view, from, to) {
616
- if (view) {
617
- // insert the text or linkified/md-converted clipboard data
618
- const selection = view.state.tr.selection;
619
- let tr;
620
- let before;
621
- if (typeof from === 'number' && typeof to === 'number') {
622
- tr = view.state.tr.replaceRange(from, to, slice);
623
- before = tr.mapping.map(from, -1);
624
- } else {
625
- tr = view.state.tr.replaceSelection(slice);
626
- before = tr.mapping.map(selection.from, -1);
627
- }
628
- view.dispatch(tr);
629
-
630
- // replace the text with the macro as a separate transaction
631
- // so the autoconversion generates 2 undo steps
632
- const macroTr = closeHistory(view.state.tr).replaceRangeWith(before, before + slice.size, macro).scrollIntoView();
633
- addLinkMetadata(view.state.selection, macroTr, {
634
- inputMethod: INPUT_METHOD.CLIPBOARD,
635
- cardAction: 'AUTO_CONVERT'
636
- });
637
- view.dispatch(macroTr);
638
- return true;
639
- }
640
- return false;
641
- }
642
614
 
643
615
  /**
644
- * Replacement for `insertAutoMacroOld` behind
645
- * `platform_editor_paste_autoconvert_robustness`.
616
+ * Inserts the pasted content and then replaces it with `macro` as a separate transaction.
646
617
  *
647
- * Identical in shape, except that the range the macro replaces is derived from the steps
648
- * that were actually applied rather than from `slice.size`. `slice.size` is the slice's
649
- * own measure and does not account for ProseMirror's fitting behaviour in
650
- * `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
651
- * is inserted without removing the text it should replace, leaving both the pasted text
652
- * and the macro in the document.
618
+ * The range the macro replaces is derived from the steps that were actually applied
619
+ * rather than from `slice.size`. `slice.size` is the slice's own measure and does not
620
+ * account for ProseMirror's fitting behaviour in `replaceRange`/`replaceSelection`, so
621
+ * the two can disagree — and when they do the macro would be inserted without removing
622
+ * the text it should replace, leaving both the pasted text and the macro in the document.
653
623
  */
654
- function insertAutoMacroNew(slice, macro, view, from, to) {
624
+ function insertAutoMacro(slice, macro, view, from, to) {
655
625
  if (!view) {
656
626
  return false;
657
627
  }
@@ -683,9 +653,6 @@ function insertAutoMacroNew(slice, macro, view, from, to) {
683
653
  view.dispatch(macroTr);
684
654
  return true;
685
655
  }
686
- function insertAutoMacro(slice, macro, view, from, to) {
687
- return isExperimentEnabled('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
688
- }
689
656
 
690
657
  /**
691
658
  * Locates the range currently occupied by a link that we pasted earlier.
@@ -838,54 +805,31 @@ export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, run
838
805
  const trackingId = uuid();
839
806
  const trackingFrom = `handleMacroAutoConvert-from-${trackingId}`;
840
807
  const trackingTo = `handleMacroAutoConvert-to-${trackingId}`;
841
- if (isExperimentEnabled('platform_editor_paste_autoconvert_robustness')) {
842
- // Insert the pasted content straight away. The smart link provider is only
843
- // consulted to decide whether the inserted link should later be swapped for
844
- // a macro, so it does not need to gate the insert — otherwise every
845
- // autoconvertible paste appears to do nothing until a network round trip
846
- // completes.
847
- let insertedRange;
848
- handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, range => {
849
- insertedRange = range;
850
- })(state, dispatch);
851
- if (!insertedRange) {
852
- return true;
853
- }
854
- startTrackingPastedMacroPositions({
855
- [trackingFrom]: insertedRange.from,
856
- [trackingTo]: insertedRange.to
857
- })(view.state, dispatch);
858
- getSmartLinkAdf(text, 'inline', cardsOptions).catch(() => {
859
- // Smart links could not resolve the URL, so fall back to the macro.
860
- // we use view.state rather than state because state becomes a stale
861
- // state reference after getSmartLinkAdf's async work
862
- const {
863
- pastedMacroPositions
864
- } = getPastePluginState(view.state);
865
- replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
866
- }).finally(() => {
867
- stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
868
- });
808
+
809
+ // Insert the pasted content straight away. The smart link provider is only
810
+ // consulted to decide whether the inserted link should later be swapped for
811
+ // a macro, so it does not need to gate the insert — otherwise every
812
+ // autoconvertible paste appears to do nothing until a network round trip
813
+ // completes.
814
+ let insertedRange;
815
+ handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, range => {
816
+ insertedRange = range;
817
+ })(state, dispatch);
818
+ if (!insertedRange) {
869
819
  return true;
870
820
  }
871
821
  startTrackingPastedMacroPositions({
872
- [trackingFrom]: state.selection.from,
873
- [trackingTo]: state.selection.to
874
- })(state, dispatch);
875
- getSmartLinkAdf(text, 'inline', cardsOptions).then(() => {
822
+ [trackingFrom]: insertedRange.from,
823
+ [trackingTo]: insertedRange.to
824
+ })(view.state, dispatch);
825
+ getSmartLinkAdf(text, 'inline', cardsOptions).catch(() => {
826
+ // Smart links could not resolve the URL, so fall back to the macro.
876
827
  // we use view.state rather than state because state becomes a stale
877
828
  // state reference after getSmartLinkAdf's async work
878
829
  const {
879
830
  pastedMacroPositions
880
831
  } = getPastePluginState(view.state);
881
- if (dispatch) {
882
- handleMarkdown(slice, queueCardsFromChangedTr, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo])(view.state, dispatch);
883
- }
884
- }).catch(() => {
885
- const {
886
- pastedMacroPositions
887
- } = getPastePluginState(view.state);
888
- insertAutoMacro(slice, macro, view, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo]);
832
+ replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
889
833
  }).finally(() => {
890
834
  stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
891
835
  });
@@ -1195,8 +1139,7 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
1195
1139
  }
1196
1140
 
1197
1141
  /**
1198
- * Variant of `handleMarkdown` used by macro auto-conversion behind
1199
- * `platform_editor_paste_autoconvert_robustness`.
1142
+ * Variant of `handleMarkdown` used by macro auto-conversion.
1200
1143
  *
1201
1144
  * Differs in two ways: the inserted range is derived from the steps that were applied
1202
1145
  * rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
@@ -1664,6 +1607,62 @@ export function applyContainerNodeTransformToSlice(slice, schema, destinationPar
1664
1607
  }
1665
1608
  }
1666
1609
 
1610
+ /**
1611
+ * Finds the type of the nearest ancestor of the paste position that can hold the panel in either
1612
+ * of its forms. That is the node a block-level panel would be placed into, which is not always the
1613
+ * direct parent of the cursor's textblock (e.g. a blockquote inside a table cell).
1614
+ */
1615
+ function findNearestPanelHolderType($from, panelAsC1, panelAsPlain) {
1616
+ const panelAsC1Content = Fragment.from(panelAsC1);
1617
+ const panelAsPlainContent = Fragment.from(panelAsPlain);
1618
+ for (let depth = $from.depth; depth >= 0; depth--) {
1619
+ const ancestorType = $from.node(depth).type;
1620
+ if (ancestorType.validContent(panelAsC1Content) || ancestorType.validContent(panelAsPlainContent)) {
1621
+ return ancestorType;
1622
+ }
1623
+ }
1624
+ return undefined;
1625
+ }
1626
+
1627
+ /**
1628
+ * Turns top-level `panel_c1` children of the slice back into `panel` when the nearest ancestor of
1629
+ * the paste position that can hold a panel accepts `panel` but not `panel_c1`. Only panels whose
1630
+ * content is valid for a plain `panel` are changed, that is content without the containers only
1631
+ * `panel_c1` allows: tables (`platform_editor_nest_table_in_panel`), and expands, nested panels and
1632
+ * other containers when `platform_editor_nest_container_in_panel` is on.
1633
+ *
1634
+ * A partial copy from a top-level panel is re-wrapped as `panel_c1` from the clipboard context, and
1635
+ * a wrapper the destination cannot hold makes the paste insert the panel after the container (e.g.
1636
+ * below a table) instead of pasting the text where the cursor is.
1637
+ */
1638
+ export function downgradePanelC1ForDestination(slice, schema, $from) {
1639
+ const {
1640
+ panel,
1641
+ panel_c1
1642
+ } = schema.nodes;
1643
+ if (!panel || !panel_c1) {
1644
+ return slice;
1645
+ }
1646
+ let isDowngraded = false;
1647
+ const children = [];
1648
+ slice.content.forEach(child => {
1649
+ if (child.type !== panel_c1 || !panel.validContent(child.content)) {
1650
+ children.push(child);
1651
+ return;
1652
+ }
1653
+ const plainPanel = panel.create(child.attrs, child.content, child.marks.filter(mark => panel.allowsMarkType(mark.type)));
1654
+ const holderType = findNearestPanelHolderType($from, child, plainPanel);
1655
+ const holderOnlyAcceptsPlainPanel = !!holderType && !holderType.validContent(Fragment.from(child)) && holderType.validContent(Fragment.from(plainPanel));
1656
+ if (holderOnlyAcceptsPlainPanel) {
1657
+ isDowngraded = true;
1658
+ children.push(plainPanel);
1659
+ return;
1660
+ }
1661
+ children.push(child);
1662
+ });
1663
+ return isDowngraded ? new Slice(Fragment.fromArray(children), slice.openStart, slice.openEnd) : slice;
1664
+ }
1665
+
1667
1666
  /*
1668
1667
  * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1669
1668
  * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
@@ -46,7 +46,7 @@ import { createPluginState, pluginKey as stateKey } from './plugin-factory';
46
46
  import { escapeBackslashAndLinksExceptCodeBlock, getPasteSource, htmlContainsSingleFile, htmlHasInvalidLinkTags, isPastedFromExcel, isPastedFromWord, removeDuplicateInvalidLinks, transformUnsupportedBlockCardToInline } from './util';
47
47
  import { handleVSCodeBlock } from './util/edge-cases/handleVSCodeBlock';
48
48
  import { getMarkdownSliceViaGfm } from './util/get-markdown-slice';
49
- import { applyContainerNodeTransformToSlice, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
49
+ import { applyContainerNodeTransformToSlice, downgradePanelC1ForDestination, splitTablesOutOfPanelHtml, handleMacroAutoConvert, handleMention, handleParagraphBlockMarks, handlePasteExpand, handleTableContentPasteInBodiedExtension } from './util/handlers';
50
50
  import { normalizePastedCodeBlockAttrs } from './util/normalize-pasted-code-block-attrs';
51
51
  import { handleSyncBlocksPaste } from './util/sync-block';
52
52
  import { htmlHasIncompleteTable, isPastedFromTinyMCEConfluence, tryRebuildCompleteTableHtml } from './util/tinyMCE';
@@ -373,6 +373,9 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
373
373
  var _view$state$selection, _view$state$selection2;
374
374
  var destinationParentType = (_view$state$selection = (_view$state$selection2 = view.state.selection.$from.node(-1)) === null || _view$state$selection2 === void 0 ? void 0 : _view$state$selection2.type.name) !== null && _view$state$selection !== void 0 ? _view$state$selection : view.state.doc.type.name;
375
375
  slice = applyContainerNodeTransformToSlice(slice, schema, destinationParentType);
376
+ if (fg('platform_editor_paste_panel_c1_into_container')) {
377
+ slice = downgradePanelC1ForDestination(slice, schema, view.state.selection.$from);
378
+ }
376
379
  }
377
380
  slice = handleVSCodeBlock({
378
381
  state: state,
@@ -27,7 +27,6 @@ import { Fragment, Node as PMNode, Slice } from '@atlaskit/editor-prosemirror/mo
27
27
  import { AllSelection, NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
28
28
  import { canInsert, contains, findParentNodeOfType, findParentNodeOfTypeClosestToPos, hasParentNode, hasParentNodeOfType, safeInsert } from '@atlaskit/editor-prosemirror/utils';
29
29
  import { replaceSelectedTable } from '@atlaskit/editor-tables/utils/replace-table';
30
- import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
31
30
  import { fg } from '@atlaskit/platform-feature-flags/fg';
32
31
  import { closeHistory } from '@atlaskit/prosemirror-history/closeHistory';
33
32
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
@@ -589,6 +588,15 @@ export function handlePastePreservingMarks(slice, queueCardsFromChangedTr) {
589
588
  function getSmartLinkAdf(_x, _x2, _x3) {
590
589
  return _getSmartLinkAdf.apply(this, arguments);
591
590
  }
591
+ /**
592
+ * Inserts the pasted content and then replaces it with `macro` as a separate transaction.
593
+ *
594
+ * The range the macro replaces is derived from the steps that were actually applied
595
+ * rather than from `slice.size`. `slice.size` is the slice's own measure and does not
596
+ * account for ProseMirror's fitting behaviour in `replaceRange`/`replaceSelection`, so
597
+ * the two can disagree — and when they do the macro would be inserted without removing
598
+ * the text it should replace, leaving both the pasted text and the macro in the document.
599
+ */
592
600
  function _getSmartLinkAdf() {
593
601
  _getSmartLinkAdf = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(text, type, cardOptions) {
594
602
  var provider;
@@ -617,46 +625,7 @@ function _getSmartLinkAdf() {
617
625
  }));
618
626
  return _getSmartLinkAdf.apply(this, arguments);
619
627
  }
620
- function insertAutoMacroOld(slice, macro, view, from, to) {
621
- if (view) {
622
- // insert the text or linkified/md-converted clipboard data
623
- var selection = view.state.tr.selection;
624
- var tr;
625
- var before;
626
- if (typeof from === 'number' && typeof to === 'number') {
627
- tr = view.state.tr.replaceRange(from, to, slice);
628
- before = tr.mapping.map(from, -1);
629
- } else {
630
- tr = view.state.tr.replaceSelection(slice);
631
- before = tr.mapping.map(selection.from, -1);
632
- }
633
- view.dispatch(tr);
634
-
635
- // replace the text with the macro as a separate transaction
636
- // so the autoconversion generates 2 undo steps
637
- var macroTr = closeHistory(view.state.tr).replaceRangeWith(before, before + slice.size, macro).scrollIntoView();
638
- addLinkMetadata(view.state.selection, macroTr, {
639
- inputMethod: INPUT_METHOD.CLIPBOARD,
640
- cardAction: 'AUTO_CONVERT'
641
- });
642
- view.dispatch(macroTr);
643
- return true;
644
- }
645
- return false;
646
- }
647
-
648
- /**
649
- * Replacement for `insertAutoMacroOld` behind
650
- * `platform_editor_paste_autoconvert_robustness`.
651
- *
652
- * Identical in shape, except that the range the macro replaces is derived from the steps
653
- * that were actually applied rather than from `slice.size`. `slice.size` is the slice's
654
- * own measure and does not account for ProseMirror's fitting behaviour in
655
- * `replaceRange`/`replaceSelection`, so the two can disagree — and when they do the macro
656
- * is inserted without removing the text it should replace, leaving both the pasted text
657
- * and the macro in the document.
658
- */
659
- function insertAutoMacroNew(slice, macro, view, from, to) {
628
+ function insertAutoMacro(slice, macro, view, from, to) {
660
629
  if (!view) {
661
630
  return false;
662
631
  }
@@ -686,9 +655,6 @@ function insertAutoMacroNew(slice, macro, view, from, to) {
686
655
  view.dispatch(macroTr);
687
656
  return true;
688
657
  }
689
- function insertAutoMacro(slice, macro, view, from, to) {
690
- return isExperimentEnabled('platform_editor_paste_autoconvert_robustness') ? insertAutoMacroNew(slice, macro, view, from, to) : insertAutoMacroOld(slice, macro, view, from, to);
691
- }
692
658
 
693
659
  /**
694
660
  * Locates the range currently occupied by a link that we pasted earlier.
@@ -839,45 +805,27 @@ export function handleMacroAutoConvert(text, slice, queueCardsFromChangedTr, run
839
805
  var trackingId = uuid();
840
806
  var trackingFrom = "handleMacroAutoConvert-from-".concat(trackingId);
841
807
  var trackingTo = "handleMacroAutoConvert-to-".concat(trackingId);
842
- if (isExperimentEnabled('platform_editor_paste_autoconvert_robustness')) {
843
- // Insert the pasted content straight away. The smart link provider is only
844
- // consulted to decide whether the inserted link should later be swapped for
845
- // a macro, so it does not need to gate the insert — otherwise every
846
- // autoconvertible paste appears to do nothing until a network round trip
847
- // completes.
848
- var insertedRange;
849
- handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
850
- insertedRange = range;
851
- })(state, dispatch);
852
- if (!insertedRange) {
853
- return true;
854
- }
855
- startTrackingPastedMacroPositions(_defineProperty(_defineProperty({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
856
- getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
857
- // Smart links could not resolve the URL, so fall back to the macro.
858
- // we use view.state rather than state because state becomes a stale
859
- // state reference after getSmartLinkAdf's async work
860
- var _getPastePluginState = getPastePluginState(view.state),
861
- pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
862
- replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
863
- }).finally(function () {
864
- stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
865
- });
808
+
809
+ // Insert the pasted content straight away. The smart link provider is only
810
+ // consulted to decide whether the inserted link should later be swapped for
811
+ // a macro, so it does not need to gate the insert — otherwise every
812
+ // autoconvertible paste appears to do nothing until a network round trip
813
+ // completes.
814
+ var insertedRange;
815
+ handleMarkdownWithInsertedRange(slice, queueCardsFromChangedTr, function (range) {
816
+ insertedRange = range;
817
+ })(state, dispatch);
818
+ if (!insertedRange) {
866
819
  return true;
867
820
  }
868
- startTrackingPastedMacroPositions(_defineProperty(_defineProperty({}, trackingFrom, state.selection.from), trackingTo, state.selection.to))(state, dispatch);
869
- getSmartLinkAdf(text, 'inline', cardsOptions).then(function () {
821
+ startTrackingPastedMacroPositions(_defineProperty(_defineProperty({}, trackingFrom, insertedRange.from), trackingTo, insertedRange.to))(view.state, dispatch);
822
+ getSmartLinkAdf(text, 'inline', cardsOptions).catch(function () {
823
+ // Smart links could not resolve the URL, so fall back to the macro.
870
824
  // we use view.state rather than state because state becomes a stale
871
825
  // state reference after getSmartLinkAdf's async work
872
- var _getPastePluginState2 = getPastePluginState(view.state),
873
- pastedMacroPositions = _getPastePluginState2.pastedMacroPositions;
874
- if (dispatch) {
875
- handleMarkdown(slice, queueCardsFromChangedTr, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo])(view.state, dispatch);
876
- }
877
- }).catch(function () {
878
- var _getPastePluginState3 = getPastePluginState(view.state),
879
- pastedMacroPositions = _getPastePluginState3.pastedMacroPositions;
880
- insertAutoMacro(slice, macro, view, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo]);
826
+ var _getPastePluginState = getPastePluginState(view.state),
827
+ pastedMacroPositions = _getPastePluginState.pastedMacroPositions;
828
+ replacePastedLinkWithMacro(view, autoConvertMacro, text, pastedMacroPositions[trackingFrom], pastedMacroPositions[trackingTo], editorAnalyticsAPI);
881
829
  }).finally(function () {
882
830
  stopTrackingPastedMacroPositions([trackingFrom, trackingTo])(view.state, dispatch);
883
831
  });
@@ -1183,8 +1131,7 @@ function resolveParagraphMarks(marks, destinationBlockMarkAttrs, shouldNormalize
1183
1131
  }
1184
1132
 
1185
1133
  /**
1186
- * Variant of `handleMarkdown` used by macro auto-conversion behind
1187
- * `platform_editor_paste_autoconvert_robustness`.
1134
+ * Variant of `handleMarkdown` used by macro auto-conversion.
1188
1135
  *
1189
1136
  * Differs in two ways: the inserted range is derived from the steps that were applied
1190
1137
  * rather than from `markdownSlice.size`, which does not account for ProseMirror's fitting
@@ -1665,6 +1612,63 @@ export function applyContainerNodeTransformToSlice(slice, schema, destinationPar
1665
1612
  }
1666
1613
  }
1667
1614
 
1615
+ /**
1616
+ * Finds the type of the nearest ancestor of the paste position that can hold the panel in either
1617
+ * of its forms. That is the node a block-level panel would be placed into, which is not always the
1618
+ * direct parent of the cursor's textblock (e.g. a blockquote inside a table cell).
1619
+ */
1620
+ function findNearestPanelHolderType($from, panelAsC1, panelAsPlain) {
1621
+ var panelAsC1Content = Fragment.from(panelAsC1);
1622
+ var panelAsPlainContent = Fragment.from(panelAsPlain);
1623
+ for (var depth = $from.depth; depth >= 0; depth--) {
1624
+ var ancestorType = $from.node(depth).type;
1625
+ if (ancestorType.validContent(panelAsC1Content) || ancestorType.validContent(panelAsPlainContent)) {
1626
+ return ancestorType;
1627
+ }
1628
+ }
1629
+ return undefined;
1630
+ }
1631
+
1632
+ /**
1633
+ * Turns top-level `panel_c1` children of the slice back into `panel` when the nearest ancestor of
1634
+ * the paste position that can hold a panel accepts `panel` but not `panel_c1`. Only panels whose
1635
+ * content is valid for a plain `panel` are changed, that is content without the containers only
1636
+ * `panel_c1` allows: tables (`platform_editor_nest_table_in_panel`), and expands, nested panels and
1637
+ * other containers when `platform_editor_nest_container_in_panel` is on.
1638
+ *
1639
+ * A partial copy from a top-level panel is re-wrapped as `panel_c1` from the clipboard context, and
1640
+ * a wrapper the destination cannot hold makes the paste insert the panel after the container (e.g.
1641
+ * below a table) instead of pasting the text where the cursor is.
1642
+ */
1643
+ export function downgradePanelC1ForDestination(slice, schema, $from) {
1644
+ var _schema$nodes4 = schema.nodes,
1645
+ panel = _schema$nodes4.panel,
1646
+ panel_c1 = _schema$nodes4.panel_c1;
1647
+ if (!panel || !panel_c1) {
1648
+ return slice;
1649
+ }
1650
+ var isDowngraded = false;
1651
+ var children = [];
1652
+ slice.content.forEach(function (child) {
1653
+ if (child.type !== panel_c1 || !panel.validContent(child.content)) {
1654
+ children.push(child);
1655
+ return;
1656
+ }
1657
+ var plainPanel = panel.create(child.attrs, child.content, child.marks.filter(function (mark) {
1658
+ return panel.allowsMarkType(mark.type);
1659
+ }));
1660
+ var holderType = findNearestPanelHolderType($from, child, plainPanel);
1661
+ var holderOnlyAcceptsPlainPanel = !!holderType && !holderType.validContent(Fragment.from(child)) && holderType.validContent(Fragment.from(plainPanel));
1662
+ if (holderOnlyAcceptsPlainPanel) {
1663
+ isDowngraded = true;
1664
+ children.push(plainPanel);
1665
+ return;
1666
+ }
1667
+ children.push(child);
1668
+ });
1669
+ return isDowngraded ? new Slice(Fragment.fromArray(children), slice.openStart, slice.openEnd) : slice;
1670
+ }
1671
+
1668
1672
  /*
1669
1673
  * When platform_editor_nest_table_in_panel is OFF, move table elements out of panel divs in the
1670
1674
  * clipboard HTML before ProseMirror parses it. This prevents the panel schema from
@@ -7,7 +7,7 @@ import type { Command } from '@atlaskit/editor-common/types/command';
7
7
  import type { RunMacroAutoConvert } from '@atlaskit/editor-plugin-extension/extensionPluginType';
8
8
  import type { FindRootParentListNode } from '@atlaskit/editor-plugin-list/types';
9
9
  import type { InsertMediaAsMediaSingle } from '@atlaskit/editor-plugin-media/types';
10
- import type { Schema } from '@atlaskit/editor-prosemirror/model';
10
+ import type { ResolvedPos, Schema } from '@atlaskit/editor-prosemirror/model';
11
11
  import { Slice } from '@atlaskit/editor-prosemirror/model';
12
12
  import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
13
13
  export declare function handleMention(slice: Slice, schema: Schema): Slice;
@@ -61,4 +61,16 @@ export declare function checkTaskListInList(state: EditorState, slice: Slice): b
61
61
  export declare function checkIfSelectionInNestedList(state: EditorState): boolean;
62
62
  export declare function handlePasteExpand(slice: Slice): Slice;
63
63
  export declare function applyContainerNodeTransformToSlice(slice: Slice, schema: Schema, destinationParentType: string): Slice;
64
+ /**
65
+ * Turns top-level `panel_c1` children of the slice back into `panel` when the nearest ancestor of
66
+ * the paste position that can hold a panel accepts `panel` but not `panel_c1`. Only panels whose
67
+ * content is valid for a plain `panel` are changed, that is content without the containers only
68
+ * `panel_c1` allows: tables (`platform_editor_nest_table_in_panel`), and expands, nested panels and
69
+ * other containers when `platform_editor_nest_container_in_panel` is on.
70
+ *
71
+ * A partial copy from a top-level panel is re-wrapped as `panel_c1` from the clipboard context, and
72
+ * a wrapper the destination cannot hold makes the paste insert the panel after the container (e.g.
73
+ * below a table) instead of pasting the text where the cursor is.
74
+ */
75
+ export declare function downgradePanelC1ForDestination(slice: Slice, schema: Schema, $from: ResolvedPos): Slice;
64
76
  export declare function splitTablesOutOfPanelHtml(html: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "31.1.8",
3
+ "version": "31.2.0",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -77,12 +77,9 @@
77
77
  },
78
78
  "platform_editor_paste_strip_invalid_block_marks": {
79
79
  "type": "boolean"
80
- }
81
- },
82
- "platform-experiments": {
83
- "platform_editor_paste_autoconvert_robustness": {
84
- "param": "isEnabled",
85
- "defaultValue": false
80
+ },
81
+ "platform_editor_paste_panel_c1_into_container": {
82
+ "type": "boolean"
86
83
  }
87
84
  }
88
85
  }