@atlaskit/editor-plugin-paste 1.12.3 → 1.12.5

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 (32) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/cjs/handlers.js +16 -1
  3. package/dist/cjs/pm-plugins/analytics.js +2 -2
  4. package/dist/cjs/pm-plugins/main.js +7 -3
  5. package/dist/cjs/pm-plugins/move-analytics/commands.js +2 -1
  6. package/dist/cjs/pm-plugins/move-analytics/plugin.js +34 -13
  7. package/dist/cjs/pm-plugins/move-analytics/types.js +1 -0
  8. package/dist/cjs/pm-plugins/move-analytics/utils.js +42 -2
  9. package/dist/es2019/handlers.js +16 -1
  10. package/dist/es2019/pm-plugins/analytics.js +2 -2
  11. package/dist/es2019/pm-plugins/main.js +7 -3
  12. package/dist/es2019/pm-plugins/move-analytics/commands.js +2 -1
  13. package/dist/es2019/pm-plugins/move-analytics/plugin.js +31 -11
  14. package/dist/es2019/pm-plugins/move-analytics/types.js +1 -0
  15. package/dist/es2019/pm-plugins/move-analytics/utils.js +46 -2
  16. package/dist/esm/handlers.js +16 -1
  17. package/dist/esm/pm-plugins/analytics.js +2 -2
  18. package/dist/esm/pm-plugins/main.js +7 -3
  19. package/dist/esm/pm-plugins/move-analytics/commands.js +2 -1
  20. package/dist/esm/pm-plugins/move-analytics/plugin.js +34 -14
  21. package/dist/esm/pm-plugins/move-analytics/types.js +1 -0
  22. package/dist/esm/pm-plugins/move-analytics/utils.js +41 -2
  23. package/dist/types/handlers.d.ts +1 -1
  24. package/dist/types/pm-plugins/analytics.d.ts +1 -1
  25. package/dist/types/pm-plugins/move-analytics/types.d.ts +1 -0
  26. package/dist/types/pm-plugins/move-analytics/utils.d.ts +4 -0
  27. package/dist/types-ts4.5/handlers.d.ts +1 -1
  28. package/dist/types-ts4.5/pm-plugins/analytics.d.ts +1 -1
  29. package/dist/types-ts4.5/pm-plugins/move-analytics/types.d.ts +1 -0
  30. package/dist/types-ts4.5/pm-plugins/move-analytics/utils.d.ts +4 -0
  31. package/package.json +8 -4
  32. package/.eslintrc.js +0 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 1.12.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#143700](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/143700)
8
+ [`9ef9b7e3bc1d7`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/9ef9b7e3bc1d7) -
9
+ [ux] [ED-24868] Paste gating for codeblock and media in quotes.
10
+ - Updated dependencies
11
+
12
+ ## 1.12.4
13
+
14
+ ### Patch Changes
15
+
16
+ - [#142433](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/142433)
17
+ [`896303d4b4390`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/896303d4b4390) -
18
+ ED-24552 Update document moved event to fire when nested nodes are cut/paste into nodes and add
19
+ nodeDepth attributes
20
+ - Updated dependencies
21
+
3
22
  ## 1.12.3
4
23
 
5
24
  ### Patch Changes
@@ -41,6 +41,7 @@ var _utils3 = require("@atlaskit/editor-tables/utils");
41
41
  var _commands = require("./commands");
42
42
  var _edgeCases = require("./edge-cases");
43
43
  var _lists = require("./edge-cases/lists");
44
+ var _main = require("./pm-plugins/main");
44
45
  var _pluginFactory = require("./pm-plugins/plugin-factory");
45
46
  var _util = require("./util");
46
47
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
@@ -905,10 +906,11 @@ function flattenNestedListInSlice(slice) {
905
906
  var contentWithFlattenedList = slice.content.replaceChild(0, listToFlatten.type.createChecked(listToFlatten.attrs, leafListItems));
906
907
  return new _model.Slice(contentWithFlattenedList, slice.openEnd, slice.openEnd);
907
908
  }
908
- function handleRichText(slice, queueCardsFromChangedTr) {
909
+ function handleRichText(slice, queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported) {
909
910
  return function (state, dispatch) {
910
911
  var _slice$content, _slice$content2, _firstChildOfSlice$ty, _lastChildOfSlice$typ, _panelParentOverCurre;
911
912
  var _state$schema$nodes4 = state.schema.nodes,
913
+ blockquote = _state$schema$nodes4.blockquote,
912
914
  codeBlock = _state$schema$nodes4.codeBlock,
913
915
  heading = _state$schema$nodes4.heading,
914
916
  paragraph = _state$schema$nodes4.paragraph,
@@ -943,12 +945,25 @@ function handleRichText(slice, queueCardsFromChangedTr) {
943
945
  var noNeedForSafeInsert = selection.$to.node().type.validContent(slice.content) || textNodes.includes(selection.$to.node().type) && selectionParent.type.validContent(slice.content);
944
946
  var panelParentOverCurrentSelection = (0, _utils2.findParentNodeOfType)(panel)(tr.selection);
945
947
  var isTargetPanelEmpty = panelParentOverCurrentSelection && ((_panelParentOverCurre = panelParentOverCurrentSelection.node) === null || _panelParentOverCurre === void 0 ? void 0 : _panelParentOverCurre.content.size) === 2;
948
+ var sliceContainsCodeblockOrMedia = false;
949
+ var codeBlockAndMediaNodes = ['codeBlock', 'mediaSingle', 'mediaGroup'];
950
+ slice.content.forEach(function (child) {
951
+ if (codeBlockAndMediaNodes.includes(child.type.name)) {
952
+ sliceContainsCodeblockOrMedia = true;
953
+ }
954
+ });
955
+ var blockquoteNode = (0, _utils2.findParentNodeOfType)(blockquote)(tr.selection);
946
956
  if (!isSliceContentTaskListNodes && (isSliceContentListNodes || isTargetPanelEmpty)) {
947
957
  (0, _edgeCases.insertSliceForLists)({
948
958
  tr: tr,
949
959
  slice: slice,
950
960
  schema: schema
951
961
  });
962
+ } else if (
963
+ // If nesting media or codeblock in blockquote is not supported
964
+ // we want to insert slice after the blockquote.
965
+ (0, _main.isInsideBlockQuote)(state) && sliceContainsCodeblockOrMedia && !isNestingMediaOrCodeblockSupported && blockquoteNode) {
966
+ tr = (0, _utils2.safeInsert)(slice.content, blockquoteNode.start + blockquoteNode.node.nodeSize - 1)(tr);
952
967
  } else if (noNeedForSafeInsert) {
953
968
  var _firstChildOfSlice$ty2, _firstChildOfSlice$co, _firstChildOfSlice$co2;
954
969
  if ((firstChildOfSlice === null || firstChildOfSlice === void 0 || (_firstChildOfSlice$ty2 = firstChildOfSlice.type) === null || _firstChildOfSlice$ty2 === void 0 ? void 0 : _firstChildOfSlice$ty2.name) === 'blockquote' && firstChildOfSlice !== null && firstChildOfSlice !== void 0 && (_firstChildOfSlice$co = firstChildOfSlice.content.firstChild) !== null && _firstChildOfSlice$co !== void 0 && _firstChildOfSlice$co.type.name && ['bulletList', 'orderedList', 'mediaSingle'].includes(firstChildOfSlice === null || firstChildOfSlice === void 0 || (_firstChildOfSlice$co2 = firstChildOfSlice.content.firstChild) === null || _firstChildOfSlice$co2 === void 0 ? void 0 : _firstChildOfSlice$co2.type.name)) {
@@ -314,11 +314,11 @@ var handleMarkdownWithAnalytics = exports.handleMarkdownWithAnalytics = function
314
314
  type: _analytics.PasteTypes.markdown
315
315
  }))((0, _handlers.handleMarkdown)(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 || (_pluginInjectionApi$c3 = _pluginInjectionApi$c3.actions) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : _pluginInjectionApi$c3.queueCardsFromChangedTr));
316
316
  };
317
- var handleRichTextWithAnalytics = exports.handleRichTextWithAnalytics = function handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi) {
317
+ var handleRichTextWithAnalytics = exports.handleRichTextWithAnalytics = function handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported) {
318
318
  var _pluginInjectionApi$a4, _pluginInjectionApi$c4;
319
319
  return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
320
320
  type: _analytics.PasteTypes.richText
321
- }, pluginInjectionApi))((0, _handlers.handleRichText)(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c4 = pluginInjectionApi.card) === null || _pluginInjectionApi$c4 === void 0 || (_pluginInjectionApi$c4 = _pluginInjectionApi$c4.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr));
321
+ }, pluginInjectionApi))((0, _handlers.handleRichText)(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c4 = pluginInjectionApi.card) === null || _pluginInjectionApi$c4 === void 0 || (_pluginInjectionApi$c4 = _pluginInjectionApi$c4.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported));
322
322
  };
323
323
  var injectAnalyticsPayloadBeforeCommand = function injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI) {
324
324
  return function (createPayloadByTransaction) {
@@ -173,8 +173,11 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
173
173
  /**
174
174
  * We want to return false for external copied image to allow
175
175
  * it to be uploaded by the client.
176
+ *
177
+ * Scenario where we are pasting an external image inside a block quote
178
+ * is skipped and handled in handleRichText
176
179
  */
177
- if ((0, _util.htmlContainsSingleFile)(html)) {
180
+ if ((0, _util.htmlContainsSingleFile)(html) && !isInsideBlockQuote(view.state)) {
178
181
  return true;
179
182
  }
180
183
 
@@ -370,7 +373,7 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
370
373
 
371
374
  // finally, handle rich-text copy-paste
372
375
  if (isRichText || isNestedMarkdownTable) {
373
- var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$f, _pluginInjectionApi$l;
376
+ var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$f, _pluginInjectionApi$l, _pluginInjectionApi$f2;
374
377
  // linkify the text where possible
375
378
  slice = (0, _utils.linkifyContent)(state.schema)(slice);
376
379
  if ((0, _analytics2.handlePasteLinkOnSelectedTextWithAnalytics)(editorAnalyticsAPI)(view, event, slice, _analytics.PasteTypes.richText)(state, dispatch)) {
@@ -447,7 +450,8 @@ function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFlags, pl
447
450
  if ((0, _analytics2.handlePasteNonNestableBlockNodesIntoListWithAnalytics)(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
448
451
  return true;
449
452
  }
450
- return (0, _analytics2.handleRichTextWithAnalytics)(view, event, slice, pluginInjectionApi)(state, dispatch);
453
+ var isNestingMediaOrCodeblockSupported = (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$f2 = pluginInjectionApi.featureFlags) === null || _pluginInjectionApi$f2 === void 0 || (_pluginInjectionApi$f2 = _pluginInjectionApi$f2.sharedState.currentState()) === null || _pluginInjectionApi$f2 === void 0 ? void 0 : _pluginInjectionApi$f2.nestMediaAndCodeblockInQuote) || (0, _platformFeatureFlags.fg)('editor_nest_media_and_codeblock_in_quotes_jira');
454
+ return (0, _analytics2.handleRichTextWithAnalytics)(view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported)(state, dispatch);
451
455
  }
452
456
  return false;
453
457
  },
@@ -16,7 +16,8 @@ var updateContentMoved = exports.updateContentMoved = function updateContentMove
16
16
  var data = {
17
17
  currentActions: [].concat((0, _toConsumableArray2.default)(contentMoved.currentActions), [nextAction]),
18
18
  size: (nextState === null || nextState === void 0 ? void 0 : nextState.size) || contentMoved.size,
19
- nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName
19
+ nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName,
20
+ nodeDepth: nextState === null || nextState === void 0 ? void 0 : nextState.nodeDepth
20
21
  };
21
22
  return {
22
23
  type: _actions.MoveAnalyticPluginTypes.UpdateMovedAction,
@@ -1,16 +1,21 @@
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.createPlugin = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
7
9
  var _analytics = require("@atlaskit/editor-common/analytics");
8
10
  var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
9
12
  var _commands = require("./commands");
10
13
  var _pluginFactory = require("./plugin-factory");
11
14
  var _pluginKey = require("./plugin-key");
12
15
  var _types = require("./types");
13
16
  var _utils = require("./utils");
17
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
18
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
14
19
  // This plugin exists only in FullPage/FullWidth Editor and is used to register an event that tells us
15
20
  // that a user cut and than pasted a node. This order of actions could be considered an alternative
16
21
  // to new Drag and Drop functionality. The event (document moved) is not accurate, but should be enough to be
@@ -42,8 +47,12 @@ var createPlugin = exports.createPlugin = function createPlugin(dispatch, editor
42
47
  var content = slice.content,
43
48
  size = slice.size;
44
49
  var nodeName = (_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type.name;
45
- // We should not account for pastes that go inside another node and create nested content as DnD can't do it.
46
- if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !(0, _utils.isValidNodeName)(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !(0, _utils.isCursorSelectionAndInsideTopLevelNode)(state.selection)) {
50
+
51
+ // We should not account for pastes that go inside another node and create nested content as DnD can't do it when nested-dnd is disabled
52
+ if ((!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !(0, _utils.isValidNodeName)(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !(0, _utils.isCursorSelectionAndInsideTopLevelNode)(state.selection)) && (0, _experiments.editorExperiment)('nested-dnd', false)) {
53
+ return;
54
+ }
55
+ if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !(0, _utils.isCursorSelectionAtTopLevel)(state.selection) && (0, _experiments.editorExperiment)('nested-dnd', true)) {
47
56
  return;
48
57
  }
49
58
  var tr = state.tr;
@@ -52,9 +61,12 @@ var createPlugin = exports.createPlugin = function createPlugin(dispatch, editor
52
61
  actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
53
62
  actionSubjectId: _analytics.ACTION_SUBJECT_ID.NODE,
54
63
  eventType: _analytics.EVENT_TYPE.TRACK,
55
- attributes: {
56
- nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName // keep nodeName from copied slice
57
- }
64
+ attributes: _objectSpread({
65
+ nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName
66
+ }, (0, _experiments.editorExperiment)('nested-dnd', true) && {
67
+ nodeDepth: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeDepth,
68
+ destinationNodeDepth: (0, _utils.getParentNodeDepth)(state.selection)
69
+ })
58
70
  })(tr);
59
71
 
60
72
  // reset to default state
@@ -84,6 +96,7 @@ var createPlugin = exports.createPlugin = function createPlugin(dispatch, editor
84
96
  resetState = true;
85
97
  }
86
98
  var nodeName = ((_content$firstChild2 = content.firstChild) === null || _content$firstChild2 === void 0 ? void 0 : _content$firstChild2.type.name) || '';
99
+
87
100
  // Some nodes are not relevant as they are parts of nodes, not whole nodes (like tableCell, tableHeader instead of table node)
88
101
  // Some nodes like lists, taskList(item), decisionList(item) requires tricky checks that we want to avoid doing.
89
102
  // These nodes were added to excludedNodes array.
@@ -93,42 +106,50 @@ var createPlugin = exports.createPlugin = function createPlugin(dispatch, editor
93
106
  var selection = state.selection;
94
107
  // DnD can't drag part of text in a paragraph/heading. DnD will select the whole node with all the text inside.
95
108
  // So we are only interested in cut slices that contain the entire node, not just a part of it.
96
- if (!resetState && nodeName === 'paragraph' && !(0, _utils.isEntireTopLevelHeadingOrParagraphSelected)(selection, nodeName)) {
109
+ if (!resetState && nodeName === 'paragraph' && !(0, _utils.isEntireTopLevelHeadingOrParagraphSelected)(selection, nodeName) && (0, _experiments.editorExperiment)('nested-dnd', false)) {
97
110
  resetState = true;
98
111
  }
99
- if (!resetState && nodeName === 'heading' && !(0, _utils.isEntireTopLevelHeadingOrParagraphSelected)(selection, nodeName)) {
112
+ if (!resetState && nodeName === 'heading' && !(0, _utils.isEntireTopLevelHeadingOrParagraphSelected)(selection, nodeName) && (0, _experiments.editorExperiment)('nested-dnd', false)) {
113
+ resetState = true;
114
+ }
115
+ if (!resetState && !(0, _utils.isEntireNestedParagraphOrHeadingSelected)(selection) && (0, _experiments.editorExperiment)('nested-dnd', true)) {
100
116
  resetState = true;
101
117
  }
102
118
 
103
119
  // DnD can't drag just one paragraph (when blockquote contains multiple paragraphs) or just a part of a paragraph in the blockquote.
104
120
  // DnD will select and drag the whole blockquote. So we need to register cut events that have the entire blockquote too.
105
- if (!resetState && nodeName === 'blockquote' && !(0, _utils.isEntireTopLevelBlockquoteSelected)(state)) {
121
+ if (!resetState && nodeName === 'blockquote' && !(0, _utils.isEntireTopLevelBlockquoteSelected)(state) && (0, _experiments.editorExperiment)('nested-dnd', false)) {
106
122
  resetState = true;
107
123
  }
108
124
  if (!resetState && (0, _utils.isInlineNode)(nodeName) && (0, _utils.isNestedInlineNode)(selection)) {
109
125
  resetState = true;
110
126
  }
111
- if (!resetState && nodeName === 'table' && (0, _utils.isNestedTable)(selection)) {
127
+ if (!resetState && nodeName === 'table' && (0, _utils.isNestedTable)(selection) && (0, _experiments.editorExperiment)('nested-dnd', false)) {
128
+ resetState = true;
129
+ }
130
+ if (!resetState && (0, _utils.isNestedInTable)(state) && (0, _experiments.editorExperiment)('nested-dnd', true) && (0, _experiments.editorExperiment)('table-nested-dnd', false)) {
112
131
  resetState = true;
113
132
  }
114
133
  var isBlockNode = (0, _utils.isBlockNodeWithoutTable)(nodeName);
115
- if (!resetState && (0, _utils.isNodeSelection)(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' // checks that the block node is not a topLevel node
134
+ if (!resetState && (0, _utils.isNodeSelection)(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' && (0, _experiments.editorExperiment)('nested-dnd', false) // checks that the block node is not a topLevel node
116
135
  ) {
117
136
  resetState = true;
118
137
  }
119
138
 
120
139
  // Some blockNodes can have text inside of them cut, in that case TextSelection occurs, we don't need to track
121
140
  // these cut events.
122
- if (!resetState && (0, _utils.isTextSelection)(selection) && isBlockNode) {
141
+ if (!resetState && (0, _utils.isTextSelection)(selection) && isBlockNode && (0, _experiments.editorExperiment)('nested-dnd', false)) {
123
142
  resetState = true;
124
143
  }
125
144
  if (resetState) {
126
145
  (0, _commands.resetContentMoved)()(state, dispatch);
127
146
  } else {
128
- (0, _commands.updateContentMoved)({
147
+ (0, _commands.updateContentMoved)(_objectSpread({
129
148
  size: size,
130
149
  nodeName: nodeName
131
- }, 'contentCut')(state, dispatch);
150
+ }, (0, _experiments.editorExperiment)('nested-dnd', true) && {
151
+ nodeDepth: (0, _utils.getParentNodeDepth)(selection)
152
+ }), 'contentCut')(state, dispatch);
132
153
  }
133
154
  isCutEvent = false;
134
155
  return slice;
@@ -14,6 +14,7 @@ var defaultState = exports.defaultState = {
14
14
  contentMoved: {
15
15
  nodeName: undefined,
16
16
  size: undefined,
17
+ nodeDepth: undefined,
17
18
  currentActions: []
18
19
  }
19
20
  };
@@ -1,12 +1,16 @@
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
- exports.isValidNodeName = exports.isTextSelection = exports.isNodeSelection = exports.isNestedTable = exports.isNestedInlineNode = exports.isInlineNode = exports.isExcludedNode = exports.isEntireTopLevelHeadingOrParagraphSelected = exports.isEntireTopLevelBlockquoteSelected = exports.isCursorSelectionAndInsideTopLevelNode = exports.isBlockNodeWithoutTable = void 0;
7
+ exports.isValidNodeName = exports.isTextSelection = exports.isNodeSelection = exports.isNestedTable = exports.isNestedInlineNode = exports.isNestedInTable = exports.isInlineNode = exports.isExcludedNode = exports.isEntireTopLevelHeadingOrParagraphSelected = exports.isEntireTopLevelBlockquoteSelected = exports.isEntireNestedParagraphOrHeadingSelected = exports.isCursorSelectionAtTopLevel = exports.isCursorSelectionAndInsideTopLevelNode = exports.isBlockNodeWithoutTable = exports.getParentNodeDepth = void 0;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
7
9
  var _state = require("@atlaskit/editor-prosemirror/state");
8
10
  var _utils = require("@atlaskit/editor-prosemirror/utils");
9
- var excludedNodes = ['caption', 'layoutColumn', 'listItem', 'nestedExpand', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'media', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'bulletList', 'orderedList', 'taskList', 'taskItem', 'decisionList', 'decisionItem'];
11
+ var _cellSelection = require("@atlaskit/editor-tables/cell-selection");
12
+ var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
13
+ var excludedNodes = ['caption', 'layoutColumn', 'listItem', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'taskItem', 'decisionItem'].concat((0, _toConsumableArray2.default)((0, _experiments.editorExperiment)('nested-dnd', false) ? ['bulletList', 'orderedList', 'taskList', 'decisionList', 'nestedExpand', 'media'] : []));
10
14
  var isExcludedNode = exports.isExcludedNode = function isExcludedNode(nodeName) {
11
15
  return excludedNodes.includes(nodeName);
12
16
  };
@@ -19,6 +23,15 @@ var isCursorSelectionAndInsideTopLevelNode = exports.isCursorSelectionAndInsideT
19
23
  }
20
24
  return true;
21
25
  };
26
+ var isCursorSelectionAtTopLevel = exports.isCursorSelectionAtTopLevel = function isCursorSelectionAtTopLevel(selection) {
27
+ var from = selection.from,
28
+ to = selection.to,
29
+ $from = selection.$from;
30
+ if (from !== to) {
31
+ return false;
32
+ }
33
+ return $from.parentOffset === 0;
34
+ };
22
35
  var inlineNodes = ['emoji', 'date', 'status', 'mention', 'mediaInline', 'inlineCard', 'inlineExtension'];
23
36
  var isInlineNode = exports.isInlineNode = function isInlineNode(nodeName) {
24
37
  return inlineNodes.includes(nodeName);
@@ -52,6 +65,28 @@ var isNestedTable = exports.isNestedTable = function isNestedTable(selection) {
52
65
  var parentNode = selection.$anchor.node(1);
53
66
  return parentNode && parentNodes.includes(parentNode.type.name);
54
67
  };
68
+ var isNestedInTable = exports.isNestedInTable = function isNestedInTable(state) {
69
+ var schema = state.schema,
70
+ selection = state.selection;
71
+ if (selection instanceof _cellSelection.CellSelection) {
72
+ return false;
73
+ }
74
+ var table = schema.nodes.table;
75
+ var tableNode = (0, _utils.findParentNodeOfTypeClosestToPos)(selection.$from, table);
76
+ if (!tableNode) {
77
+ return false;
78
+ }
79
+ return true;
80
+ };
81
+ var getParentNodeDepth = exports.getParentNodeDepth = function getParentNodeDepth(selection) {
82
+ var parentNode = (0, _utils.findParentNodeClosestToPos)(selection.$from, function () {
83
+ return true;
84
+ });
85
+ if (!parentNode) {
86
+ return 0;
87
+ }
88
+ return parentNode.node.type.name === 'heading' || parentNode.node.type.name === 'paragraph' ? parentNode.depth - 1 : parentNode.depth;
89
+ };
55
90
  var getPastedNameOfInlineNode = function getPastedNameOfInlineNode(nodeName) {
56
91
  if (inlineNodes.includes(nodeName)) {
57
92
  return 'paragraph';
@@ -117,4 +152,9 @@ var isEntireTopLevelBlockquoteSelected = exports.isEntireTopLevelBlockquoteSelec
117
152
  }
118
153
  });
119
154
  return selectedNodesCount === blockquoteNode.node.childCount;
155
+ };
156
+ var isEntireNestedParagraphOrHeadingSelected = exports.isEntireNestedParagraphOrHeadingSelected = function isEntireNestedParagraphOrHeadingSelected(selection) {
157
+ var $from = selection.$from,
158
+ $to = selection.$to;
159
+ return $from.textOffset === 0 && $to.textOffset === 0;
120
160
  };
@@ -14,6 +14,7 @@ import { replaceSelectedTable } from '@atlaskit/editor-tables/utils';
14
14
  import { startTrackingPastedMacroPositions, stopTrackingPastedMacroPositions } from './commands';
15
15
  import { insertSliceForLists, insertSliceForTaskInsideList, insertSliceInsideBlockquote } from './edge-cases';
16
16
  import { insertSliceInsideOfPanelNodeSelected } from './edge-cases/lists';
17
+ import { isInsideBlockQuote } from './pm-plugins/main';
17
18
  import { getPluginState as getPastePluginState } from './pm-plugins/plugin-factory';
18
19
  import { addReplaceSelectedTableAnalytics, applyTextMarksToSlice, hasOnlyNodesOfType, isEmptyNode, isSelectionInsidePanel } from './util';
19
20
  const insideExpand = state => {
@@ -880,10 +881,11 @@ export function flattenNestedListInSlice(slice) {
880
881
  const contentWithFlattenedList = slice.content.replaceChild(0, listToFlatten.type.createChecked(listToFlatten.attrs, leafListItems));
881
882
  return new Slice(contentWithFlattenedList, slice.openEnd, slice.openEnd);
882
883
  }
883
- export function handleRichText(slice, queueCardsFromChangedTr) {
884
+ export function handleRichText(slice, queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported) {
884
885
  return (state, dispatch) => {
885
886
  var _slice$content, _slice$content2, _firstChildOfSlice$ty, _lastChildOfSlice$typ, _panelParentOverCurre;
886
887
  const {
888
+ blockquote,
887
889
  codeBlock,
888
890
  heading,
889
891
  paragraph,
@@ -921,12 +923,25 @@ export function handleRichText(slice, queueCardsFromChangedTr) {
921
923
  const noNeedForSafeInsert = selection.$to.node().type.validContent(slice.content) || textNodes.includes(selection.$to.node().type) && selectionParent.type.validContent(slice.content);
922
924
  let panelParentOverCurrentSelection = findParentNodeOfType(panel)(tr.selection);
923
925
  const isTargetPanelEmpty = panelParentOverCurrentSelection && ((_panelParentOverCurre = panelParentOverCurrentSelection.node) === null || _panelParentOverCurre === void 0 ? void 0 : _panelParentOverCurre.content.size) === 2;
926
+ let sliceContainsCodeblockOrMedia = false;
927
+ const codeBlockAndMediaNodes = ['codeBlock', 'mediaSingle', 'mediaGroup'];
928
+ slice.content.forEach(child => {
929
+ if (codeBlockAndMediaNodes.includes(child.type.name)) {
930
+ sliceContainsCodeblockOrMedia = true;
931
+ }
932
+ });
933
+ const blockquoteNode = findParentNodeOfType(blockquote)(tr.selection);
924
934
  if (!isSliceContentTaskListNodes && (isSliceContentListNodes || isTargetPanelEmpty)) {
925
935
  insertSliceForLists({
926
936
  tr,
927
937
  slice,
928
938
  schema
929
939
  });
940
+ } else if (
941
+ // If nesting media or codeblock in blockquote is not supported
942
+ // we want to insert slice after the blockquote.
943
+ isInsideBlockQuote(state) && sliceContainsCodeblockOrMedia && !isNestingMediaOrCodeblockSupported && blockquoteNode) {
944
+ tr = safeInsert(slice.content, blockquoteNode.start + blockquoteNode.node.nodeSize - 1)(tr);
930
945
  } else if (noNeedForSafeInsert) {
931
946
  var _firstChildOfSlice$ty2, _firstChildOfSlice$co, _firstChildOfSlice$co2;
932
947
  if ((firstChildOfSlice === null || firstChildOfSlice === void 0 ? void 0 : (_firstChildOfSlice$ty2 = firstChildOfSlice.type) === null || _firstChildOfSlice$ty2 === void 0 ? void 0 : _firstChildOfSlice$ty2.name) === 'blockquote' && firstChildOfSlice !== null && firstChildOfSlice !== void 0 && (_firstChildOfSlice$co = firstChildOfSlice.content.firstChild) !== null && _firstChildOfSlice$co !== void 0 && _firstChildOfSlice$co.type.name && ['bulletList', 'orderedList', 'mediaSingle'].includes(firstChildOfSlice === null || firstChildOfSlice === void 0 ? void 0 : (_firstChildOfSlice$co2 = firstChildOfSlice.content.firstChild) === null || _firstChildOfSlice$co2 === void 0 ? void 0 : _firstChildOfSlice$co2.type.name)) {
@@ -291,11 +291,11 @@ export const handleMarkdownWithAnalytics = (view, event, slice, pluginInjectionA
291
291
  type: PasteTypes.markdown
292
292
  }))(handleMarkdown(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c5 = pluginInjectionApi.card) === null || _pluginInjectionApi$c5 === void 0 ? void 0 : (_pluginInjectionApi$c6 = _pluginInjectionApi$c5.actions) === null || _pluginInjectionApi$c6 === void 0 ? void 0 : _pluginInjectionApi$c6.queueCardsFromChangedTr));
293
293
  };
294
- export const handleRichTextWithAnalytics = (view, event, slice, pluginInjectionApi) => {
294
+ export const handleRichTextWithAnalytics = (view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported) => {
295
295
  var _pluginInjectionApi$a4, _pluginInjectionApi$c7, _pluginInjectionApi$c8;
296
296
  return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
297
297
  type: PasteTypes.richText
298
- }, pluginInjectionApi))(handleRichText(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c7 = pluginInjectionApi.card) === null || _pluginInjectionApi$c7 === void 0 ? void 0 : (_pluginInjectionApi$c8 = _pluginInjectionApi$c7.actions) === null || _pluginInjectionApi$c8 === void 0 ? void 0 : _pluginInjectionApi$c8.queueCardsFromChangedTr));
298
+ }, pluginInjectionApi))(handleRichText(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$c7 = pluginInjectionApi.card) === null || _pluginInjectionApi$c7 === void 0 ? void 0 : (_pluginInjectionApi$c8 = _pluginInjectionApi$c7.actions) === null || _pluginInjectionApi$c8 === void 0 ? void 0 : _pluginInjectionApi$c8.queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported));
299
299
  };
300
300
  const injectAnalyticsPayloadBeforeCommand = editorAnalyticsAPI => createPayloadByTransaction => {
301
301
  return mainCommand => {
@@ -137,8 +137,11 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
137
137
  /**
138
138
  * We want to return false for external copied image to allow
139
139
  * it to be uploaded by the client.
140
+ *
141
+ * Scenario where we are pasting an external image inside a block quote
142
+ * is skipped and handled in handleRichText
140
143
  */
141
- if (htmlContainsSingleFile(html)) {
144
+ if (htmlContainsSingleFile(html) && !isInsideBlockQuote(view.state)) {
142
145
  return true;
143
146
  }
144
147
 
@@ -341,7 +344,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
341
344
 
342
345
  // finally, handle rich-text copy-paste
343
346
  if (isRichText || isNestedMarkdownTable) {
344
- var _pluginInjectionApi$c3, _pluginInjectionApi$c4, _pluginInjectionApi$e3, _pluginInjectionApi$e4, _pluginInjectionApi$f, _pluginInjectionApi$f2, _pluginInjectionApi$l;
347
+ var _pluginInjectionApi$c3, _pluginInjectionApi$c4, _pluginInjectionApi$e3, _pluginInjectionApi$e4, _pluginInjectionApi$f, _pluginInjectionApi$f2, _pluginInjectionApi$l, _pluginInjectionApi$f3, _pluginInjectionApi$f4;
345
348
  // linkify the text where possible
346
349
  slice = linkifyContent(state.schema)(slice);
347
350
  if (handlePasteLinkOnSelectedTextWithAnalytics(editorAnalyticsAPI)(view, event, slice, PasteTypes.richText)(state, dispatch)) {
@@ -416,7 +419,8 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
416
419
  if (handlePasteNonNestableBlockNodesIntoListWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
417
420
  return true;
418
421
  }
419
- return handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi)(state, dispatch);
422
+ const isNestingMediaOrCodeblockSupported = (pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$f3 = pluginInjectionApi.featureFlags) === null || _pluginInjectionApi$f3 === void 0 ? void 0 : (_pluginInjectionApi$f4 = _pluginInjectionApi$f3.sharedState.currentState()) === null || _pluginInjectionApi$f4 === void 0 ? void 0 : _pluginInjectionApi$f4.nestMediaAndCodeblockInQuote) || fg('editor_nest_media_and_codeblock_in_quotes_jira');
423
+ return handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported)(state, dispatch);
420
424
  }
421
425
  return false;
422
426
  },
@@ -8,7 +8,8 @@ export const updateContentMoved = (nextState, nextAction) => createCommand(state
8
8
  const data = {
9
9
  currentActions: [...contentMoved.currentActions, nextAction],
10
10
  size: (nextState === null || nextState === void 0 ? void 0 : nextState.size) || contentMoved.size,
11
- nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName
11
+ nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName,
12
+ nodeDepth: nextState === null || nextState === void 0 ? void 0 : nextState.nodeDepth
12
13
  };
13
14
  return {
14
15
  type: MoveAnalyticPluginTypes.UpdateMovedAction,
@@ -1,10 +1,11 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
2
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
4
  import { resetContentMoved, resetContentMovedTransform, updateContentMoved } from './commands';
4
5
  import { createPluginState, getPluginState } from './plugin-factory';
5
6
  import { pluginKey } from './plugin-key';
6
7
  import { defaultState } from './types';
7
- import { isBlockNodeWithoutTable, isCursorSelectionAndInsideTopLevelNode, isEntireTopLevelBlockquoteSelected, isEntireTopLevelHeadingOrParagraphSelected, isExcludedNode, isInlineNode, isNestedInlineNode, isNestedTable, isNodeSelection, isTextSelection, isValidNodeName } from './utils';
8
+ import { getParentNodeDepth, isBlockNodeWithoutTable, isCursorSelectionAndInsideTopLevelNode, isCursorSelectionAtTopLevel, isEntireNestedParagraphOrHeadingSelected, isEntireTopLevelBlockquoteSelected, isEntireTopLevelHeadingOrParagraphSelected, isExcludedNode, isInlineNode, isNestedInlineNode, isNestedInTable, isNestedTable, isNodeSelection, isTextSelection, isValidNodeName } from './utils';
8
9
 
9
10
  // This plugin exists only in FullPage/FullWidth Editor and is used to register an event that tells us
10
11
  // that a user cut and than pasted a node. This order of actions could be considered an alternative
@@ -41,8 +42,12 @@ export const createPlugin = (dispatch, editorAnalyticsAPI) => {
41
42
  size
42
43
  } = slice;
43
44
  const nodeName = (_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type.name;
44
- // We should not account for pastes that go inside another node and create nested content as DnD can't do it.
45
- if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isValidNodeName(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !isCursorSelectionAndInsideTopLevelNode(state.selection)) {
45
+
46
+ // We should not account for pastes that go inside another node and create nested content as DnD can't do it when nested-dnd is disabled
47
+ if ((!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isValidNodeName(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !isCursorSelectionAndInsideTopLevelNode(state.selection)) && editorExperiment('nested-dnd', false)) {
48
+ return;
49
+ }
50
+ if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isCursorSelectionAtTopLevel(state.selection) && editorExperiment('nested-dnd', true)) {
46
51
  return;
47
52
  }
48
53
  const {
@@ -54,7 +59,12 @@ export const createPlugin = (dispatch, editorAnalyticsAPI) => {
54
59
  actionSubjectId: ACTION_SUBJECT_ID.NODE,
55
60
  eventType: EVENT_TYPE.TRACK,
56
61
  attributes: {
57
- nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName // keep nodeName from copied slice
62
+ nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName,
63
+ ...(editorExperiment('nested-dnd', true) && {
64
+ nodeDepth: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeDepth,
65
+ destinationNodeDepth: getParentNodeDepth(state.selection)
66
+ })
67
+ // keep nodeName from copied slice
58
68
  }
59
69
  })(tr);
60
70
 
@@ -88,6 +98,7 @@ export const createPlugin = (dispatch, editorAnalyticsAPI) => {
88
98
  resetState = true;
89
99
  }
90
100
  const nodeName = ((_content$firstChild2 = content.firstChild) === null || _content$firstChild2 === void 0 ? void 0 : _content$firstChild2.type.name) || '';
101
+
91
102
  // Some nodes are not relevant as they are parts of nodes, not whole nodes (like tableCell, tableHeader instead of table node)
92
103
  // Some nodes like lists, taskList(item), decisionList(item) requires tricky checks that we want to avoid doing.
93
104
  // These nodes were added to excludedNodes array.
@@ -99,33 +110,39 @@ export const createPlugin = (dispatch, editorAnalyticsAPI) => {
99
110
  } = state;
100
111
  // DnD can't drag part of text in a paragraph/heading. DnD will select the whole node with all the text inside.
101
112
  // So we are only interested in cut slices that contain the entire node, not just a part of it.
102
- if (!resetState && nodeName === 'paragraph' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName)) {
113
+ if (!resetState && nodeName === 'paragraph' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName) && editorExperiment('nested-dnd', false)) {
103
114
  resetState = true;
104
115
  }
105
- if (!resetState && nodeName === 'heading' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName)) {
116
+ if (!resetState && nodeName === 'heading' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName) && editorExperiment('nested-dnd', false)) {
117
+ resetState = true;
118
+ }
119
+ if (!resetState && !isEntireNestedParagraphOrHeadingSelected(selection) && editorExperiment('nested-dnd', true)) {
106
120
  resetState = true;
107
121
  }
108
122
 
109
123
  // DnD can't drag just one paragraph (when blockquote contains multiple paragraphs) or just a part of a paragraph in the blockquote.
110
124
  // DnD will select and drag the whole blockquote. So we need to register cut events that have the entire blockquote too.
111
- if (!resetState && nodeName === 'blockquote' && !isEntireTopLevelBlockquoteSelected(state)) {
125
+ if (!resetState && nodeName === 'blockquote' && !isEntireTopLevelBlockquoteSelected(state) && editorExperiment('nested-dnd', false)) {
112
126
  resetState = true;
113
127
  }
114
128
  if (!resetState && isInlineNode(nodeName) && isNestedInlineNode(selection)) {
115
129
  resetState = true;
116
130
  }
117
- if (!resetState && nodeName === 'table' && isNestedTable(selection)) {
131
+ if (!resetState && nodeName === 'table' && isNestedTable(selection) && editorExperiment('nested-dnd', false)) {
132
+ resetState = true;
133
+ }
134
+ if (!resetState && isNestedInTable(state) && editorExperiment('nested-dnd', true) && editorExperiment('table-nested-dnd', false)) {
118
135
  resetState = true;
119
136
  }
120
137
  const isBlockNode = isBlockNodeWithoutTable(nodeName);
121
- if (!resetState && isNodeSelection(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' // checks that the block node is not a topLevel node
138
+ if (!resetState && isNodeSelection(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' && editorExperiment('nested-dnd', false) // checks that the block node is not a topLevel node
122
139
  ) {
123
140
  resetState = true;
124
141
  }
125
142
 
126
143
  // Some blockNodes can have text inside of them cut, in that case TextSelection occurs, we don't need to track
127
144
  // these cut events.
128
- if (!resetState && isTextSelection(selection) && isBlockNode) {
145
+ if (!resetState && isTextSelection(selection) && isBlockNode && editorExperiment('nested-dnd', false)) {
129
146
  resetState = true;
130
147
  }
131
148
  if (resetState) {
@@ -133,7 +150,10 @@ export const createPlugin = (dispatch, editorAnalyticsAPI) => {
133
150
  } else {
134
151
  updateContentMoved({
135
152
  size: size,
136
- nodeName: nodeName
153
+ nodeName: nodeName,
154
+ ...(editorExperiment('nested-dnd', true) && {
155
+ nodeDepth: getParentNodeDepth(selection)
156
+ })
137
157
  }, 'contentCut')(state, dispatch);
138
158
  }
139
159
  isCutEvent = false;
@@ -8,6 +8,7 @@ export const defaultState = {
8
8
  contentMoved: {
9
9
  nodeName: undefined,
10
10
  size: undefined,
11
+ nodeDepth: undefined,
11
12
  currentActions: []
12
13
  }
13
14
  };
@@ -1,6 +1,8 @@
1
1
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
2
- import { findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
3
- const excludedNodes = ['caption', 'layoutColumn', 'listItem', 'nestedExpand', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'media', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'bulletList', 'orderedList', 'taskList', 'taskItem', 'decisionList', 'decisionItem'];
2
+ import { findParentNodeClosestToPos, findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
3
+ import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
4
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
5
+ const excludedNodes = ['caption', 'layoutColumn', 'listItem', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'taskItem', 'decisionItem', ...(editorExperiment('nested-dnd', false) ? ['bulletList', 'orderedList', 'taskList', 'decisionList', 'nestedExpand', 'media'] : [])];
4
6
  export const isExcludedNode = nodeName => excludedNodes.includes(nodeName);
5
7
  export const isCursorSelectionAndInsideTopLevelNode = selection => {
6
8
  const {
@@ -13,6 +15,17 @@ export const isCursorSelectionAndInsideTopLevelNode = selection => {
13
15
  }
14
16
  return true;
15
17
  };
18
+ export const isCursorSelectionAtTopLevel = selection => {
19
+ const {
20
+ from,
21
+ to,
22
+ $from
23
+ } = selection;
24
+ if (from !== to) {
25
+ return false;
26
+ }
27
+ return $from.parentOffset === 0;
28
+ };
16
29
  const inlineNodes = ['emoji', 'date', 'status', 'mention', 'mediaInline', 'inlineCard', 'inlineExtension'];
17
30
  export const isInlineNode = nodeName => {
18
31
  return inlineNodes.includes(nodeName);
@@ -46,6 +59,30 @@ export const isNestedTable = selection => {
46
59
  const parentNode = selection.$anchor.node(1);
47
60
  return parentNode && parentNodes.includes(parentNode.type.name);
48
61
  };
62
+ export const isNestedInTable = state => {
63
+ const {
64
+ schema,
65
+ selection
66
+ } = state;
67
+ if (selection instanceof CellSelection) {
68
+ return false;
69
+ }
70
+ const {
71
+ table
72
+ } = schema.nodes;
73
+ const tableNode = findParentNodeOfTypeClosestToPos(selection.$from, table);
74
+ if (!tableNode) {
75
+ return false;
76
+ }
77
+ return true;
78
+ };
79
+ export const getParentNodeDepth = selection => {
80
+ const parentNode = findParentNodeClosestToPos(selection.$from, () => true);
81
+ if (!parentNode) {
82
+ return 0;
83
+ }
84
+ return parentNode.node.type.name === 'heading' || parentNode.node.type.name === 'paragraph' ? parentNode.depth - 1 : parentNode.depth;
85
+ };
49
86
  const getPastedNameOfInlineNode = nodeName => {
50
87
  if (inlineNodes.includes(nodeName)) {
51
88
  return 'paragraph';
@@ -121,4 +158,11 @@ export const isEntireTopLevelBlockquoteSelected = state => {
121
158
  }
122
159
  });
123
160
  return selectedNodesCount === blockquoteNode.node.childCount;
161
+ };
162
+ export const isEntireNestedParagraphOrHeadingSelected = selection => {
163
+ const {
164
+ $from,
165
+ $to
166
+ } = selection;
167
+ return $from.textOffset === 0 && $to.textOffset === 0;
124
168
  };
@@ -22,6 +22,7 @@ import { replaceSelectedTable } from '@atlaskit/editor-tables/utils';
22
22
  import { startTrackingPastedMacroPositions, stopTrackingPastedMacroPositions } from './commands';
23
23
  import { insertSliceForLists, insertSliceForTaskInsideList, insertSliceInsideBlockquote } from './edge-cases';
24
24
  import { insertSliceInsideOfPanelNodeSelected } from './edge-cases/lists';
25
+ import { isInsideBlockQuote } from './pm-plugins/main';
25
26
  import { getPluginState as getPastePluginState } from './pm-plugins/plugin-factory';
26
27
  import { addReplaceSelectedTableAnalytics, applyTextMarksToSlice, hasOnlyNodesOfType, isEmptyNode, isSelectionInsidePanel } from './util';
27
28
  var insideExpand = function insideExpand(state) {
@@ -881,10 +882,11 @@ export function flattenNestedListInSlice(slice) {
881
882
  var contentWithFlattenedList = slice.content.replaceChild(0, listToFlatten.type.createChecked(listToFlatten.attrs, leafListItems));
882
883
  return new Slice(contentWithFlattenedList, slice.openEnd, slice.openEnd);
883
884
  }
884
- export function handleRichText(slice, queueCardsFromChangedTr) {
885
+ export function handleRichText(slice, queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported) {
885
886
  return function (state, dispatch) {
886
887
  var _slice$content, _slice$content2, _firstChildOfSlice$ty, _lastChildOfSlice$typ, _panelParentOverCurre;
887
888
  var _state$schema$nodes4 = state.schema.nodes,
889
+ blockquote = _state$schema$nodes4.blockquote,
888
890
  codeBlock = _state$schema$nodes4.codeBlock,
889
891
  heading = _state$schema$nodes4.heading,
890
892
  paragraph = _state$schema$nodes4.paragraph,
@@ -919,12 +921,25 @@ export function handleRichText(slice, queueCardsFromChangedTr) {
919
921
  var noNeedForSafeInsert = selection.$to.node().type.validContent(slice.content) || textNodes.includes(selection.$to.node().type) && selectionParent.type.validContent(slice.content);
920
922
  var panelParentOverCurrentSelection = findParentNodeOfType(panel)(tr.selection);
921
923
  var isTargetPanelEmpty = panelParentOverCurrentSelection && ((_panelParentOverCurre = panelParentOverCurrentSelection.node) === null || _panelParentOverCurre === void 0 ? void 0 : _panelParentOverCurre.content.size) === 2;
924
+ var sliceContainsCodeblockOrMedia = false;
925
+ var codeBlockAndMediaNodes = ['codeBlock', 'mediaSingle', 'mediaGroup'];
926
+ slice.content.forEach(function (child) {
927
+ if (codeBlockAndMediaNodes.includes(child.type.name)) {
928
+ sliceContainsCodeblockOrMedia = true;
929
+ }
930
+ });
931
+ var blockquoteNode = findParentNodeOfType(blockquote)(tr.selection);
922
932
  if (!isSliceContentTaskListNodes && (isSliceContentListNodes || isTargetPanelEmpty)) {
923
933
  insertSliceForLists({
924
934
  tr: tr,
925
935
  slice: slice,
926
936
  schema: schema
927
937
  });
938
+ } else if (
939
+ // If nesting media or codeblock in blockquote is not supported
940
+ // we want to insert slice after the blockquote.
941
+ isInsideBlockQuote(state) && sliceContainsCodeblockOrMedia && !isNestingMediaOrCodeblockSupported && blockquoteNode) {
942
+ tr = safeInsert(slice.content, blockquoteNode.start + blockquoteNode.node.nodeSize - 1)(tr);
928
943
  } else if (noNeedForSafeInsert) {
929
944
  var _firstChildOfSlice$ty2, _firstChildOfSlice$co, _firstChildOfSlice$co2;
930
945
  if ((firstChildOfSlice === null || firstChildOfSlice === void 0 || (_firstChildOfSlice$ty2 = firstChildOfSlice.type) === null || _firstChildOfSlice$ty2 === void 0 ? void 0 : _firstChildOfSlice$ty2.name) === 'blockquote' && firstChildOfSlice !== null && firstChildOfSlice !== void 0 && (_firstChildOfSlice$co = firstChildOfSlice.content.firstChild) !== null && _firstChildOfSlice$co !== void 0 && _firstChildOfSlice$co.type.name && ['bulletList', 'orderedList', 'mediaSingle'].includes(firstChildOfSlice === null || firstChildOfSlice === void 0 || (_firstChildOfSlice$co2 = firstChildOfSlice.content.firstChild) === null || _firstChildOfSlice$co2 === void 0 ? void 0 : _firstChildOfSlice$co2.type.name)) {
@@ -302,11 +302,11 @@ export var handleMarkdownWithAnalytics = function handleMarkdownWithAnalytics(vi
302
302
  type: PasteTypes.markdown
303
303
  }))(handleMarkdown(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c3 = pluginInjectionApi.card) === null || _pluginInjectionApi$c3 === void 0 || (_pluginInjectionApi$c3 = _pluginInjectionApi$c3.actions) === null || _pluginInjectionApi$c3 === void 0 ? void 0 : _pluginInjectionApi$c3.queueCardsFromChangedTr));
304
304
  };
305
- export var handleRichTextWithAnalytics = function handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi) {
305
+ export var handleRichTextWithAnalytics = function handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported) {
306
306
  var _pluginInjectionApi$a4, _pluginInjectionApi$c4;
307
307
  return injectAnalyticsPayloadBeforeCommand(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a4 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a4 === void 0 ? void 0 : _pluginInjectionApi$a4.actions)(createPasteAnalyticsPayloadBySelection(event, slice, {
308
308
  type: PasteTypes.richText
309
- }, pluginInjectionApi))(handleRichText(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c4 = pluginInjectionApi.card) === null || _pluginInjectionApi$c4 === void 0 || (_pluginInjectionApi$c4 = _pluginInjectionApi$c4.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr));
309
+ }, pluginInjectionApi))(handleRichText(slice, pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c4 = pluginInjectionApi.card) === null || _pluginInjectionApi$c4 === void 0 || (_pluginInjectionApi$c4 = _pluginInjectionApi$c4.actions) === null || _pluginInjectionApi$c4 === void 0 ? void 0 : _pluginInjectionApi$c4.queueCardsFromChangedTr, isNestingMediaOrCodeblockSupported));
310
310
  };
311
311
  var injectAnalyticsPayloadBeforeCommand = function injectAnalyticsPayloadBeforeCommand(editorAnalyticsAPI) {
312
312
  return function (createPayloadByTransaction) {
@@ -160,8 +160,11 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
160
160
  /**
161
161
  * We want to return false for external copied image to allow
162
162
  * it to be uploaded by the client.
163
+ *
164
+ * Scenario where we are pasting an external image inside a block quote
165
+ * is skipped and handled in handleRichText
163
166
  */
164
- if (htmlContainsSingleFile(html)) {
167
+ if (htmlContainsSingleFile(html) && !isInsideBlockQuote(view.state)) {
165
168
  return true;
166
169
  }
167
170
 
@@ -357,7 +360,7 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
357
360
 
358
361
  // finally, handle rich-text copy-paste
359
362
  if (isRichText || isNestedMarkdownTable) {
360
- var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$f, _pluginInjectionApi$l;
363
+ var _pluginInjectionApi$c2, _pluginInjectionApi$e2, _pluginInjectionApi$f, _pluginInjectionApi$l, _pluginInjectionApi$f2;
361
364
  // linkify the text where possible
362
365
  slice = linkifyContent(state.schema)(slice);
363
366
  if (handlePasteLinkOnSelectedTextWithAnalytics(editorAnalyticsAPI)(view, event, slice, PasteTypes.richText)(state, dispatch)) {
@@ -434,7 +437,8 @@ export function createPlugin(schema, dispatchAnalyticsEvent, dispatch, featureFl
434
437
  if (handlePasteNonNestableBlockNodesIntoListWithAnalytics(editorAnalyticsAPI)(view, event, slice)(state, dispatch)) {
435
438
  return true;
436
439
  }
437
- return handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi)(state, dispatch);
440
+ var isNestingMediaOrCodeblockSupported = (pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$f2 = pluginInjectionApi.featureFlags) === null || _pluginInjectionApi$f2 === void 0 || (_pluginInjectionApi$f2 = _pluginInjectionApi$f2.sharedState.currentState()) === null || _pluginInjectionApi$f2 === void 0 ? void 0 : _pluginInjectionApi$f2.nestMediaAndCodeblockInQuote) || fg('editor_nest_media_and_codeblock_in_quotes_jira');
441
+ return handleRichTextWithAnalytics(view, event, slice, pluginInjectionApi, isNestingMediaOrCodeblockSupported)(state, dispatch);
438
442
  }
439
443
  return false;
440
444
  },
@@ -9,7 +9,8 @@ export var updateContentMoved = function updateContentMoved(nextState, nextActio
9
9
  var data = {
10
10
  currentActions: [].concat(_toConsumableArray(contentMoved.currentActions), [nextAction]),
11
11
  size: (nextState === null || nextState === void 0 ? void 0 : nextState.size) || contentMoved.size,
12
- nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName
12
+ nodeName: nextState === null || nextState === void 0 ? void 0 : nextState.nodeName,
13
+ nodeDepth: nextState === null || nextState === void 0 ? void 0 : nextState.nodeDepth
13
14
  };
14
15
  return {
15
16
  type: MoveAnalyticPluginTypes.UpdateMovedAction,
@@ -1,10 +1,14 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
1
4
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
5
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
3
7
  import { resetContentMoved, resetContentMovedTransform, updateContentMoved } from './commands';
4
8
  import { createPluginState, getPluginState } from './plugin-factory';
5
9
  import { pluginKey } from './plugin-key';
6
10
  import { defaultState } from './types';
7
- import { isBlockNodeWithoutTable, isCursorSelectionAndInsideTopLevelNode, isEntireTopLevelBlockquoteSelected, isEntireTopLevelHeadingOrParagraphSelected, isExcludedNode, isInlineNode, isNestedInlineNode, isNestedTable, isNodeSelection, isTextSelection, isValidNodeName } from './utils';
11
+ import { getParentNodeDepth, isBlockNodeWithoutTable, isCursorSelectionAndInsideTopLevelNode, isCursorSelectionAtTopLevel, isEntireNestedParagraphOrHeadingSelected, isEntireTopLevelBlockquoteSelected, isEntireTopLevelHeadingOrParagraphSelected, isExcludedNode, isInlineNode, isNestedInlineNode, isNestedInTable, isNestedTable, isNodeSelection, isTextSelection, isValidNodeName } from './utils';
8
12
 
9
13
  // This plugin exists only in FullPage/FullWidth Editor and is used to register an event that tells us
10
14
  // that a user cut and than pasted a node. This order of actions could be considered an alternative
@@ -37,8 +41,12 @@ export var createPlugin = function createPlugin(dispatch, editorAnalyticsAPI) {
37
41
  var content = slice.content,
38
42
  size = slice.size;
39
43
  var nodeName = (_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type.name;
40
- // We should not account for pastes that go inside another node and create nested content as DnD can't do it.
41
- if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isValidNodeName(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !isCursorSelectionAndInsideTopLevelNode(state.selection)) {
44
+
45
+ // We should not account for pastes that go inside another node and create nested content as DnD can't do it when nested-dnd is disabled
46
+ if ((!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isValidNodeName(contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName, nodeName) || size !== (contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.size) || !isCursorSelectionAndInsideTopLevelNode(state.selection)) && editorExperiment('nested-dnd', false)) {
47
+ return;
48
+ }
49
+ if (!nodeName || !(contentMoved !== null && contentMoved !== void 0 && contentMoved.nodeName) || !isCursorSelectionAtTopLevel(state.selection) && editorExperiment('nested-dnd', true)) {
42
50
  return;
43
51
  }
44
52
  var tr = state.tr;
@@ -47,9 +55,12 @@ export var createPlugin = function createPlugin(dispatch, editorAnalyticsAPI) {
47
55
  actionSubject: ACTION_SUBJECT.DOCUMENT,
48
56
  actionSubjectId: ACTION_SUBJECT_ID.NODE,
49
57
  eventType: EVENT_TYPE.TRACK,
50
- attributes: {
51
- nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName // keep nodeName from copied slice
52
- }
58
+ attributes: _objectSpread({
59
+ nodeType: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeName
60
+ }, editorExperiment('nested-dnd', true) && {
61
+ nodeDepth: contentMoved === null || contentMoved === void 0 ? void 0 : contentMoved.nodeDepth,
62
+ destinationNodeDepth: getParentNodeDepth(state.selection)
63
+ })
53
64
  })(tr);
54
65
 
55
66
  // reset to default state
@@ -79,6 +90,7 @@ export var createPlugin = function createPlugin(dispatch, editorAnalyticsAPI) {
79
90
  resetState = true;
80
91
  }
81
92
  var nodeName = ((_content$firstChild2 = content.firstChild) === null || _content$firstChild2 === void 0 ? void 0 : _content$firstChild2.type.name) || '';
93
+
82
94
  // Some nodes are not relevant as they are parts of nodes, not whole nodes (like tableCell, tableHeader instead of table node)
83
95
  // Some nodes like lists, taskList(item), decisionList(item) requires tricky checks that we want to avoid doing.
84
96
  // These nodes were added to excludedNodes array.
@@ -88,42 +100,50 @@ export var createPlugin = function createPlugin(dispatch, editorAnalyticsAPI) {
88
100
  var selection = state.selection;
89
101
  // DnD can't drag part of text in a paragraph/heading. DnD will select the whole node with all the text inside.
90
102
  // So we are only interested in cut slices that contain the entire node, not just a part of it.
91
- if (!resetState && nodeName === 'paragraph' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName)) {
103
+ if (!resetState && nodeName === 'paragraph' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName) && editorExperiment('nested-dnd', false)) {
92
104
  resetState = true;
93
105
  }
94
- if (!resetState && nodeName === 'heading' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName)) {
106
+ if (!resetState && nodeName === 'heading' && !isEntireTopLevelHeadingOrParagraphSelected(selection, nodeName) && editorExperiment('nested-dnd', false)) {
107
+ resetState = true;
108
+ }
109
+ if (!resetState && !isEntireNestedParagraphOrHeadingSelected(selection) && editorExperiment('nested-dnd', true)) {
95
110
  resetState = true;
96
111
  }
97
112
 
98
113
  // DnD can't drag just one paragraph (when blockquote contains multiple paragraphs) or just a part of a paragraph in the blockquote.
99
114
  // DnD will select and drag the whole blockquote. So we need to register cut events that have the entire blockquote too.
100
- if (!resetState && nodeName === 'blockquote' && !isEntireTopLevelBlockquoteSelected(state)) {
115
+ if (!resetState && nodeName === 'blockquote' && !isEntireTopLevelBlockquoteSelected(state) && editorExperiment('nested-dnd', false)) {
101
116
  resetState = true;
102
117
  }
103
118
  if (!resetState && isInlineNode(nodeName) && isNestedInlineNode(selection)) {
104
119
  resetState = true;
105
120
  }
106
- if (!resetState && nodeName === 'table' && isNestedTable(selection)) {
121
+ if (!resetState && nodeName === 'table' && isNestedTable(selection) && editorExperiment('nested-dnd', false)) {
122
+ resetState = true;
123
+ }
124
+ if (!resetState && isNestedInTable(state) && editorExperiment('nested-dnd', true) && editorExperiment('table-nested-dnd', false)) {
107
125
  resetState = true;
108
126
  }
109
127
  var isBlockNode = isBlockNodeWithoutTable(nodeName);
110
- if (!resetState && isNodeSelection(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' // checks that the block node is not a topLevel node
128
+ if (!resetState && isNodeSelection(selection) && isBlockNode && selection.$anchor.node().type.name !== 'doc' && editorExperiment('nested-dnd', false) // checks that the block node is not a topLevel node
111
129
  ) {
112
130
  resetState = true;
113
131
  }
114
132
 
115
133
  // Some blockNodes can have text inside of them cut, in that case TextSelection occurs, we don't need to track
116
134
  // these cut events.
117
- if (!resetState && isTextSelection(selection) && isBlockNode) {
135
+ if (!resetState && isTextSelection(selection) && isBlockNode && editorExperiment('nested-dnd', false)) {
118
136
  resetState = true;
119
137
  }
120
138
  if (resetState) {
121
139
  resetContentMoved()(state, dispatch);
122
140
  } else {
123
- updateContentMoved({
141
+ updateContentMoved(_objectSpread({
124
142
  size: size,
125
143
  nodeName: nodeName
126
- }, 'contentCut')(state, dispatch);
144
+ }, editorExperiment('nested-dnd', true) && {
145
+ nodeDepth: getParentNodeDepth(selection)
146
+ }), 'contentCut')(state, dispatch);
127
147
  }
128
148
  isCutEvent = false;
129
149
  return slice;
@@ -8,6 +8,7 @@ export var defaultState = {
8
8
  contentMoved: {
9
9
  nodeName: undefined,
10
10
  size: undefined,
11
+ nodeDepth: undefined,
11
12
  currentActions: []
12
13
  }
13
14
  };
@@ -1,6 +1,9 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
1
2
  import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state';
2
- import { findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
3
- var excludedNodes = ['caption', 'layoutColumn', 'listItem', 'nestedExpand', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'media', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'bulletList', 'orderedList', 'taskList', 'taskItem', 'decisionList', 'decisionItem'];
3
+ import { findParentNodeClosestToPos, findParentNodeOfTypeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
4
+ import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
5
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
+ var excludedNodes = ['caption', 'layoutColumn', 'listItem', 'tableHeader', 'tableCell', 'tableRow', 'text', 'placeholder', 'unsupportedBlock', 'unsupportedInline', 'hardBreak', 'confluenceUnsupportedBlock', 'confluenceUnsupportedInline', 'taskItem', 'decisionItem'].concat(_toConsumableArray(editorExperiment('nested-dnd', false) ? ['bulletList', 'orderedList', 'taskList', 'decisionList', 'nestedExpand', 'media'] : []));
4
7
  export var isExcludedNode = function isExcludedNode(nodeName) {
5
8
  return excludedNodes.includes(nodeName);
6
9
  };
@@ -13,6 +16,15 @@ export var isCursorSelectionAndInsideTopLevelNode = function isCursorSelectionAn
13
16
  }
14
17
  return true;
15
18
  };
19
+ export var isCursorSelectionAtTopLevel = function isCursorSelectionAtTopLevel(selection) {
20
+ var from = selection.from,
21
+ to = selection.to,
22
+ $from = selection.$from;
23
+ if (from !== to) {
24
+ return false;
25
+ }
26
+ return $from.parentOffset === 0;
27
+ };
16
28
  var inlineNodes = ['emoji', 'date', 'status', 'mention', 'mediaInline', 'inlineCard', 'inlineExtension'];
17
29
  export var isInlineNode = function isInlineNode(nodeName) {
18
30
  return inlineNodes.includes(nodeName);
@@ -46,6 +58,28 @@ export var isNestedTable = function isNestedTable(selection) {
46
58
  var parentNode = selection.$anchor.node(1);
47
59
  return parentNode && parentNodes.includes(parentNode.type.name);
48
60
  };
61
+ export var isNestedInTable = function isNestedInTable(state) {
62
+ var schema = state.schema,
63
+ selection = state.selection;
64
+ if (selection instanceof CellSelection) {
65
+ return false;
66
+ }
67
+ var table = schema.nodes.table;
68
+ var tableNode = findParentNodeOfTypeClosestToPos(selection.$from, table);
69
+ if (!tableNode) {
70
+ return false;
71
+ }
72
+ return true;
73
+ };
74
+ export var getParentNodeDepth = function getParentNodeDepth(selection) {
75
+ var parentNode = findParentNodeClosestToPos(selection.$from, function () {
76
+ return true;
77
+ });
78
+ if (!parentNode) {
79
+ return 0;
80
+ }
81
+ return parentNode.node.type.name === 'heading' || parentNode.node.type.name === 'paragraph' ? parentNode.depth - 1 : parentNode.depth;
82
+ };
49
83
  var getPastedNameOfInlineNode = function getPastedNameOfInlineNode(nodeName) {
50
84
  if (inlineNodes.includes(nodeName)) {
51
85
  return 'paragraph';
@@ -111,4 +145,9 @@ export var isEntireTopLevelBlockquoteSelected = function isEntireTopLevelBlockqu
111
145
  }
112
146
  });
113
147
  return selectedNodesCount === blockquoteNode.node.childCount;
148
+ };
149
+ export var isEntireNestedParagraphOrHeadingSelected = function isEntireNestedParagraphOrHeadingSelected(selection) {
150
+ var $from = selection.$from,
151
+ $to = selection.$to;
152
+ return $from.textOffset === 0 && $to.textOffset === 0;
114
153
  };
@@ -51,7 +51,7 @@ export declare function handleParagraphBlockMarks(state: EditorState, slice: Sli
51
51
  * ┗━p -> "two"
52
52
  */
53
53
  export declare function flattenNestedListInSlice(slice: Slice): Slice;
54
- export declare function handleRichText(slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined): Command;
54
+ export declare function handleRichText(slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined, isNestingMediaOrCodeblockSupported?: boolean): Command;
55
55
  export declare function handlePasteIntoCaption(slice: Slice): Command;
56
56
  export declare const handleSelectedTable: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (slice: Slice) => Command;
57
57
  export declare function checkTaskListInList(state: EditorState, slice: Slice): boolean;
@@ -28,7 +28,7 @@ export declare const handleCodeBlockWithAnalytics: (editorAnalyticsAPI: EditorAn
28
28
  export declare const handleMediaSingleWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice, type: PasteType, insertMediaAsMediaSingle: InsertMediaAsMediaSingle | undefined) => Command;
29
29
  export declare const handlePastePreservingMarksWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, type: PasteType, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
30
30
  export declare const handleMarkdownWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
31
- export declare const handleRichTextWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
31
+ export declare const handleRichTextWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined, isNestingMediaOrCodeblockSupported: boolean) => Command;
32
32
  export declare const handlePastePanelOrDecisionIntoListWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice, findRootParentListNode: FindRootParentListNode | undefined) => Command;
33
33
  export declare const handlePasteNonNestableBlockNodesIntoListWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice) => Command;
34
34
  export declare const handleExpandWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined, isNestingExpandsSupported: boolean) => (view: EditorView, event: ClipboardEvent, slice: Slice) => Command;
@@ -4,6 +4,7 @@ export type ContentMoved = {
4
4
  nodeName?: NodeName;
5
5
  size?: number;
6
6
  currentActions: Array<ActionType>;
7
+ nodeDepth?: number;
7
8
  };
8
9
  export type MoveAnalyticsPluginState = {
9
10
  contentMoved: ContentMoved;
@@ -1,12 +1,16 @@
1
1
  import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
2
2
  export declare const isExcludedNode: (nodeName: string) => boolean;
3
3
  export declare const isCursorSelectionAndInsideTopLevelNode: (selection: Selection) => boolean;
4
+ export declare const isCursorSelectionAtTopLevel: (selection: Selection) => boolean;
4
5
  export declare const isInlineNode: (nodeName: string) => boolean;
5
6
  export declare const isNestedInlineNode: (selection: Selection) => boolean;
6
7
  export declare const isBlockNodeWithoutTable: (nodeName: string) => boolean;
7
8
  export declare const isNestedTable: (selection: Selection) => boolean;
9
+ export declare const isNestedInTable: (state: EditorState) => boolean;
10
+ export declare const getParentNodeDepth: (selection: Selection) => number;
8
11
  export declare const isValidNodeName: (copiedNodeName: string, pastedNodeName: string) => boolean;
9
12
  export declare const isTextSelection: (selection: Selection) => boolean;
10
13
  export declare const isNodeSelection: (selection: Selection) => boolean;
11
14
  export declare const isEntireTopLevelHeadingOrParagraphSelected: (selection: Selection, nodeName: 'paragraph' | 'heading') => boolean | undefined;
12
15
  export declare const isEntireTopLevelBlockquoteSelected: (state: EditorState) => boolean;
16
+ export declare const isEntireNestedParagraphOrHeadingSelected: (selection: Selection) => boolean;
@@ -51,7 +51,7 @@ export declare function handleParagraphBlockMarks(state: EditorState, slice: Sli
51
51
  * ┗━p -> "two"
52
52
  */
53
53
  export declare function flattenNestedListInSlice(slice: Slice): Slice;
54
- export declare function handleRichText(slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined): Command;
54
+ export declare function handleRichText(slice: Slice, queueCardsFromChangedTr: QueueCardsFromTransactionAction | undefined, isNestingMediaOrCodeblockSupported?: boolean): Command;
55
55
  export declare function handlePasteIntoCaption(slice: Slice): Command;
56
56
  export declare const handleSelectedTable: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (slice: Slice) => Command;
57
57
  export declare function checkTaskListInList(state: EditorState, slice: Slice): boolean;
@@ -28,7 +28,7 @@ export declare const handleCodeBlockWithAnalytics: (editorAnalyticsAPI: EditorAn
28
28
  export declare const handleMediaSingleWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice, type: PasteType, insertMediaAsMediaSingle: InsertMediaAsMediaSingle | undefined) => Command;
29
29
  export declare const handlePastePreservingMarksWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, type: PasteType, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
30
30
  export declare const handleMarkdownWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
31
- export declare const handleRichTextWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined) => Command;
31
+ export declare const handleRichTextWithAnalytics: (view: EditorView, event: ClipboardEvent, slice: Slice, pluginInjectionApi: ExtractInjectionAPI<PastePlugin> | undefined, isNestingMediaOrCodeblockSupported: boolean) => Command;
32
32
  export declare const handlePastePanelOrDecisionIntoListWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice, findRootParentListNode: FindRootParentListNode | undefined) => Command;
33
33
  export declare const handlePasteNonNestableBlockNodesIntoListWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined) => (view: EditorView, event: ClipboardEvent, slice: Slice) => Command;
34
34
  export declare const handleExpandWithAnalytics: (editorAnalyticsAPI: EditorAnalyticsAPI | undefined, isNestingExpandsSupported: boolean) => (view: EditorView, event: ClipboardEvent, slice: Slice) => Command;
@@ -4,6 +4,7 @@ export type ContentMoved = {
4
4
  nodeName?: NodeName;
5
5
  size?: number;
6
6
  currentActions: Array<ActionType>;
7
+ nodeDepth?: number;
7
8
  };
8
9
  export type MoveAnalyticsPluginState = {
9
10
  contentMoved: ContentMoved;
@@ -1,12 +1,16 @@
1
1
  import type { EditorState, Selection } from '@atlaskit/editor-prosemirror/state';
2
2
  export declare const isExcludedNode: (nodeName: string) => boolean;
3
3
  export declare const isCursorSelectionAndInsideTopLevelNode: (selection: Selection) => boolean;
4
+ export declare const isCursorSelectionAtTopLevel: (selection: Selection) => boolean;
4
5
  export declare const isInlineNode: (nodeName: string) => boolean;
5
6
  export declare const isNestedInlineNode: (selection: Selection) => boolean;
6
7
  export declare const isBlockNodeWithoutTable: (nodeName: string) => boolean;
7
8
  export declare const isNestedTable: (selection: Selection) => boolean;
9
+ export declare const isNestedInTable: (state: EditorState) => boolean;
10
+ export declare const getParentNodeDepth: (selection: Selection) => number;
8
11
  export declare const isValidNodeName: (copiedNodeName: string, pastedNodeName: string) => boolean;
9
12
  export declare const isTextSelection: (selection: Selection) => boolean;
10
13
  export declare const isNodeSelection: (selection: Selection) => boolean;
11
14
  export declare const isEntireTopLevelHeadingOrParagraphSelected: (selection: Selection, nodeName: 'paragraph' | 'heading') => boolean | undefined;
12
15
  export declare const isEntireTopLevelBlockquoteSelected: (state: EditorState) => boolean;
16
+ export declare const isEntireNestedParagraphOrHeadingSelected: (selection: Selection) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "1.12.3",
3
+ "version": "1.12.5",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  ".": "./src/index.ts"
32
32
  },
33
33
  "dependencies": {
34
- "@atlaskit/editor-common": "^89.1.0",
34
+ "@atlaskit/editor-common": "^91.0.0",
35
35
  "@atlaskit/editor-markdown-transformer": "^5.12.0",
36
36
  "@atlaskit/editor-plugin-analytics": "^1.8.0",
37
37
  "@atlaskit/editor-plugin-annotation": "^1.19.0",
@@ -39,13 +39,14 @@
39
39
  "@atlaskit/editor-plugin-card": "^3.0.0",
40
40
  "@atlaskit/editor-plugin-feature-flags": "^1.2.0",
41
41
  "@atlaskit/editor-plugin-list": "^3.8.0",
42
- "@atlaskit/editor-plugin-media": "^1.31.0",
42
+ "@atlaskit/editor-plugin-media": "^1.32.0",
43
43
  "@atlaskit/editor-plugin-mentions": "^2.6.0",
44
44
  "@atlaskit/editor-prosemirror": "6.0.0",
45
45
  "@atlaskit/editor-tables": "^2.8.0",
46
46
  "@atlaskit/media-client": "^28.0.0",
47
47
  "@atlaskit/media-common": "^11.4.0",
48
48
  "@atlaskit/platform-feature-flags": "^0.3.0",
49
+ "@atlaskit/tmp-editor-statsig": "*",
49
50
  "@babel/runtime": "^7.0.0",
50
51
  "lodash": "^4.17.21",
51
52
  "uuid": "^3.1.0"
@@ -56,7 +57,7 @@
56
57
  "devDependencies": {
57
58
  "@af/visual-regression": "*",
58
59
  "@atlaskit/adf-schema": "^40.9.0",
59
- "@atlaskit/editor-plugin-block-type": "^3.14.0",
60
+ "@atlaskit/editor-plugin-block-type": "^3.15.0",
60
61
  "@atlaskit/editor-plugin-history": "^1.3.0",
61
62
  "@atlaskit/editor-plugin-type-ahead": "^1.8.0",
62
63
  "@atlaskit/ssr": "*",
@@ -119,6 +120,9 @@
119
120
  },
120
121
  "platform_editor_nest_nested_expand_in_expand_jira": {
121
122
  "type": "boolean"
123
+ },
124
+ "editor_nest_media_and_codeblock_in_quotes_jira": {
125
+ "type": "boolean"
122
126
  }
123
127
  }
124
128
  }
package/.eslintrc.js DELETED
@@ -1,18 +0,0 @@
1
- module.exports = {
2
- rules: {
3
- '@typescript-eslint/no-duplicate-imports': 'error',
4
- '@typescript-eslint/no-explicit-any': 'error',
5
- '@typescript-eslint/ban-types': [
6
- 'error',
7
- {
8
- types: {
9
- 'React.FC':
10
- 'Please use types directly on props instead, and explicitly define children if required',
11
- 'React.FunctionalComponent':
12
- 'Please use types directly on props instead, and explicitly define children if required',
13
- },
14
- extendDefaults: false,
15
- },
16
- ],
17
- },
18
- };