@atlaskit/editor-plugin-tasks-and-decisions 11.2.1 → 11.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atlaskit/editor-plugin-tasks-and-decisions
2
2
 
3
+ ## 11.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`aa4e1fcb89ca8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/aa4e1fcb89ca8) -
8
+ Added new `platform_editor_flexible_list_schema` experiment that independently enables ADF schema
9
+ and CSS rendering support for flexible lists, without creation behaviour. Updated schema node
10
+ selection, CSS rendering, ADF validation, and task list schema to use the new gate. Indent/outdent
11
+ behaviour remains on the existing `platform_editor_flexible_list_indentation` gate.
12
+ - Updated dependencies
13
+
14
+ ## 11.3.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [`65cb641586cc0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/65cb641586cc0) -
19
+ Add flexible per-item task list indentation with breakout support behind
20
+ platform_editor_flexible_list_indentation experiment
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies
25
+
3
26
  ## 11.2.1
4
27
 
5
28
  ### Patch Changes
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.moveSelectedTaskListItems = moveSelectedTaskListItems;
7
+ var _lists = require("@atlaskit/editor-common/lists");
8
+ var _utils = require("@atlaskit/editor-common/utils");
9
+ var _taskListIndentation = require("../task-list-indentation");
10
+ var MAX_TASK_LIST_DEPTH = 6;
11
+ function moveSelectedTaskListItems(tr, indentDelta) {
12
+ var doc = tr.doc,
13
+ selection = tr.selection;
14
+ var schema = doc.type.schema;
15
+ var taskList = schema.nodes.taskList;
16
+ var $from = selection.$from,
17
+ $to = selection.$to;
18
+ var rootListResult = (0, _utils.findFarthestParentNode)(function (node) {
19
+ return node.type === taskList;
20
+ })($from);
21
+ if (!rootListResult) {
22
+ return null;
23
+ }
24
+ var rootListStart = rootListResult.pos;
25
+ var rootListEnd = rootListStart + rootListResult.node.nodeSize;
26
+ var flattenResult = (0, _taskListIndentation.flattenTaskList)({
27
+ doc: doc,
28
+ rootListStart: rootListStart,
29
+ rootListEnd: rootListEnd,
30
+ selectionFrom: $from.pos,
31
+ selectionTo: $to.pos,
32
+ indentDelta: indentDelta,
33
+ maxDepth: MAX_TASK_LIST_DEPTH
34
+ });
35
+ if (!flattenResult) {
36
+ return null;
37
+ }
38
+ var flattenedItems = flattenResult.items,
39
+ startIndex = flattenResult.startIndex,
40
+ endIndex = flattenResult.endIndex;
41
+ var _buildReplacementFrag = (0, _lists.buildReplacementFragment)({
42
+ items: flattenedItems,
43
+ schema: schema,
44
+ rebuildFn: _taskListIndentation.rebuildTaskList,
45
+ extractContentFn: function extractContentFn(item, s) {
46
+ // Extract task item content for breakout.
47
+ // taskItem has inline content, so wrap in a paragraph.
48
+ // blockTaskItem already has paragraph children.
49
+ var blockTaskItem = s.nodes.blockTaskItem;
50
+ if (blockTaskItem != null && item.node.type === blockTaskItem) {
51
+ // blockTaskItem children are already paragraphs/extensions
52
+ var children = [];
53
+ item.node.forEach(function (child) {
54
+ return children.push(child);
55
+ });
56
+ return children;
57
+ }
58
+ // Regular taskItem — wrap inline content in a paragraph
59
+ return [s.nodes.paragraph.create(null, item.node.content)];
60
+ }
61
+ }),
62
+ fragment = _buildReplacementFrag.fragment,
63
+ contentStartOffsets = _buildReplacementFrag.contentStartOffsets;
64
+ if (fragment.size === 0) {
65
+ return null;
66
+ }
67
+ tr.replaceWith(rootListStart, rootListEnd, fragment);
68
+ var _computeSelectionOffs = (0, _lists.computeSelectionOffsets)({
69
+ items: flattenedItems,
70
+ startIndex: startIndex,
71
+ endIndex: endIndex,
72
+ originalFrom: $from.pos,
73
+ originalTo: $to.pos,
74
+ contentStartOffsets: contentStartOffsets,
75
+ rootListStart: rootListStart,
76
+ docSize: tr.doc.content.size
77
+ }),
78
+ from = _computeSelectionOffs.from,
79
+ to = _computeSelectionOffs.to;
80
+ (0, _lists.restoreSelection)({
81
+ tr: tr,
82
+ originalSelection: selection,
83
+ from: from,
84
+ to: to
85
+ });
86
+ tr.scrollIntoView();
87
+ return tr;
88
+ }
@@ -17,9 +17,11 @@ var _utils = require("@atlaskit/editor-common/utils");
17
17
  var _commands = require("@atlaskit/editor-prosemirror/commands");
18
18
  var _keymap = require("@atlaskit/editor-prosemirror/keymap");
19
19
  var _model = require("@atlaskit/editor-prosemirror/model");
20
- var _state = require("@atlaskit/editor-prosemirror/state");
20
+ var _state2 = require("@atlaskit/editor-prosemirror/state");
21
21
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
22
22
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
23
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
24
+ var _moveSelectedTaskListItems = require("./actions/move-selected-task-list-items");
23
25
  var _commands2 = require("./commands");
24
26
  var _helpers = require("./helpers");
25
27
  var _insertCommands = require("./insert-commands");
@@ -106,6 +108,20 @@ var getUnindentCommand = exports.getUnindentCommand = function getUnindentComman
106
108
  return (0, _utils.filterCommand)(_helpers.isInsideTask, function (state, dispatch) {
107
109
  var normalizedSelection = (0, _utils3.normalizeTaskItemsSelection)(state.selection);
108
110
  var curIndentLevel = (0, _helpers.getCurrentIndentLevel)(normalizedSelection);
111
+ if ((0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
112
+ if (!curIndentLevel) {
113
+ return true;
114
+ }
115
+ var outdentTr = (0, _moveSelectedTaskListItems.moveSelectedTaskListItems)(state.tr, -1);
116
+ if (outdentTr) {
117
+ (0, _editorAnalytics.withAnalytics)(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, _analytics.INDENT_DIRECTION.OUTDENT, inputMethod))(function (_state, d) {
118
+ d === null || d === void 0 || d(outdentTr);
119
+ return true;
120
+ })(state, dispatch);
121
+ return true;
122
+ }
123
+ return false;
124
+ }
109
125
  if (!curIndentLevel || curIndentLevel === 1) {
110
126
  return false;
111
127
  }
@@ -136,6 +152,20 @@ var getIndentCommand = exports.getIndentCommand = function getIndentCommand(edit
136
152
  return (0, _utils.filterCommand)(_helpers.isInsideTask, function (state, dispatch) {
137
153
  var normalizedSelection = (0, _utils3.normalizeTaskItemsSelection)(state.selection);
138
154
  var curIndentLevel = (0, _helpers.getCurrentIndentLevel)(normalizedSelection);
155
+ if ((0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
156
+ if (!curIndentLevel) {
157
+ return true;
158
+ }
159
+ var indentTr = (0, _moveSelectedTaskListItems.moveSelectedTaskListItems)(state.tr, 1);
160
+ if (indentTr) {
161
+ (0, _editorAnalytics.withAnalytics)(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, _analytics.INDENT_DIRECTION.INDENT, inputMethod))(function (_state, d) {
162
+ d === null || d === void 0 || d(indentTr);
163
+ return true;
164
+ })(state, dispatch);
165
+ return true;
166
+ }
167
+ return false;
168
+ }
139
169
  if (!curIndentLevel || curIndentLevel >= 6) {
140
170
  return true;
141
171
  }
@@ -379,7 +409,7 @@ var processNestedActionItem = function processNestedActionItem(tr, $from, previo
379
409
 
380
410
  // Set custom selection for nested action inside lists using previosuly calculated previousListItem position
381
411
  var stableResolvedPos = tr.doc.resolve(previousListItemPos);
382
- tr.setSelection(_state.TextSelection.create(tr.doc, stableResolvedPos.after() + 2));
412
+ tr.setSelection(_state2.TextSelection.create(tr.doc, stableResolvedPos.after() + 2));
383
413
  };
384
414
  var splitListItemWith = function splitListItemWith(tr, content, $from, setSelection) {
385
415
  var _frag$firstChild;
@@ -469,7 +499,7 @@ var splitListItemWith = function splitListItemWith(tr, content, $from, setSelect
469
499
  if (isGapCursorSelection) {
470
500
  tr = tr.setSelection(new _selection.GapCursorSelection(tr.doc.resolve(newPos), _selection.Side.LEFT));
471
501
  } else {
472
- tr = tr.setSelection(new _state.TextSelection(tr.doc.resolve(newPos)));
502
+ tr = tr.setSelection(new _state2.TextSelection(tr.doc.resolve(newPos)));
473
503
  }
474
504
  }
475
505
 
@@ -608,7 +638,7 @@ var enter = function enter(editorAnalyticsAPI, getContextIdentifier) {
608
638
  tr.insert(_blockTaskItemNode.pos + _blockTaskItemNode.node.nodeSize, _newTaskItem);
609
639
 
610
640
  // Move the cursor to the end of the newly inserted blockTaskItem
611
- tr.setSelection(_state.TextSelection.create(tr.doc, _blockTaskItemNode.pos + _blockTaskItemNode.node.nodeSize + 1));
641
+ tr.setSelection(_state2.TextSelection.create(tr.doc, _blockTaskItemNode.pos + _blockTaskItemNode.node.nodeSize + 1));
612
642
  return tr;
613
643
  }
614
644
  }
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.flattenTaskList = flattenTaskList;
7
+ exports.rebuildTaskList = rebuildTaskList;
8
+ var _adfSchema = require("@atlaskit/adf-schema");
9
+ var _lists = require("@atlaskit/editor-common/lists");
10
+ 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; } } }; }
11
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
12
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13
+ /**
14
+ * Flattens a taskList tree into an array of task items with computed depths.
15
+ * Only selected items have their depth adjusted by indentDelta.
16
+ *
17
+ * Delegates to the shared `flattenList` with task-list-specific callbacks.
18
+ */
19
+ function flattenTaskList(options) {
20
+ var _options$doc$type$sch = options.doc.type.schema.nodes,
21
+ taskList = _options$doc$type$sch.taskList,
22
+ taskItem = _options$doc$type$sch.taskItem,
23
+ blockTaskItem = _options$doc$type$sch.blockTaskItem;
24
+ return (0, _lists.flattenList)(options, {
25
+ isContentNode: function isContentNode(node, parent) {
26
+ var isTaskItemType = node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
27
+ return isTaskItemType && parent != null && parent.type === taskList;
28
+ },
29
+ getSelectionBounds: function getSelectionBounds(node, pos) {
30
+ return {
31
+ start: pos,
32
+ end: pos + node.nodeSize
33
+ };
34
+ },
35
+ getDepth: function getDepth(resolvedDepth, rootDepth) {
36
+ return resolvedDepth - rootDepth - 1;
37
+ }
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Rebuilds a taskList tree from a flattened array of task items.
43
+ * Uses a stack-based approach to create proper nesting.
44
+ *
45
+ * Preserves original taskList attributes (e.g. localId) for wrappers
46
+ * of unselected items. Only generates fresh UUIDs for newly-created
47
+ * nesting levels (i.e. when items were moved via indent/outdent).
48
+ *
49
+ * Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
50
+ *
51
+ * taskList
52
+ * taskItem 'A'
53
+ * taskList
54
+ * taskItem 'B'
55
+ * taskList
56
+ * taskItem 'C'
57
+ * taskItem 'D'
58
+ * taskItem 'E'
59
+ *
60
+ * Also computes `contentStartOffsets`: for each item (by index),
61
+ * the offset within the returned fragment where the item's content
62
+ * begins. This is used for accurate selection restoration.
63
+ */
64
+ function rebuildTaskList(items, schema) {
65
+ var _stack$0$listAttrs;
66
+ var taskList = schema.nodes.taskList;
67
+ var contentStartOffsets = new Array(items.length);
68
+ if (items.length === 0) {
69
+ return null;
70
+ }
71
+
72
+ // Each stack entry collects children for a taskList at a given depth.
73
+ // The root entry (depth -1) is special: its children become the content
74
+ // of the outermost taskList directly (not wrapped in another taskList).
75
+ // Entries at depth >= 0 each produce a nested taskList when popped.
76
+ // listAttrs preserves the original parent's attributes when available.
77
+ // sourceParentAttrs tracks the original parent taskList attrs of the items
78
+ // in this entry, used to decide whether consecutive same-depth items
79
+ // should share a wrapper or be separated.
80
+
81
+ // Start with the root level (depth -1 represents the root taskList wrapper)
82
+ var stack = [{
83
+ depth: -1,
84
+ children: [],
85
+ listAttrs: items[0].parentListAttrs,
86
+ sourceParentAttrs: items[0].parentListAttrs
87
+ }];
88
+ var _iterator = _createForOfIteratorHelper(items),
89
+ _step;
90
+ try {
91
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
92
+ var item = _step.value;
93
+ var targetDepth = item.depth;
94
+
95
+ // Pop stack entries strictly deeper than target, wrapping them into taskLists
96
+ while (stack.length > 1 && stack[stack.length - 1].depth > targetDepth) {
97
+ var _popped$listAttrs2;
98
+ var _popped = stack.pop();
99
+ if (!_popped) {
100
+ break;
101
+ }
102
+ var _attrs = (_popped$listAttrs2 = _popped.listAttrs) !== null && _popped$listAttrs2 !== void 0 ? _popped$listAttrs2 : {
103
+ localId: _adfSchema.uuid.generate()
104
+ };
105
+ var _wrappedList = taskList.create(_attrs, _popped.children);
106
+ stack[stack.length - 1].children.push(_wrappedList);
107
+ }
108
+
109
+ // If the stack top is at the same depth as the target, decide whether
110
+ // to merge into the existing entry or close it and start a new one.
111
+ // Unselected items from different original parent taskLists get
112
+ // separate wrappers (preserving original nesting). Selected (moved)
113
+ // items always merge with whatever is already at the target depth,
114
+ // since they're being placed into a new structural context.
115
+ if (stack.length > 1 && stack[stack.length - 1].depth === targetDepth && !item.isSelected) {
116
+ var top = stack[stack.length - 1];
117
+ if (top.sourceParentAttrs !== item.parentListAttrs) {
118
+ var _popped2$listAttrs;
119
+ var _popped2 = stack.pop();
120
+ if (!_popped2) {
121
+ break;
122
+ }
123
+ var _attrs2 = (_popped2$listAttrs = _popped2.listAttrs) !== null && _popped2$listAttrs !== void 0 ? _popped2$listAttrs : {
124
+ localId: _adfSchema.uuid.generate()
125
+ };
126
+ var _wrappedList2 = taskList.create(_attrs2, _popped2.children);
127
+ stack[stack.length - 1].children.push(_wrappedList2);
128
+ }
129
+ }
130
+
131
+ // Push new stack entries to reach the target depth.
132
+ // Skip intermediate entries at depth 0 — depth-0 items belong
133
+ // directly in the root entry (depth -1), so we only need
134
+ // intermediates for depths > 1.
135
+ while (stack[stack.length - 1].depth < targetDepth - 1) {
136
+ var nextDepth = Math.max(stack[stack.length - 1].depth + 1, 1);
137
+ stack.push({
138
+ depth: nextDepth,
139
+ children: [],
140
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
141
+ sourceParentAttrs: item.parentListAttrs
142
+ });
143
+ }
144
+
145
+ // Add the item at the target depth
146
+ if (targetDepth === 0) {
147
+ // Depth 0 items go directly into the root taskList
148
+ stack[0].children.push(item.node);
149
+ } else {
150
+ // Ensure there's a stack entry at depth targetDepth to hold this item
151
+ if (stack[stack.length - 1].depth < targetDepth) {
152
+ stack.push({
153
+ depth: targetDepth,
154
+ children: [],
155
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
156
+ sourceParentAttrs: item.parentListAttrs
157
+ });
158
+ }
159
+ stack[stack.length - 1].children.push(item.node);
160
+ }
161
+ }
162
+
163
+ // Close remaining stack entries
164
+ } catch (err) {
165
+ _iterator.e(err);
166
+ } finally {
167
+ _iterator.f();
168
+ }
169
+ while (stack.length > 1) {
170
+ var _popped$listAttrs;
171
+ var popped = stack.pop();
172
+ if (!popped) {
173
+ break;
174
+ }
175
+ var attrs = (_popped$listAttrs = popped.listAttrs) !== null && _popped$listAttrs !== void 0 ? _popped$listAttrs : {
176
+ localId: _adfSchema.uuid.generate()
177
+ };
178
+ var wrappedList = taskList.create(attrs, popped.children);
179
+ stack[stack.length - 1].children.push(wrappedList);
180
+ }
181
+
182
+ // The root entry's children form the root taskList.
183
+ // Preserve the root's original attrs when available.
184
+ var rootChildren = stack[0].children;
185
+ var rootAttrs = (_stack$0$listAttrs = stack[0].listAttrs) !== null && _stack$0$listAttrs !== void 0 ? _stack$0$listAttrs : {
186
+ localId: _adfSchema.uuid.generate()
187
+ };
188
+ var rootList = taskList.create(rootAttrs, rootChildren);
189
+
190
+ // Compute contentStartOffsets by walking the rebuilt tree.
191
+ // Each taskItem's content starts at (pos_within_root + 1) for the taskItem opening tag.
192
+ // We add +1 for the root taskList's opening tag.
193
+ var isTaskItemType = function isTaskItemType(node) {
194
+ var _schema$nodes = schema.nodes,
195
+ taskItem = _schema$nodes.taskItem,
196
+ blockTaskItem = _schema$nodes.blockTaskItem;
197
+ return node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
198
+ };
199
+ var itemIdx = 0;
200
+ rootList.descendants(function (node, pos) {
201
+ if (isTaskItemType(node) && itemIdx < items.length) {
202
+ // pos is relative to rootList content start;
203
+ // +1 for rootList's opening tag, +1 for taskItem's opening tag
204
+ contentStartOffsets[itemIdx] = 1 + pos + 1;
205
+ itemIdx++;
206
+ }
207
+ return true;
208
+ });
209
+ return {
210
+ node: rootList,
211
+ contentStartOffsets: contentStartOffsets
212
+ };
213
+ }
@@ -176,7 +176,7 @@ var tasksAndDecisionsPlugin = exports.tasksAndDecisionsPlugin = function tasksAn
176
176
  node: (0, _decisionItem.decisionItemSpecWithFixedToDOM)()
177
177
  }, {
178
178
  name: 'taskList',
179
- node: (0, _expValEquals.expValEquals)('platform_editor_flexible_list_indentation', 'isEnabled', true) ? _adfSchema.taskListWithFlexibleFirstChildStage0 : _adfSchema.taskList
179
+ node: (0, _expValEquals.expValEquals)('platform_editor_flexible_list_schema', 'isEnabled', true) ? _adfSchema.taskListWithFlexibleFirstChildStage0 : _adfSchema.taskList
180
180
  }, {
181
181
  name: 'taskItem',
182
182
  node: (0, _taskItemNodeSpec.taskItemNodeSpec)()
@@ -0,0 +1,92 @@
1
+ import { buildReplacementFragment, computeSelectionOffsets, restoreSelection } from '@atlaskit/editor-common/lists';
2
+ import { findFarthestParentNode } from '@atlaskit/editor-common/utils';
3
+ import { flattenTaskList, rebuildTaskList } from '../task-list-indentation';
4
+ const MAX_TASK_LIST_DEPTH = 6;
5
+ export function moveSelectedTaskListItems(tr, indentDelta) {
6
+ const {
7
+ doc,
8
+ selection
9
+ } = tr;
10
+ const {
11
+ schema
12
+ } = doc.type;
13
+ const {
14
+ taskList
15
+ } = schema.nodes;
16
+ const {
17
+ $from,
18
+ $to
19
+ } = selection;
20
+ const rootListResult = findFarthestParentNode(node => node.type === taskList)($from);
21
+ if (!rootListResult) {
22
+ return null;
23
+ }
24
+ const rootListStart = rootListResult.pos;
25
+ const rootListEnd = rootListStart + rootListResult.node.nodeSize;
26
+ const flattenResult = flattenTaskList({
27
+ doc,
28
+ rootListStart,
29
+ rootListEnd,
30
+ selectionFrom: $from.pos,
31
+ selectionTo: $to.pos,
32
+ indentDelta,
33
+ maxDepth: MAX_TASK_LIST_DEPTH
34
+ });
35
+ if (!flattenResult) {
36
+ return null;
37
+ }
38
+ const {
39
+ items: flattenedItems,
40
+ startIndex,
41
+ endIndex
42
+ } = flattenResult;
43
+ const {
44
+ fragment,
45
+ contentStartOffsets
46
+ } = buildReplacementFragment({
47
+ items: flattenedItems,
48
+ schema,
49
+ rebuildFn: rebuildTaskList,
50
+ extractContentFn: (item, s) => {
51
+ // Extract task item content for breakout.
52
+ // taskItem has inline content, so wrap in a paragraph.
53
+ // blockTaskItem already has paragraph children.
54
+ const {
55
+ blockTaskItem
56
+ } = s.nodes;
57
+ if (blockTaskItem != null && item.node.type === blockTaskItem) {
58
+ // blockTaskItem children are already paragraphs/extensions
59
+ const children = [];
60
+ item.node.forEach(child => children.push(child));
61
+ return children;
62
+ }
63
+ // Regular taskItem — wrap inline content in a paragraph
64
+ return [s.nodes.paragraph.create(null, item.node.content)];
65
+ }
66
+ });
67
+ if (fragment.size === 0) {
68
+ return null;
69
+ }
70
+ tr.replaceWith(rootListStart, rootListEnd, fragment);
71
+ const {
72
+ from,
73
+ to
74
+ } = computeSelectionOffsets({
75
+ items: flattenedItems,
76
+ startIndex,
77
+ endIndex,
78
+ originalFrom: $from.pos,
79
+ originalTo: $to.pos,
80
+ contentStartOffsets,
81
+ rootListStart,
82
+ docSize: tr.doc.content.size
83
+ });
84
+ restoreSelection({
85
+ tr,
86
+ originalSelection: selection,
87
+ from,
88
+ to
89
+ });
90
+ tr.scrollIntoView();
91
+ return tr;
92
+ }
@@ -11,6 +11,8 @@ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
11
11
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
12
12
  import { findParentNodeOfType, findParentNodeOfTypeClosestToPos, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
13
13
  import { fg } from '@atlaskit/platform-feature-flags';
14
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
+ import { moveSelectedTaskListItems } from './actions/move-selected-task-list-items';
14
16
  import { joinAtCut, liftSelection, wrapSelectionInTaskList } from './commands';
15
17
  import { findFirstParentListNode, getBlockRange, getCurrentIndentLevel, getTaskItemIndex, isActionOrDecisionItem, isActionOrDecisionList, isEmptyTaskDecision, isInFirstTextblockOfBlockTaskItem, isInLastTextblockOfBlockTaskItem, isInsideDecision, isInsideTask, isInsideTaskOrDecisionItem, isTable, liftBlock, walkOut } from './helpers';
16
18
  import { insertTaskDecisionWithAnalytics } from './insert-commands';
@@ -89,6 +91,20 @@ const joinTaskDecisionFollowing = (state, dispatch) => {
89
91
  export const getUnindentCommand = editorAnalyticsAPI => (inputMethod = INPUT_METHOD.KEYBOARD) => filter(isInsideTask, (state, dispatch) => {
90
92
  const normalizedSelection = normalizeTaskItemsSelection(state.selection);
91
93
  const curIndentLevel = getCurrentIndentLevel(normalizedSelection);
94
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
95
+ if (!curIndentLevel) {
96
+ return true;
97
+ }
98
+ const outdentTr = moveSelectedTaskListItems(state.tr, -1);
99
+ if (outdentTr) {
100
+ withAnalytics(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, INDENT_DIRECTION.OUTDENT, inputMethod))((_state, d) => {
101
+ d === null || d === void 0 ? void 0 : d(outdentTr);
102
+ return true;
103
+ })(state, dispatch);
104
+ return true;
105
+ }
106
+ return false;
107
+ }
92
108
  if (!curIndentLevel || curIndentLevel === 1) {
93
109
  return false;
94
110
  }
@@ -115,6 +131,20 @@ const shouldLetTabThroughInTable = state => {
115
131
  export const getIndentCommand = editorAnalyticsAPI => (inputMethod = INPUT_METHOD.KEYBOARD) => filter(isInsideTask, (state, dispatch) => {
116
132
  const normalizedSelection = normalizeTaskItemsSelection(state.selection);
117
133
  const curIndentLevel = getCurrentIndentLevel(normalizedSelection);
134
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
135
+ if (!curIndentLevel) {
136
+ return true;
137
+ }
138
+ const indentTr = moveSelectedTaskListItems(state.tr, 1);
139
+ if (indentTr) {
140
+ withAnalytics(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, INDENT_DIRECTION.INDENT, inputMethod))((_state, d) => {
141
+ d === null || d === void 0 ? void 0 : d(indentTr);
142
+ return true;
143
+ })(state, dispatch);
144
+ return true;
145
+ }
146
+ return false;
147
+ }
118
148
  if (!curIndentLevel || curIndentLevel >= 6) {
119
149
  return true;
120
150
  }
@@ -0,0 +1,194 @@
1
+ import { uuid } from '@atlaskit/adf-schema';
2
+ import { flattenList as flattenListBase } from '@atlaskit/editor-common/lists';
3
+ /**
4
+ * Flattens a taskList tree into an array of task items with computed depths.
5
+ * Only selected items have their depth adjusted by indentDelta.
6
+ *
7
+ * Delegates to the shared `flattenList` with task-list-specific callbacks.
8
+ */
9
+ export function flattenTaskList(options) {
10
+ const {
11
+ taskList,
12
+ taskItem,
13
+ blockTaskItem
14
+ } = options.doc.type.schema.nodes;
15
+ return flattenListBase(options, {
16
+ isContentNode: (node, parent) => {
17
+ const isTaskItemType = node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
18
+ return isTaskItemType && parent != null && parent.type === taskList;
19
+ },
20
+ getSelectionBounds: (node, pos) => ({
21
+ start: pos,
22
+ end: pos + node.nodeSize
23
+ }),
24
+ getDepth: (resolvedDepth, rootDepth) => resolvedDepth - rootDepth - 1
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Rebuilds a taskList tree from a flattened array of task items.
30
+ * Uses a stack-based approach to create proper nesting.
31
+ *
32
+ * Preserves original taskList attributes (e.g. localId) for wrappers
33
+ * of unselected items. Only generates fresh UUIDs for newly-created
34
+ * nesting levels (i.e. when items were moved via indent/outdent).
35
+ *
36
+ * Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
37
+ *
38
+ * taskList
39
+ * taskItem 'A'
40
+ * taskList
41
+ * taskItem 'B'
42
+ * taskList
43
+ * taskItem 'C'
44
+ * taskItem 'D'
45
+ * taskItem 'E'
46
+ *
47
+ * Also computes `contentStartOffsets`: for each item (by index),
48
+ * the offset within the returned fragment where the item's content
49
+ * begins. This is used for accurate selection restoration.
50
+ */
51
+ export function rebuildTaskList(items, schema) {
52
+ var _stack$0$listAttrs;
53
+ const {
54
+ taskList
55
+ } = schema.nodes;
56
+ const contentStartOffsets = new Array(items.length);
57
+ if (items.length === 0) {
58
+ return null;
59
+ }
60
+
61
+ // Each stack entry collects children for a taskList at a given depth.
62
+ // The root entry (depth -1) is special: its children become the content
63
+ // of the outermost taskList directly (not wrapped in another taskList).
64
+ // Entries at depth >= 0 each produce a nested taskList when popped.
65
+ // listAttrs preserves the original parent's attributes when available.
66
+ // sourceParentAttrs tracks the original parent taskList attrs of the items
67
+ // in this entry, used to decide whether consecutive same-depth items
68
+ // should share a wrapper or be separated.
69
+
70
+ // Start with the root level (depth -1 represents the root taskList wrapper)
71
+ const stack = [{
72
+ depth: -1,
73
+ children: [],
74
+ listAttrs: items[0].parentListAttrs,
75
+ sourceParentAttrs: items[0].parentListAttrs
76
+ }];
77
+ for (const item of items) {
78
+ const targetDepth = item.depth;
79
+
80
+ // Pop stack entries strictly deeper than target, wrapping them into taskLists
81
+ while (stack.length > 1 && stack[stack.length - 1].depth > targetDepth) {
82
+ var _popped$listAttrs;
83
+ const popped = stack.pop();
84
+ if (!popped) {
85
+ break;
86
+ }
87
+ const attrs = (_popped$listAttrs = popped.listAttrs) !== null && _popped$listAttrs !== void 0 ? _popped$listAttrs : {
88
+ localId: uuid.generate()
89
+ };
90
+ const wrappedList = taskList.create(attrs, popped.children);
91
+ stack[stack.length - 1].children.push(wrappedList);
92
+ }
93
+
94
+ // If the stack top is at the same depth as the target, decide whether
95
+ // to merge into the existing entry or close it and start a new one.
96
+ // Unselected items from different original parent taskLists get
97
+ // separate wrappers (preserving original nesting). Selected (moved)
98
+ // items always merge with whatever is already at the target depth,
99
+ // since they're being placed into a new structural context.
100
+ if (stack.length > 1 && stack[stack.length - 1].depth === targetDepth && !item.isSelected) {
101
+ const top = stack[stack.length - 1];
102
+ if (top.sourceParentAttrs !== item.parentListAttrs) {
103
+ var _popped$listAttrs2;
104
+ const popped = stack.pop();
105
+ if (!popped) {
106
+ break;
107
+ }
108
+ const attrs = (_popped$listAttrs2 = popped.listAttrs) !== null && _popped$listAttrs2 !== void 0 ? _popped$listAttrs2 : {
109
+ localId: uuid.generate()
110
+ };
111
+ const wrappedList = taskList.create(attrs, popped.children);
112
+ stack[stack.length - 1].children.push(wrappedList);
113
+ }
114
+ }
115
+
116
+ // Push new stack entries to reach the target depth.
117
+ // Skip intermediate entries at depth 0 — depth-0 items belong
118
+ // directly in the root entry (depth -1), so we only need
119
+ // intermediates for depths > 1.
120
+ while (stack[stack.length - 1].depth < targetDepth - 1) {
121
+ const nextDepth = Math.max(stack[stack.length - 1].depth + 1, 1);
122
+ stack.push({
123
+ depth: nextDepth,
124
+ children: [],
125
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
126
+ sourceParentAttrs: item.parentListAttrs
127
+ });
128
+ }
129
+
130
+ // Add the item at the target depth
131
+ if (targetDepth === 0) {
132
+ // Depth 0 items go directly into the root taskList
133
+ stack[0].children.push(item.node);
134
+ } else {
135
+ // Ensure there's a stack entry at depth targetDepth to hold this item
136
+ if (stack[stack.length - 1].depth < targetDepth) {
137
+ stack.push({
138
+ depth: targetDepth,
139
+ children: [],
140
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
141
+ sourceParentAttrs: item.parentListAttrs
142
+ });
143
+ }
144
+ stack[stack.length - 1].children.push(item.node);
145
+ }
146
+ }
147
+
148
+ // Close remaining stack entries
149
+ while (stack.length > 1) {
150
+ var _popped$listAttrs3;
151
+ const popped = stack.pop();
152
+ if (!popped) {
153
+ break;
154
+ }
155
+ const attrs = (_popped$listAttrs3 = popped.listAttrs) !== null && _popped$listAttrs3 !== void 0 ? _popped$listAttrs3 : {
156
+ localId: uuid.generate()
157
+ };
158
+ const wrappedList = taskList.create(attrs, popped.children);
159
+ stack[stack.length - 1].children.push(wrappedList);
160
+ }
161
+
162
+ // The root entry's children form the root taskList.
163
+ // Preserve the root's original attrs when available.
164
+ const rootChildren = stack[0].children;
165
+ const rootAttrs = (_stack$0$listAttrs = stack[0].listAttrs) !== null && _stack$0$listAttrs !== void 0 ? _stack$0$listAttrs : {
166
+ localId: uuid.generate()
167
+ };
168
+ const rootList = taskList.create(rootAttrs, rootChildren);
169
+
170
+ // Compute contentStartOffsets by walking the rebuilt tree.
171
+ // Each taskItem's content starts at (pos_within_root + 1) for the taskItem opening tag.
172
+ // We add +1 for the root taskList's opening tag.
173
+ const isTaskItemType = node => {
174
+ const {
175
+ taskItem,
176
+ blockTaskItem
177
+ } = schema.nodes;
178
+ return node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
179
+ };
180
+ let itemIdx = 0;
181
+ rootList.descendants((node, pos) => {
182
+ if (isTaskItemType(node) && itemIdx < items.length) {
183
+ // pos is relative to rootList content start;
184
+ // +1 for rootList's opening tag, +1 for taskItem's opening tag
185
+ contentStartOffsets[itemIdx] = 1 + pos + 1;
186
+ itemIdx++;
187
+ }
188
+ return true;
189
+ });
190
+ return {
191
+ node: rootList,
192
+ contentStartOffsets
193
+ };
194
+ }
@@ -164,7 +164,7 @@ export const tasksAndDecisionsPlugin = ({
164
164
  node: decisionItemSpecWithFixedToDOM()
165
165
  }, {
166
166
  name: 'taskList',
167
- node: expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) ? taskListWithFlexibleFirstChildStage0 : taskList
167
+ node: expValEquals('platform_editor_flexible_list_schema', 'isEnabled', true) ? taskListWithFlexibleFirstChildStage0 : taskList
168
168
  }, {
169
169
  name: 'taskItem',
170
170
  node: taskItemNodeSpec()
@@ -0,0 +1,82 @@
1
+ import { buildReplacementFragment, computeSelectionOffsets, restoreSelection } from '@atlaskit/editor-common/lists';
2
+ import { findFarthestParentNode } from '@atlaskit/editor-common/utils';
3
+ import { flattenTaskList, rebuildTaskList } from '../task-list-indentation';
4
+ var MAX_TASK_LIST_DEPTH = 6;
5
+ export function moveSelectedTaskListItems(tr, indentDelta) {
6
+ var doc = tr.doc,
7
+ selection = tr.selection;
8
+ var schema = doc.type.schema;
9
+ var taskList = schema.nodes.taskList;
10
+ var $from = selection.$from,
11
+ $to = selection.$to;
12
+ var rootListResult = findFarthestParentNode(function (node) {
13
+ return node.type === taskList;
14
+ })($from);
15
+ if (!rootListResult) {
16
+ return null;
17
+ }
18
+ var rootListStart = rootListResult.pos;
19
+ var rootListEnd = rootListStart + rootListResult.node.nodeSize;
20
+ var flattenResult = flattenTaskList({
21
+ doc: doc,
22
+ rootListStart: rootListStart,
23
+ rootListEnd: rootListEnd,
24
+ selectionFrom: $from.pos,
25
+ selectionTo: $to.pos,
26
+ indentDelta: indentDelta,
27
+ maxDepth: MAX_TASK_LIST_DEPTH
28
+ });
29
+ if (!flattenResult) {
30
+ return null;
31
+ }
32
+ var flattenedItems = flattenResult.items,
33
+ startIndex = flattenResult.startIndex,
34
+ endIndex = flattenResult.endIndex;
35
+ var _buildReplacementFrag = buildReplacementFragment({
36
+ items: flattenedItems,
37
+ schema: schema,
38
+ rebuildFn: rebuildTaskList,
39
+ extractContentFn: function extractContentFn(item, s) {
40
+ // Extract task item content for breakout.
41
+ // taskItem has inline content, so wrap in a paragraph.
42
+ // blockTaskItem already has paragraph children.
43
+ var blockTaskItem = s.nodes.blockTaskItem;
44
+ if (blockTaskItem != null && item.node.type === blockTaskItem) {
45
+ // blockTaskItem children are already paragraphs/extensions
46
+ var children = [];
47
+ item.node.forEach(function (child) {
48
+ return children.push(child);
49
+ });
50
+ return children;
51
+ }
52
+ // Regular taskItem — wrap inline content in a paragraph
53
+ return [s.nodes.paragraph.create(null, item.node.content)];
54
+ }
55
+ }),
56
+ fragment = _buildReplacementFrag.fragment,
57
+ contentStartOffsets = _buildReplacementFrag.contentStartOffsets;
58
+ if (fragment.size === 0) {
59
+ return null;
60
+ }
61
+ tr.replaceWith(rootListStart, rootListEnd, fragment);
62
+ var _computeSelectionOffs = computeSelectionOffsets({
63
+ items: flattenedItems,
64
+ startIndex: startIndex,
65
+ endIndex: endIndex,
66
+ originalFrom: $from.pos,
67
+ originalTo: $to.pos,
68
+ contentStartOffsets: contentStartOffsets,
69
+ rootListStart: rootListStart,
70
+ docSize: tr.doc.content.size
71
+ }),
72
+ from = _computeSelectionOffs.from,
73
+ to = _computeSelectionOffs.to;
74
+ restoreSelection({
75
+ tr: tr,
76
+ originalSelection: selection,
77
+ from: from,
78
+ to: to
79
+ });
80
+ tr.scrollIntoView();
81
+ return tr;
82
+ }
@@ -14,6 +14,8 @@ import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model';
14
14
  import { TextSelection } from '@atlaskit/editor-prosemirror/state';
15
15
  import { findParentNodeOfType, findParentNodeOfTypeClosestToPos, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
16
16
  import { fg } from '@atlaskit/platform-feature-flags';
17
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
18
+ import { moveSelectedTaskListItems } from './actions/move-selected-task-list-items';
17
19
  import { joinAtCut, liftSelection, wrapSelectionInTaskList } from './commands';
18
20
  import { findFirstParentListNode, getBlockRange, getCurrentIndentLevel, getTaskItemIndex, isActionOrDecisionItem, isActionOrDecisionList, isEmptyTaskDecision, isInFirstTextblockOfBlockTaskItem, isInLastTextblockOfBlockTaskItem, isInsideDecision, isInsideTask, isInsideTaskOrDecisionItem, isTable, liftBlock, walkOut } from './helpers';
19
21
  import { insertTaskDecisionWithAnalytics } from './insert-commands';
@@ -98,6 +100,20 @@ export var getUnindentCommand = function getUnindentCommand(editorAnalyticsAPI)
98
100
  return filter(isInsideTask, function (state, dispatch) {
99
101
  var normalizedSelection = normalizeTaskItemsSelection(state.selection);
100
102
  var curIndentLevel = getCurrentIndentLevel(normalizedSelection);
103
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
104
+ if (!curIndentLevel) {
105
+ return true;
106
+ }
107
+ var outdentTr = moveSelectedTaskListItems(state.tr, -1);
108
+ if (outdentTr) {
109
+ withAnalytics(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, INDENT_DIRECTION.OUTDENT, inputMethod))(function (_state, d) {
110
+ d === null || d === void 0 || d(outdentTr);
111
+ return true;
112
+ })(state, dispatch);
113
+ return true;
114
+ }
115
+ return false;
116
+ }
101
117
  if (!curIndentLevel || curIndentLevel === 1) {
102
118
  return false;
103
119
  }
@@ -128,6 +144,20 @@ export var getIndentCommand = function getIndentCommand(editorAnalyticsAPI) {
128
144
  return filter(isInsideTask, function (state, dispatch) {
129
145
  var normalizedSelection = normalizeTaskItemsSelection(state.selection);
130
146
  var curIndentLevel = getCurrentIndentLevel(normalizedSelection);
147
+ if (expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
148
+ if (!curIndentLevel) {
149
+ return true;
150
+ }
151
+ var indentTr = moveSelectedTaskListItems(state.tr, 1);
152
+ if (indentTr) {
153
+ withAnalytics(editorAnalyticsAPI, indentationAnalytics(curIndentLevel, INDENT_DIRECTION.INDENT, inputMethod))(function (_state, d) {
154
+ d === null || d === void 0 || d(indentTr);
155
+ return true;
156
+ })(state, dispatch);
157
+ return true;
158
+ }
159
+ return false;
160
+ }
131
161
  if (!curIndentLevel || curIndentLevel >= 6) {
132
162
  return true;
133
163
  }
@@ -0,0 +1,206 @@
1
+ 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; } } }; }
2
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
3
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
4
+ import { uuid } from '@atlaskit/adf-schema';
5
+ import { flattenList as flattenListBase } from '@atlaskit/editor-common/lists';
6
+ /**
7
+ * Flattens a taskList tree into an array of task items with computed depths.
8
+ * Only selected items have their depth adjusted by indentDelta.
9
+ *
10
+ * Delegates to the shared `flattenList` with task-list-specific callbacks.
11
+ */
12
+ export function flattenTaskList(options) {
13
+ var _options$doc$type$sch = options.doc.type.schema.nodes,
14
+ taskList = _options$doc$type$sch.taskList,
15
+ taskItem = _options$doc$type$sch.taskItem,
16
+ blockTaskItem = _options$doc$type$sch.blockTaskItem;
17
+ return flattenListBase(options, {
18
+ isContentNode: function isContentNode(node, parent) {
19
+ var isTaskItemType = node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
20
+ return isTaskItemType && parent != null && parent.type === taskList;
21
+ },
22
+ getSelectionBounds: function getSelectionBounds(node, pos) {
23
+ return {
24
+ start: pos,
25
+ end: pos + node.nodeSize
26
+ };
27
+ },
28
+ getDepth: function getDepth(resolvedDepth, rootDepth) {
29
+ return resolvedDepth - rootDepth - 1;
30
+ }
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Rebuilds a taskList tree from a flattened array of task items.
36
+ * Uses a stack-based approach to create proper nesting.
37
+ *
38
+ * Preserves original taskList attributes (e.g. localId) for wrappers
39
+ * of unselected items. Only generates fresh UUIDs for newly-created
40
+ * nesting levels (i.e. when items were moved via indent/outdent).
41
+ *
42
+ * Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
43
+ *
44
+ * taskList
45
+ * taskItem 'A'
46
+ * taskList
47
+ * taskItem 'B'
48
+ * taskList
49
+ * taskItem 'C'
50
+ * taskItem 'D'
51
+ * taskItem 'E'
52
+ *
53
+ * Also computes `contentStartOffsets`: for each item (by index),
54
+ * the offset within the returned fragment where the item's content
55
+ * begins. This is used for accurate selection restoration.
56
+ */
57
+ export function rebuildTaskList(items, schema) {
58
+ var _stack$0$listAttrs;
59
+ var taskList = schema.nodes.taskList;
60
+ var contentStartOffsets = new Array(items.length);
61
+ if (items.length === 0) {
62
+ return null;
63
+ }
64
+
65
+ // Each stack entry collects children for a taskList at a given depth.
66
+ // The root entry (depth -1) is special: its children become the content
67
+ // of the outermost taskList directly (not wrapped in another taskList).
68
+ // Entries at depth >= 0 each produce a nested taskList when popped.
69
+ // listAttrs preserves the original parent's attributes when available.
70
+ // sourceParentAttrs tracks the original parent taskList attrs of the items
71
+ // in this entry, used to decide whether consecutive same-depth items
72
+ // should share a wrapper or be separated.
73
+
74
+ // Start with the root level (depth -1 represents the root taskList wrapper)
75
+ var stack = [{
76
+ depth: -1,
77
+ children: [],
78
+ listAttrs: items[0].parentListAttrs,
79
+ sourceParentAttrs: items[0].parentListAttrs
80
+ }];
81
+ var _iterator = _createForOfIteratorHelper(items),
82
+ _step;
83
+ try {
84
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
85
+ var item = _step.value;
86
+ var targetDepth = item.depth;
87
+
88
+ // Pop stack entries strictly deeper than target, wrapping them into taskLists
89
+ while (stack.length > 1 && stack[stack.length - 1].depth > targetDepth) {
90
+ var _popped$listAttrs2;
91
+ var _popped = stack.pop();
92
+ if (!_popped) {
93
+ break;
94
+ }
95
+ var _attrs = (_popped$listAttrs2 = _popped.listAttrs) !== null && _popped$listAttrs2 !== void 0 ? _popped$listAttrs2 : {
96
+ localId: uuid.generate()
97
+ };
98
+ var _wrappedList = taskList.create(_attrs, _popped.children);
99
+ stack[stack.length - 1].children.push(_wrappedList);
100
+ }
101
+
102
+ // If the stack top is at the same depth as the target, decide whether
103
+ // to merge into the existing entry or close it and start a new one.
104
+ // Unselected items from different original parent taskLists get
105
+ // separate wrappers (preserving original nesting). Selected (moved)
106
+ // items always merge with whatever is already at the target depth,
107
+ // since they're being placed into a new structural context.
108
+ if (stack.length > 1 && stack[stack.length - 1].depth === targetDepth && !item.isSelected) {
109
+ var top = stack[stack.length - 1];
110
+ if (top.sourceParentAttrs !== item.parentListAttrs) {
111
+ var _popped2$listAttrs;
112
+ var _popped2 = stack.pop();
113
+ if (!_popped2) {
114
+ break;
115
+ }
116
+ var _attrs2 = (_popped2$listAttrs = _popped2.listAttrs) !== null && _popped2$listAttrs !== void 0 ? _popped2$listAttrs : {
117
+ localId: uuid.generate()
118
+ };
119
+ var _wrappedList2 = taskList.create(_attrs2, _popped2.children);
120
+ stack[stack.length - 1].children.push(_wrappedList2);
121
+ }
122
+ }
123
+
124
+ // Push new stack entries to reach the target depth.
125
+ // Skip intermediate entries at depth 0 — depth-0 items belong
126
+ // directly in the root entry (depth -1), so we only need
127
+ // intermediates for depths > 1.
128
+ while (stack[stack.length - 1].depth < targetDepth - 1) {
129
+ var nextDepth = Math.max(stack[stack.length - 1].depth + 1, 1);
130
+ stack.push({
131
+ depth: nextDepth,
132
+ children: [],
133
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
134
+ sourceParentAttrs: item.parentListAttrs
135
+ });
136
+ }
137
+
138
+ // Add the item at the target depth
139
+ if (targetDepth === 0) {
140
+ // Depth 0 items go directly into the root taskList
141
+ stack[0].children.push(item.node);
142
+ } else {
143
+ // Ensure there's a stack entry at depth targetDepth to hold this item
144
+ if (stack[stack.length - 1].depth < targetDepth) {
145
+ stack.push({
146
+ depth: targetDepth,
147
+ children: [],
148
+ listAttrs: item.isSelected ? null : item.parentListAttrs,
149
+ sourceParentAttrs: item.parentListAttrs
150
+ });
151
+ }
152
+ stack[stack.length - 1].children.push(item.node);
153
+ }
154
+ }
155
+
156
+ // Close remaining stack entries
157
+ } catch (err) {
158
+ _iterator.e(err);
159
+ } finally {
160
+ _iterator.f();
161
+ }
162
+ while (stack.length > 1) {
163
+ var _popped$listAttrs;
164
+ var popped = stack.pop();
165
+ if (!popped) {
166
+ break;
167
+ }
168
+ var attrs = (_popped$listAttrs = popped.listAttrs) !== null && _popped$listAttrs !== void 0 ? _popped$listAttrs : {
169
+ localId: uuid.generate()
170
+ };
171
+ var wrappedList = taskList.create(attrs, popped.children);
172
+ stack[stack.length - 1].children.push(wrappedList);
173
+ }
174
+
175
+ // The root entry's children form the root taskList.
176
+ // Preserve the root's original attrs when available.
177
+ var rootChildren = stack[0].children;
178
+ var rootAttrs = (_stack$0$listAttrs = stack[0].listAttrs) !== null && _stack$0$listAttrs !== void 0 ? _stack$0$listAttrs : {
179
+ localId: uuid.generate()
180
+ };
181
+ var rootList = taskList.create(rootAttrs, rootChildren);
182
+
183
+ // Compute contentStartOffsets by walking the rebuilt tree.
184
+ // Each taskItem's content starts at (pos_within_root + 1) for the taskItem opening tag.
185
+ // We add +1 for the root taskList's opening tag.
186
+ var isTaskItemType = function isTaskItemType(node) {
187
+ var _schema$nodes = schema.nodes,
188
+ taskItem = _schema$nodes.taskItem,
189
+ blockTaskItem = _schema$nodes.blockTaskItem;
190
+ return node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
191
+ };
192
+ var itemIdx = 0;
193
+ rootList.descendants(function (node, pos) {
194
+ if (isTaskItemType(node) && itemIdx < items.length) {
195
+ // pos is relative to rootList content start;
196
+ // +1 for rootList's opening tag, +1 for taskItem's opening tag
197
+ contentStartOffsets[itemIdx] = 1 + pos + 1;
198
+ itemIdx++;
199
+ }
200
+ return true;
201
+ });
202
+ return {
203
+ node: rootList,
204
+ contentStartOffsets: contentStartOffsets
205
+ };
206
+ }
@@ -167,7 +167,7 @@ export var tasksAndDecisionsPlugin = function tasksAndDecisionsPlugin(_ref3) {
167
167
  node: decisionItemSpecWithFixedToDOM()
168
168
  }, {
169
169
  name: 'taskList',
170
- node: expValEquals('platform_editor_flexible_list_indentation', 'isEnabled', true) ? taskListWithFlexibleFirstChildStage0 : taskList
170
+ node: expValEquals('platform_editor_flexible_list_schema', 'isEnabled', true) ? taskListWithFlexibleFirstChildStage0 : taskList
171
171
  }, {
172
172
  name: 'taskItem',
173
173
  node: taskItemNodeSpec()
@@ -0,0 +1,2 @@
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare function moveSelectedTaskListItems(tr: Transaction, indentDelta: number): Transaction | null;
@@ -0,0 +1,36 @@
1
+ import { type FlattenedItem, type FlattenListOptions, type FlattenListResult } from '@atlaskit/editor-common/lists';
2
+ import type { Node as PMNode, Schema } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Flattens a taskList tree into an array of task items with computed depths.
5
+ * Only selected items have their depth adjusted by indentDelta.
6
+ *
7
+ * Delegates to the shared `flattenList` with task-list-specific callbacks.
8
+ */
9
+ export declare function flattenTaskList(options: FlattenListOptions): FlattenListResult | null;
10
+ /**
11
+ * Rebuilds a taskList tree from a flattened array of task items.
12
+ * Uses a stack-based approach to create proper nesting.
13
+ *
14
+ * Preserves original taskList attributes (e.g. localId) for wrappers
15
+ * of unselected items. Only generates fresh UUIDs for newly-created
16
+ * nesting levels (i.e. when items were moved via indent/outdent).
17
+ *
18
+ * Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
19
+ *
20
+ * taskList
21
+ * taskItem 'A'
22
+ * taskList
23
+ * taskItem 'B'
24
+ * taskList
25
+ * taskItem 'C'
26
+ * taskItem 'D'
27
+ * taskItem 'E'
28
+ *
29
+ * Also computes `contentStartOffsets`: for each item (by index),
30
+ * the offset within the returned fragment where the item's content
31
+ * begins. This is used for accurate selection restoration.
32
+ */
33
+ export declare function rebuildTaskList(items: FlattenedItem[], schema: Schema): {
34
+ contentStartOffsets: number[];
35
+ node: PMNode;
36
+ } | null;
@@ -0,0 +1,2 @@
1
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
2
+ export declare function moveSelectedTaskListItems(tr: Transaction, indentDelta: number): Transaction | null;
@@ -0,0 +1,36 @@
1
+ import { type FlattenedItem, type FlattenListOptions, type FlattenListResult } from '@atlaskit/editor-common/lists';
2
+ import type { Node as PMNode, Schema } from '@atlaskit/editor-prosemirror/model';
3
+ /**
4
+ * Flattens a taskList tree into an array of task items with computed depths.
5
+ * Only selected items have their depth adjusted by indentDelta.
6
+ *
7
+ * Delegates to the shared `flattenList` with task-list-specific callbacks.
8
+ */
9
+ export declare function flattenTaskList(options: FlattenListOptions): FlattenListResult | null;
10
+ /**
11
+ * Rebuilds a taskList tree from a flattened array of task items.
12
+ * Uses a stack-based approach to create proper nesting.
13
+ *
14
+ * Preserves original taskList attributes (e.g. localId) for wrappers
15
+ * of unselected items. Only generates fresh UUIDs for newly-created
16
+ * nesting levels (i.e. when items were moved via indent/outdent).
17
+ *
18
+ * Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
19
+ *
20
+ * taskList
21
+ * taskItem 'A'
22
+ * taskList
23
+ * taskItem 'B'
24
+ * taskList
25
+ * taskItem 'C'
26
+ * taskItem 'D'
27
+ * taskItem 'E'
28
+ *
29
+ * Also computes `contentStartOffsets`: for each item (by index),
30
+ * the offset within the returned fragment where the item's content
31
+ * begins. This is used for accurate selection restoration.
32
+ */
33
+ export declare function rebuildTaskList(items: FlattenedItem[], schema: Schema): {
34
+ contentStartOffsets: number[];
35
+ node: PMNode;
36
+ } | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-tasks-and-decisions",
3
- "version": "11.2.1",
3
+ "version": "11.3.1",
4
4
  "description": "Tasks and decisions plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -49,14 +49,14 @@
49
49
  "@atlaskit/primitives": "^18.0.0",
50
50
  "@atlaskit/prosemirror-input-rules": "^3.6.0",
51
51
  "@atlaskit/task-decision": "^19.3.0",
52
- "@atlaskit/tmp-editor-statsig": "^44.0.0",
52
+ "@atlaskit/tmp-editor-statsig": "^44.2.0",
53
53
  "@atlaskit/tokens": "^11.1.0",
54
54
  "@babel/runtime": "^7.0.0",
55
55
  "@compiled/react": "^0.20.0",
56
56
  "bind-event-listener": "^3.0.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@atlaskit/editor-common": "^112.7.0",
59
+ "@atlaskit/editor-common": "^112.8.0",
60
60
  "react": "^18.2.0",
61
61
  "react-dom": "^18.2.0",
62
62
  "react-intl-next": "npm:react-intl@^5.18.1"