@atlaskit/editor-plugin-paste 13.2.2 → 13.3.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,23 @@
1
1
  # @atlaskit/editor-plugin-paste
2
2
 
3
+ ## 13.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`3f5a55729ccfa`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/3f5a55729ccfa) -
8
+ Fix over-nesting (auto-indent) when pasting a task/action into a bullet list item under the
9
+ flexible list schema (EDITOR-6738), behind kill switch platform_editor_flexible_list_kill_switch_1
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
15
+ ## 13.2.3
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+
3
21
  ## 13.2.2
4
22
 
5
23
  ### Patch Changes
@@ -13,6 +13,7 @@ var _model = require("@atlaskit/editor-prosemirror/model");
13
13
  var _state = require("@atlaskit/editor-prosemirror/state");
14
14
  var _transform = require("@atlaskit/editor-prosemirror/transform");
15
15
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
17
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
17
18
  var _index = require("../index");
18
19
  var _lists = require("./lists");
@@ -139,10 +140,35 @@ function insertSliceForTaskInsideList(_ref5) {
139
140
  var tr = _ref5.tr,
140
141
  slice = _ref5.slice;
141
142
  var schema = tr.doc.type.schema;
142
- //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
143
- var selectionBeforeReplace = tr.selection.from;
144
- tr.replaceSelection(new _model.Slice(_model.Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
145
- var nextSelection = _state.Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
146
- tr.setSelection(nextSelection);
147
- tr.replaceSelection(slice);
143
+
144
+ // The corrected insertion path only applies for the flexible list indentation dogfooding audience
145
+ // (`platform_editor_flexible_list_indentation`), and the kill switch protects that audience from
146
+ // regressions. Fall back to the legacy path when the experiment is OFF or the kill switch is ON.
147
+ if (!(0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true) || (0, _platformFeatureFlags.fg)('platform_editor_flexible_list_kill_switch_1')) {
148
+ //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
149
+ var selectionBeforeReplace = tr.selection.from;
150
+ tr.replaceSelection(new _model.Slice(_model.Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
151
+ var nextSelection = _state.Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
152
+ tr.setSelection(nextSelection);
153
+ tr.replaceSelection(slice);
154
+ return tr;
155
+ }
156
+
157
+ // Collapse redundant nested taskList wrappers from the slice context, always keeping a single
158
+ // outermost taskList so the content can be safely inserted as a sibling of the list item's
159
+ // paragraph (a listItem cannot directly contain bare taskItems).
160
+ var taskList = schema.nodes.taskList;
161
+ var content = slice.content;
162
+ while (content.childCount === 1 && ((_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type) === taskList && ((_content$firstChild$f = content.firstChild.firstChild) === null || _content$firstChild$f === void 0 ? void 0 : _content$firstChild$f.type) === taskList) {
163
+ var _content$firstChild, _content$firstChild$f;
164
+ content = content.firstChild.content;
165
+ }
166
+
167
+ // safeInsert keeps the task structure intact at the closest schema-valid position, instead of
168
+ // replacing into an empty taskItem which would add an extra taskList level.
169
+ tr = (0, _utils2.safeInsert)(content, tr.selection.$to.pos)(tr);
170
+ updateSelectionAfterReplace({
171
+ tr: tr
172
+ });
173
+ return tr;
148
174
  }
@@ -107,7 +107,7 @@ function handleMention(slice, schema) {
107
107
  }
108
108
  function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChangedTr) {
109
109
  return function (state, dispatch) {
110
- var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con;
110
+ var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con, _transformedSlice$con2;
111
111
  var schema = state.schema,
112
112
  selection = state.tr.selection;
113
113
  var codeMark = schema.marks.code,
@@ -187,9 +187,21 @@ function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChangedTr) {
187
187
  // or the slice's first node is a paragraph,
188
188
  // then we can replace the selection with our slice.
189
189
  var pastingIntoExtendedPanel = selectionIsPanel && panel.validContent(transformedSlice.content);
190
- if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
190
+
191
+ // A task slice carrying its own open taskList wrapper would, on a plain replaceSelection,
192
+ // nest that wrapper inside the current item and add an extra taskList level. Unwrapping one
193
+ // level keeps the pasted task(s) at the same level. Guarded by a kill switch.
194
+ var isOpenTaskListSlice = ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === taskList && transformedSlice.openStart >= 2 && transformedSlice.openEnd >= 1;
195
+ // Restrict the unwrap to a taskItem destination: its task content is schema-invalid inside a
196
+ // decisionItem (decisionList only accepts decisionItem), so decisionItem must fall through to
197
+ // the normal replaceSelection path.
198
+ var pastingIntoTaskItem = (0, _utils2.hasParentNodeOfType)([taskItem])(selection);
199
+ if ((0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true) && !(0, _platformFeatureFlags.fg)('platform_editor_flexible_list_kill_switch_1') && isOpenTaskListSlice && pastingIntoTaskItem && !selectionIsPanel) {
200
+ var unwrappedSlice = new _model.Slice(transformedSlice.content.firstChild.content, transformedSlice.openStart - 1, transformedSlice.openEnd - 1);
201
+ tr.replaceSelection(unwrappedSlice).scrollIntoView();
202
+ } else if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
191
203
  // Whole codeblock node has reverse slice depths.
192
- transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === paragraph) {
204
+ transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con2 = transformedSlice.content.firstChild) === null || _transformedSlice$con2 === void 0 ? void 0 : _transformedSlice$con2.type) === paragraph) {
193
205
  tr.replaceSelection(transformedSlice).scrollIntoView();
194
206
  } else {
195
207
  var isWholeContentSelected = selection.$from.pos === selection.$from.start() && selection.$to.end() === selection.$to.pos;
@@ -208,14 +220,14 @@ function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChangedTr) {
208
220
  tr.replaceSelection(transformedSlice).scrollIntoView();
209
221
  }
210
222
  } else {
211
- var _transformedSlice$con2;
223
+ var _transformedSlice$con3;
212
224
  if (pastingIntoExtendedPanel && isWholeContentSelected) {
213
225
  // if the entire panel content is selected, doing a replaceSelection removes the panel as well. Hence we do delete followed by safeInsert
214
226
  tr.delete(selection.$from.pos, selection.$to.pos);
215
227
  }
216
228
  // This maintains both the selection (destination) and the slice (paste content).
217
229
  (0, _utils2.safeInsert)(transformedSlice.content)(tr).scrollIntoView();
218
- if (((_transformedSlice$con2 = transformedSlice.content.lastChild) === null || _transformedSlice$con2 === void 0 || (_transformedSlice$con2 = _transformedSlice$con2.type) === null || _transformedSlice$con2 === void 0 ? void 0 : _transformedSlice$con2.name) === 'rule') {
230
+ if (((_transformedSlice$con3 = transformedSlice.content.lastChild) === null || _transformedSlice$con3 === void 0 || (_transformedSlice$con3 = _transformedSlice$con3.type) === null || _transformedSlice$con3 === void 0 ? void 0 : _transformedSlice$con3.name) === 'rule') {
219
231
  tr.setSelection(_state.TextSelection.near(tr.doc.resolve(tr.selection.$from.pos + transformedSlice.content.size)));
220
232
  } else {
221
233
  // safeInsert doesn't set correct cursor position
@@ -1231,8 +1243,17 @@ function handleRichText(slice, queueCardsFromChangedTr) {
1231
1243
  });
1232
1244
  if ((0, _utils.insideTableCell)(state) && (0, _utils.isInListItem)(state) && (0, _utils2.canInsert)(selection.$from, slice.content) && (0, _utils2.canInsert)(selection.$to, slice.content) || sliceHasList) {
1233
1245
  tr.replaceSelection(slice);
1246
+ } else if ((0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true) && !(0, _platformFeatureFlags.fg)('platform_editor_flexible_list_kill_switch_1') && checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1247
+ // Corrected task-into-list insertion is gated behind the flexible list indentation
1248
+ // experiment and protected by the kill switch.
1249
+ tr = (0, _edgeCases.insertSliceForTaskInsideList)({
1250
+ tr: tr,
1251
+ slice: slice
1252
+ });
1234
1253
  } else if (checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1235
- (0, _edgeCases.insertSliceForTaskInsideList)({
1254
+ // Legacy path (experiment OFF or kill switch ON): preserve the previous over-nesting
1255
+ // behaviour via the function's internal fallback.
1256
+ tr = (0, _edgeCases.insertSliceForTaskInsideList)({
1236
1257
  tr: tr,
1237
1258
  slice: slice
1238
1259
  });
@@ -3,7 +3,8 @@ import { isListNode } from '@atlaskit/editor-common/utils';
3
3
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
4
4
  import { Selection } from '@atlaskit/editor-prosemirror/state';
5
5
  import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
6
- import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
6
+ import { findParentNodeOfType, safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  import { isCursorSelectionAtTextStartOrEnd, isEmptyNode, isSelectionInsidePanel } from '../index';
9
10
  import { insertSliceAtNodeEdge, insertSliceInsideOfPanelNodeSelected, insertSliceIntoEmptyNode, insertSliceIntoRangeSelectionInsideList } from './lists';
@@ -140,10 +141,37 @@ export function insertSliceForTaskInsideList({
140
141
  const {
141
142
  schema
142
143
  } = tr.doc.type;
143
- //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
144
- const selectionBeforeReplace = tr.selection.from;
145
- tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
146
- const nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
147
- tr.setSelection(nextSelection);
148
- tr.replaceSelection(slice);
144
+
145
+ // The corrected insertion path only applies for the flexible list indentation dogfooding audience
146
+ // (`platform_editor_flexible_list_indentation`), and the kill switch protects that audience from
147
+ // regressions. Fall back to the legacy path when the experiment is OFF or the kill switch is ON.
148
+ if (!expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) || fg('platform_editor_flexible_list_kill_switch_1')) {
149
+ //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
150
+ const selectionBeforeReplace = tr.selection.from;
151
+ tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
152
+ const nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
153
+ tr.setSelection(nextSelection);
154
+ tr.replaceSelection(slice);
155
+ return tr;
156
+ }
157
+
158
+ // Collapse redundant nested taskList wrappers from the slice context, always keeping a single
159
+ // outermost taskList so the content can be safely inserted as a sibling of the list item's
160
+ // paragraph (a listItem cannot directly contain bare taskItems).
161
+ const {
162
+ taskList
163
+ } = schema.nodes;
164
+ let content = slice.content;
165
+ while (content.childCount === 1 && ((_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type) === taskList && ((_content$firstChild$f = content.firstChild.firstChild) === null || _content$firstChild$f === void 0 ? void 0 : _content$firstChild$f.type) === taskList) {
166
+ var _content$firstChild, _content$firstChild$f;
167
+ content = content.firstChild.content;
168
+ }
169
+
170
+ // safeInsert keeps the task structure intact at the closest schema-valid position, instead of
171
+ // replacing into an empty taskItem which would add an extra taskList level.
172
+ tr = safeInsert(content, tr.selection.$to.pos)(tr);
173
+ updateSelectionAfterReplace({
174
+ tr
175
+ });
176
+ return tr;
149
177
  }
@@ -67,7 +67,7 @@ export function handleMention(slice, schema) {
67
67
  }
68
68
  export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChangedTr) {
69
69
  return (state, dispatch) => {
70
- var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con;
70
+ var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con, _transformedSlice$con2;
71
71
  const {
72
72
  schema,
73
73
  tr: {
@@ -154,9 +154,21 @@ export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChange
154
154
  // or the slice's first node is a paragraph,
155
155
  // then we can replace the selection with our slice.
156
156
  const pastingIntoExtendedPanel = selectionIsPanel && panel.validContent(transformedSlice.content);
157
- if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
157
+
158
+ // A task slice carrying its own open taskList wrapper would, on a plain replaceSelection,
159
+ // nest that wrapper inside the current item and add an extra taskList level. Unwrapping one
160
+ // level keeps the pasted task(s) at the same level. Guarded by a kill switch.
161
+ const isOpenTaskListSlice = ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === taskList && transformedSlice.openStart >= 2 && transformedSlice.openEnd >= 1;
162
+ // Restrict the unwrap to a taskItem destination: its task content is schema-invalid inside a
163
+ // decisionItem (decisionList only accepts decisionItem), so decisionItem must fall through to
164
+ // the normal replaceSelection path.
165
+ const pastingIntoTaskItem = hasParentNodeOfType([taskItem])(selection);
166
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) && !fg('platform_editor_flexible_list_kill_switch_1') && isOpenTaskListSlice && pastingIntoTaskItem && !selectionIsPanel) {
167
+ const unwrappedSlice = new Slice(transformedSlice.content.firstChild.content, transformedSlice.openStart - 1, transformedSlice.openEnd - 1);
168
+ tr.replaceSelection(unwrappedSlice).scrollIntoView();
169
+ } else if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
158
170
  // Whole codeblock node has reverse slice depths.
159
- transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === paragraph) {
171
+ transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con2 = transformedSlice.content.firstChild) === null || _transformedSlice$con2 === void 0 ? void 0 : _transformedSlice$con2.type) === paragraph) {
160
172
  tr.replaceSelection(transformedSlice).scrollIntoView();
161
173
  } else {
162
174
  const isWholeContentSelected = selection.$from.pos === selection.$from.start() && selection.$to.end() === selection.$to.pos;
@@ -175,14 +187,14 @@ export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChange
175
187
  tr.replaceSelection(transformedSlice).scrollIntoView();
176
188
  }
177
189
  } else {
178
- var _transformedSlice$con2, _transformedSlice$con3;
190
+ var _transformedSlice$con3, _transformedSlice$con4;
179
191
  if (pastingIntoExtendedPanel && isWholeContentSelected) {
180
192
  // if the entire panel content is selected, doing a replaceSelection removes the panel as well. Hence we do delete followed by safeInsert
181
193
  tr.delete(selection.$from.pos, selection.$to.pos);
182
194
  }
183
195
  // This maintains both the selection (destination) and the slice (paste content).
184
196
  safeInsert(transformedSlice.content)(tr).scrollIntoView();
185
- if (((_transformedSlice$con2 = transformedSlice.content.lastChild) === null || _transformedSlice$con2 === void 0 ? void 0 : (_transformedSlice$con3 = _transformedSlice$con2.type) === null || _transformedSlice$con3 === void 0 ? void 0 : _transformedSlice$con3.name) === 'rule') {
197
+ if (((_transformedSlice$con3 = transformedSlice.content.lastChild) === null || _transformedSlice$con3 === void 0 ? void 0 : (_transformedSlice$con4 = _transformedSlice$con3.type) === null || _transformedSlice$con4 === void 0 ? void 0 : _transformedSlice$con4.name) === 'rule') {
186
198
  tr.setSelection(TextSelection.near(tr.doc.resolve(tr.selection.$from.pos + transformedSlice.content.size)));
187
199
  } else {
188
200
  // safeInsert doesn't set correct cursor position
@@ -1206,8 +1218,17 @@ export function handleRichText(slice, queueCardsFromChangedTr) {
1206
1218
  });
1207
1219
  if (insideTableCell(state) && isInListItem(state) && canInsert(selection.$from, slice.content) && canInsert(selection.$to, slice.content) || sliceHasList) {
1208
1220
  tr.replaceSelection(slice);
1221
+ } else if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) && !fg('platform_editor_flexible_list_kill_switch_1') && checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1222
+ // Corrected task-into-list insertion is gated behind the flexible list indentation
1223
+ // experiment and protected by the kill switch.
1224
+ tr = insertSliceForTaskInsideList({
1225
+ tr,
1226
+ slice
1227
+ });
1209
1228
  } else if (checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1210
- insertSliceForTaskInsideList({
1229
+ // Legacy path (experiment OFF or kill switch ON): preserve the previous over-nesting
1230
+ // behaviour via the function's internal fallback.
1231
+ tr = insertSliceForTaskInsideList({
1211
1232
  tr,
1212
1233
  slice
1213
1234
  });
@@ -3,7 +3,8 @@ import { isListNode } from '@atlaskit/editor-common/utils';
3
3
  import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
4
4
  import { Selection } from '@atlaskit/editor-prosemirror/state';
5
5
  import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
6
- import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
6
+ import { findParentNodeOfType, safeInsert } from '@atlaskit/editor-prosemirror/utils';
7
+ import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  import { isCursorSelectionAtTextStartOrEnd, isEmptyNode, isSelectionInsidePanel } from '../index';
9
10
  import { insertSliceAtNodeEdge, insertSliceInsideOfPanelNodeSelected, insertSliceIntoEmptyNode, insertSliceIntoRangeSelectionInsideList } from './lists';
@@ -130,10 +131,35 @@ export function insertSliceForTaskInsideList(_ref5) {
130
131
  var tr = _ref5.tr,
131
132
  slice = _ref5.slice;
132
133
  var schema = tr.doc.type.schema;
133
- //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
134
- var selectionBeforeReplace = tr.selection.from;
135
- tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
136
- var nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
137
- tr.setSelection(nextSelection);
138
- tr.replaceSelection(slice);
134
+
135
+ // The corrected insertion path only applies for the flexible list indentation dogfooding audience
136
+ // (`platform_editor_flexible_list_indentation`), and the kill switch protects that audience from
137
+ // regressions. Fall back to the legacy path when the experiment is OFF or the kill switch is ON.
138
+ if (!expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) || fg('platform_editor_flexible_list_kill_switch_1')) {
139
+ //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem.
140
+ var selectionBeforeReplace = tr.selection.from;
141
+ tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0));
142
+ var nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1));
143
+ tr.setSelection(nextSelection);
144
+ tr.replaceSelection(slice);
145
+ return tr;
146
+ }
147
+
148
+ // Collapse redundant nested taskList wrappers from the slice context, always keeping a single
149
+ // outermost taskList so the content can be safely inserted as a sibling of the list item's
150
+ // paragraph (a listItem cannot directly contain bare taskItems).
151
+ var taskList = schema.nodes.taskList;
152
+ var content = slice.content;
153
+ while (content.childCount === 1 && ((_content$firstChild = content.firstChild) === null || _content$firstChild === void 0 ? void 0 : _content$firstChild.type) === taskList && ((_content$firstChild$f = content.firstChild.firstChild) === null || _content$firstChild$f === void 0 ? void 0 : _content$firstChild$f.type) === taskList) {
154
+ var _content$firstChild, _content$firstChild$f;
155
+ content = content.firstChild.content;
156
+ }
157
+
158
+ // safeInsert keeps the task structure intact at the closest schema-valid position, instead of
159
+ // replacing into an empty taskItem which would add an extra taskList level.
160
+ tr = safeInsert(content, tr.selection.$to.pos)(tr);
161
+ updateSelectionAfterReplace({
162
+ tr: tr
163
+ });
164
+ return tr;
139
165
  }
@@ -78,7 +78,7 @@ export function handleMention(slice, schema) {
78
78
  }
79
79
  export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChangedTr) {
80
80
  return function (state, dispatch) {
81
- var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con;
81
+ var _slice$content$firstC, _slice$content$firstC2, _slice$content$firstC3, _transformedSlice$con, _transformedSlice$con2;
82
82
  var schema = state.schema,
83
83
  selection = state.tr.selection;
84
84
  var codeMark = schema.marks.code,
@@ -158,9 +158,21 @@ export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChange
158
158
  // or the slice's first node is a paragraph,
159
159
  // then we can replace the selection with our slice.
160
160
  var pastingIntoExtendedPanel = selectionIsPanel && panel.validContent(transformedSlice.content);
161
- if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
161
+
162
+ // A task slice carrying its own open taskList wrapper would, on a plain replaceSelection,
163
+ // nest that wrapper inside the current item and add an extra taskList level. Unwrapping one
164
+ // level keeps the pasted task(s) at the same level. Guarded by a kill switch.
165
+ var isOpenTaskListSlice = ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === taskList && transformedSlice.openStart >= 2 && transformedSlice.openEnd >= 1;
166
+ // Restrict the unwrap to a taskItem destination: its task content is schema-invalid inside a
167
+ // decisionItem (decisionList only accepts decisionItem), so decisionItem must fall through to
168
+ // the normal replaceSelection path.
169
+ var pastingIntoTaskItem = hasParentNodeOfType([taskItem])(selection);
170
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) && !fg('platform_editor_flexible_list_kill_switch_1') && isOpenTaskListSlice && pastingIntoTaskItem && !selectionIsPanel) {
171
+ var unwrappedSlice = new Slice(transformedSlice.content.firstChild.content, transformedSlice.openStart - 1, transformedSlice.openEnd - 1);
172
+ tr.replaceSelection(unwrappedSlice).scrollIntoView();
173
+ } else if ((transformedSliceIsValidNode || selectionIsValidNode) && !pastingIntoExtendedPanel && !(transformedSlice.openStart === 1 && transformedSlice.openEnd === 0 ||
162
174
  // Whole codeblock node has reverse slice depths.
163
- transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con = transformedSlice.content.firstChild) === null || _transformedSlice$con === void 0 ? void 0 : _transformedSlice$con.type) === paragraph) {
175
+ transformedSlice.openStart === 0 && transformedSlice.openEnd === 1) || ((_transformedSlice$con2 = transformedSlice.content.firstChild) === null || _transformedSlice$con2 === void 0 ? void 0 : _transformedSlice$con2.type) === paragraph) {
164
176
  tr.replaceSelection(transformedSlice).scrollIntoView();
165
177
  } else {
166
178
  var isWholeContentSelected = selection.$from.pos === selection.$from.start() && selection.$to.end() === selection.$to.pos;
@@ -179,14 +191,14 @@ export function handlePasteIntoTaskOrDecisionOrPanel(slice, queueCardsFromChange
179
191
  tr.replaceSelection(transformedSlice).scrollIntoView();
180
192
  }
181
193
  } else {
182
- var _transformedSlice$con2;
194
+ var _transformedSlice$con3;
183
195
  if (pastingIntoExtendedPanel && isWholeContentSelected) {
184
196
  // if the entire panel content is selected, doing a replaceSelection removes the panel as well. Hence we do delete followed by safeInsert
185
197
  tr.delete(selection.$from.pos, selection.$to.pos);
186
198
  }
187
199
  // This maintains both the selection (destination) and the slice (paste content).
188
200
  safeInsert(transformedSlice.content)(tr).scrollIntoView();
189
- if (((_transformedSlice$con2 = transformedSlice.content.lastChild) === null || _transformedSlice$con2 === void 0 || (_transformedSlice$con2 = _transformedSlice$con2.type) === null || _transformedSlice$con2 === void 0 ? void 0 : _transformedSlice$con2.name) === 'rule') {
201
+ if (((_transformedSlice$con3 = transformedSlice.content.lastChild) === null || _transformedSlice$con3 === void 0 || (_transformedSlice$con3 = _transformedSlice$con3.type) === null || _transformedSlice$con3 === void 0 ? void 0 : _transformedSlice$con3.name) === 'rule') {
190
202
  tr.setSelection(TextSelection.near(tr.doc.resolve(tr.selection.$from.pos + transformedSlice.content.size)));
191
203
  } else {
192
204
  // safeInsert doesn't set correct cursor position
@@ -1202,8 +1214,17 @@ export function handleRichText(slice, queueCardsFromChangedTr) {
1202
1214
  });
1203
1215
  if (insideTableCell(state) && isInListItem(state) && canInsert(selection.$from, slice.content) && canInsert(selection.$to, slice.content) || sliceHasList) {
1204
1216
  tr.replaceSelection(slice);
1217
+ } else if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) && !fg('platform_editor_flexible_list_kill_switch_1') && checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1218
+ // Corrected task-into-list insertion is gated behind the flexible list indentation
1219
+ // experiment and protected by the kill switch.
1220
+ tr = insertSliceForTaskInsideList({
1221
+ tr: tr,
1222
+ slice: slice
1223
+ });
1205
1224
  } else if (checkTaskListInList(state, slice) && !checkIfSelectionInNestedList(state)) {
1206
- insertSliceForTaskInsideList({
1225
+ // Legacy path (experiment OFF or kill switch ON): preserve the previous over-nesting
1226
+ // behaviour via the function's internal fallback.
1227
+ tr = insertSliceForTaskInsideList({
1207
1228
  tr: tr,
1208
1229
  slice: slice
1209
1230
  });
@@ -15,4 +15,4 @@ export declare function updateSelectionAfterReplace({ tr }: {
15
15
  export declare function insertSliceForTaskInsideList({ tr, slice, }: {
16
16
  slice: PMSlice;
17
17
  tr: Transaction;
18
- }): void;
18
+ }): Transaction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-paste",
3
- "version": "13.2.2",
3
+ "version": "13.3.0",
4
4
  "description": "Paste plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@atlaskit/adf-schema": "^56.0.0",
23
23
  "@atlaskit/adf-utils": "^20.0.0",
24
- "@atlaskit/code": "^18.0.0",
24
+ "@atlaskit/code": "^18.1.0",
25
25
  "@atlaskit/editor-markdown-transformer": "^6.0.0",
26
26
  "@atlaskit/editor-plugin-analytics": "^12.0.0",
27
27
  "@atlaskit/editor-plugin-annotation": "^12.0.0",
@@ -31,7 +31,7 @@
31
31
  "@atlaskit/editor-plugin-feature-flags": "^11.0.0",
32
32
  "@atlaskit/editor-plugin-list": "^14.1.0",
33
33
  "@atlaskit/editor-plugin-media": "^14.2.0",
34
- "@atlaskit/editor-plugin-mentions": "^14.4.0",
34
+ "@atlaskit/editor-plugin-mentions": "^14.5.0",
35
35
  "@atlaskit/editor-prosemirror": "^8.0.0",
36
36
  "@atlaskit/editor-tables": "^3.0.0",
37
37
  "@atlaskit/flag": "^18.0.0",
@@ -41,14 +41,14 @@
41
41
  "@atlaskit/media-common": "^14.1.0",
42
42
  "@atlaskit/platform-feature-flags": "^2.0.0",
43
43
  "@atlaskit/prosemirror-history": "^1.0.0",
44
- "@atlaskit/tmp-editor-statsig": "^113.0.0",
44
+ "@atlaskit/tmp-editor-statsig": "^114.0.0",
45
45
  "@atlaskit/tokens": "^15.0.0",
46
46
  "@babel/runtime": "^7.0.0",
47
47
  "lodash": "^4.17.21",
48
48
  "uuid": "^3.1.0"
49
49
  },
50
50
  "peerDependencies": {
51
- "@atlaskit/editor-common": "^116.12.0",
51
+ "@atlaskit/editor-common": "^116.13.0",
52
52
  "react": "^18.2.0",
53
53
  "react-dom": "^18.2.0",
54
54
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
@@ -127,6 +127,9 @@
127
127
  },
128
128
  "platform_editor_paste_renderer_analytics": {
129
129
  "type": "boolean"
130
+ },
131
+ "platform_editor_flexible_list_kill_switch_1": {
132
+ "type": "boolean"
130
133
  }
131
134
  }
132
135
  }