@atlaskit/editor-plugin-tasks-and-decisions 13.0.1 → 13.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-tasks-and-decisions
2
2
 
3
+ ## 13.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 13.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`402738b592e0b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/402738b592e0b) -
14
+ Fix invalid flexible list structures caused by delete, paste, and typing operations.
15
+
16
+ Under platform_editor_flexible_list_schema, operations that remove content spanning list or task
17
+ list items could leave nodes with a nested list as their first child instead of a required
18
+ paragraph/item. Normalisation now runs efficiently on all relevant transactions in
19
+ appendTransaction.
20
+
21
+ - Updated dependencies
22
+
3
23
  ## 13.0.1
4
24
 
5
25
  ### Patch Changes
@@ -15,12 +15,14 @@ var _utils = require("@atlaskit/editor-common/utils");
15
15
  var _state = require("@atlaskit/editor-prosemirror/state");
16
16
  var _view = require("@atlaskit/editor-prosemirror/view");
17
17
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
18
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
18
19
  var _DecisionItemNodeView = require("../nodeviews/DecisionItemNodeView");
19
20
  var _taskNodeView = require("../nodeviews/task-node-view");
20
21
  var _actions = require("./actions");
21
22
  var _helpers = require("./helpers");
22
23
  var _pluginKey = require("./plugin-key");
23
24
  var _taskItemOnChange = require("./taskItemOnChange");
25
+ var _transforms = require("./transforms");
24
26
  var _types = require("./types");
25
27
  var _paste = require("./utils/paste");
26
28
  var _excluded = ["localId"];
@@ -268,6 +270,26 @@ function createPlugin(portalProviderAPI, eventDispatcher, dispatch, api, getIntl
268
270
  * Note: we currently do not handle the edge case where two nodes may have the same localId
269
271
  */
270
272
  appendTransaction: function appendTransaction(transactions, _oldState, newState) {
273
+ // Normalise taskList structure first — runs on its own transaction (without addToHistory: false)
274
+ // so it is included in history and correctly undone with the triggering operation.
275
+ if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_schema', 'isEnabled', true) && transactions.some(function (t) {
276
+ return t.docChanged;
277
+ })) {
278
+ var normTr = (0, _transforms.applyTaskListNormalisationFixes)({
279
+ tr: newState.tr,
280
+ transactions: transactions,
281
+ doc: newState.doc,
282
+ schema: newState.schema
283
+ });
284
+ if (normTr.docChanged) {
285
+ // Return the normalisation transaction — the next appendTransaction call will
286
+ // assign any missing localIds to newly inserted nodes via the localId path below.
287
+ return normTr;
288
+ }
289
+ }
290
+
291
+ // Assign unique localIds to any new nodes that don't have one.
292
+ // Runs with addToHistory: false so localId assignment is not part of the undo history.
271
293
  var tr = newState.tr;
272
294
  var modified = false;
273
295
  transactions.forEach(function (transaction) {
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.applyTaskListNormalisationFixes = applyTaskListNormalisationFixes;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _transform = require("@atlaskit/editor-prosemirror/transform");
11
+ var _expValEqualsNoExposure = require("@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure");
12
+ 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; } } }; }
13
+ 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; } }
14
+ 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; }
15
+ function getAffectedTaskListsFromTransactions(transactions, doc, schema) {
16
+ var taskList = schema.nodes.taskList;
17
+ if (!taskList) {
18
+ return new Map();
19
+ }
20
+ var result = new Map();
21
+ var _iterator = _createForOfIteratorHelper(transactions),
22
+ _step;
23
+ try {
24
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
25
+ var tr = _step.value;
26
+ var _iterator2 = _createForOfIteratorHelper(tr.steps),
27
+ _step2;
28
+ try {
29
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
30
+ var step = _step2.value;
31
+ // ReplaceStep and ReplaceAroundStep both have from/to — other step types are skipped.
32
+ if (!(step instanceof _transform.ReplaceStep) && !(step instanceof _transform.ReplaceAroundStep)) {
33
+ continue;
34
+ }
35
+ // Check both the start and end of each changed range, mapped to post-transaction positions.
36
+ for (var _i = 0, _arr = [step.from, step.to]; _i < _arr.length; _i++) {
37
+ var rawPos = _arr[_i];
38
+ var mappedPos = Math.min(tr.mapping.map(rawPos), doc.content.size - 1);
39
+ var $pos = doc.resolve(mappedPos);
40
+ // Walk ancestors from inner to outer, recording the outermost taskList node.
41
+ // Once we find a taskList and then exit list structure (hit a non-taskList ancestor),
42
+ // break early — prevents container nodes (e.g. panel) from causing us to
43
+ // return an outer taskList in a different structural context.
44
+ var rootTaskListPos = null;
45
+ var rootTaskListNode = null;
46
+ for (var depth = $pos.depth; depth >= 0; depth--) {
47
+ var node = $pos.node(depth);
48
+ if (node.type === taskList) {
49
+ rootTaskListPos = $pos.before(depth);
50
+ rootTaskListNode = node;
51
+ } else if (rootTaskListNode !== null && node.type !== schema.nodes.taskItem) {
52
+ // We've exited the taskList structure — stop walking.
53
+ break;
54
+ }
55
+ }
56
+ if (rootTaskListPos !== null && rootTaskListNode !== null) {
57
+ result.set(rootTaskListPos, rootTaskListNode);
58
+ }
59
+ }
60
+ }
61
+ } catch (err) {
62
+ _iterator2.e(err);
63
+ } finally {
64
+ _iterator2.f();
65
+ }
66
+ }
67
+ } catch (err) {
68
+ _iterator.e(err);
69
+ } finally {
70
+ _iterator.f();
71
+ }
72
+ return result;
73
+ }
74
+ function applyTaskListNormalisationFixes(_ref) {
75
+ var doc = _ref.doc,
76
+ schema = _ref.schema,
77
+ tr = _ref.tr,
78
+ transactions = _ref.transactions;
79
+ var _schema$nodes = schema.nodes,
80
+ taskList = _schema$nodes.taskList,
81
+ taskItem = _schema$nodes.taskItem;
82
+ if (!taskList || !taskItem) {
83
+ return tr;
84
+ }
85
+ var affectedTaskLists = getAffectedTaskListsFromTransactions(transactions, doc, schema);
86
+ if (affectedTaskLists.size === 0) {
87
+ return tr;
88
+ }
89
+
90
+ // Sort by position descending so we process deeper/later positions first,
91
+ // preventing earlier insertions from shifting later positions.
92
+ var sortedEntries = (0, _toConsumableArray2.default)(affectedTaskLists.entries()).sort(function (_ref2, _ref3) {
93
+ var _ref4 = (0, _slicedToArray2.default)(_ref2, 1),
94
+ a = _ref4[0];
95
+ var _ref5 = (0, _slicedToArray2.default)(_ref3, 1),
96
+ b = _ref5[0];
97
+ return b - a;
98
+ });
99
+ var _iterator3 = _createForOfIteratorHelper(sortedEntries),
100
+ _step3;
101
+ try {
102
+ var _loop = function _loop() {
103
+ var _step3$value = (0, _slicedToArray2.default)(_step3.value, 1),
104
+ taskListPos = _step3$value[0];
105
+ // Re-resolve the taskList node from the current transaction doc (post-operation state).
106
+ var mappedTaskListPos = tr.mapping.map(taskListPos);
107
+ var currentTaskListNode = tr.doc.nodeAt(mappedTaskListPos);
108
+ if (!currentTaskListNode) {
109
+ return 1; // continue
110
+ }
111
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
112
+ // Collect positions of all taskList nodes (at any depth) whose direct children
113
+ // include a taskList — this is the invalid structure. The taskList schema requires
114
+ // taskItem as children, not nested taskLists directly.
115
+ // Process in reverse order so higher-position insertions don't shift lower positions.
116
+ var invalidTaskListPositions = [];
117
+ currentTaskListNode.descendants(function (node, offsetPos) {
118
+ if (node.type === taskList) {
119
+ // A taskList as the FIRST child of another taskList is invalid — it means a
120
+ // delete or paste removed the leading taskItem, leaving a bare nested list.
121
+ // A taskList that follows a taskItem is valid (that's normal indentation).
122
+ var firstChild = node.firstChild;
123
+ if (firstChild && firstChild.type === taskList) {
124
+ var pos = mappedTaskListPos + 1 + offsetPos + 1;
125
+ invalidTaskListPositions.push(pos);
126
+ }
127
+ }
128
+ return true;
129
+ });
130
+
131
+ // Process in reverse (highest positions first).
132
+ for (var i = invalidTaskListPositions.length - 1; i >= 0; i--) {
133
+ var remappedPos = tr.mapping.map(invalidTaskListPositions[i]);
134
+ var emptyTaskItem = taskItem.createAndFill();
135
+ if (emptyTaskItem) {
136
+ tr.insert(remappedPos, emptyTaskItem);
137
+ }
138
+ }
139
+ }
140
+ };
141
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
142
+ if (_loop()) continue;
143
+ }
144
+ } catch (err) {
145
+ _iterator3.e(err);
146
+ } finally {
147
+ _iterator3.f();
148
+ }
149
+ return tr;
150
+ }
@@ -6,12 +6,14 @@ import { getStepRange } from '@atlaskit/editor-common/utils';
6
6
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
7
7
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
8
8
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
9
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
9
10
  import { DecisionItemNodeView } from '../nodeviews/DecisionItemNodeView';
10
11
  import { taskView } from '../nodeviews/task-node-view';
11
- import { focusTaskDecision, setProvider, openRequestEditPopup } from './actions';
12
+ import { focusTaskDecision, openRequestEditPopup, setProvider } from './actions';
12
13
  import { focusCheckbox, focusCheckboxAndUpdateSelection, getTaskItemDataAtPos, getTaskItemDataToFocus, isInsideTask, removeCheckboxFocus } from './helpers';
13
14
  import { stateKey } from './plugin-key';
14
15
  import { taskItemOnChange } from './taskItemOnChange';
16
+ import { applyTaskListNormalisationFixes } from './transforms';
15
17
  import { ACTIONS } from './types';
16
18
  import { tempTransformSliceToRemoveBlockTaskItem } from './utils/paste';
17
19
  function nodesBetweenChanged(tr, f, startPos) {
@@ -264,6 +266,24 @@ export function createPlugin(portalProviderAPI, eventDispatcher, dispatch, api,
264
266
  * Note: we currently do not handle the edge case where two nodes may have the same localId
265
267
  */
266
268
  appendTransaction: (transactions, _oldState, newState) => {
269
+ // Normalise taskList structure first — runs on its own transaction (without addToHistory: false)
270
+ // so it is included in history and correctly undone with the triggering operation.
271
+ if (expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true) && transactions.some(t => t.docChanged)) {
272
+ const normTr = applyTaskListNormalisationFixes({
273
+ tr: newState.tr,
274
+ transactions,
275
+ doc: newState.doc,
276
+ schema: newState.schema
277
+ });
278
+ if (normTr.docChanged) {
279
+ // Return the normalisation transaction — the next appendTransaction call will
280
+ // assign any missing localIds to newly inserted nodes via the localId path below.
281
+ return normTr;
282
+ }
283
+ }
284
+
285
+ // Assign unique localIds to any new nodes that don't have one.
286
+ // Runs with addToHistory: false so localId assignment is not part of the undo history.
267
287
  const tr = newState.tr;
268
288
  let modified = false;
269
289
  transactions.forEach(transaction => {
@@ -0,0 +1,104 @@
1
+ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
2
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
3
+ function getAffectedTaskListsFromTransactions(transactions, doc, schema) {
4
+ const {
5
+ taskList
6
+ } = schema.nodes;
7
+ if (!taskList) {
8
+ return new Map();
9
+ }
10
+ const result = new Map();
11
+ for (const tr of transactions) {
12
+ for (const step of tr.steps) {
13
+ // ReplaceStep and ReplaceAroundStep both have from/to — other step types are skipped.
14
+ if (!(step instanceof ReplaceStep) && !(step instanceof ReplaceAroundStep)) {
15
+ continue;
16
+ }
17
+ // Check both the start and end of each changed range, mapped to post-transaction positions.
18
+ for (const rawPos of [step.from, step.to]) {
19
+ const mappedPos = Math.min(tr.mapping.map(rawPos), doc.content.size - 1);
20
+ const $pos = doc.resolve(mappedPos);
21
+ // Walk ancestors from inner to outer, recording the outermost taskList node.
22
+ // Once we find a taskList and then exit list structure (hit a non-taskList ancestor),
23
+ // break early — prevents container nodes (e.g. panel) from causing us to
24
+ // return an outer taskList in a different structural context.
25
+ let rootTaskListPos = null;
26
+ let rootTaskListNode = null;
27
+ for (let depth = $pos.depth; depth >= 0; depth--) {
28
+ const node = $pos.node(depth);
29
+ if (node.type === taskList) {
30
+ rootTaskListPos = $pos.before(depth);
31
+ rootTaskListNode = node;
32
+ } else if (rootTaskListNode !== null && node.type !== schema.nodes.taskItem) {
33
+ // We've exited the taskList structure — stop walking.
34
+ break;
35
+ }
36
+ }
37
+ if (rootTaskListPos !== null && rootTaskListNode !== null) {
38
+ result.set(rootTaskListPos, rootTaskListNode);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+ export function applyTaskListNormalisationFixes({
46
+ doc,
47
+ schema,
48
+ tr,
49
+ transactions
50
+ }) {
51
+ const {
52
+ taskList,
53
+ taskItem
54
+ } = schema.nodes;
55
+ if (!taskList || !taskItem) {
56
+ return tr;
57
+ }
58
+ const affectedTaskLists = getAffectedTaskListsFromTransactions(transactions, doc, schema);
59
+ if (affectedTaskLists.size === 0) {
60
+ return tr;
61
+ }
62
+
63
+ // Sort by position descending so we process deeper/later positions first,
64
+ // preventing earlier insertions from shifting later positions.
65
+ const sortedEntries = [...affectedTaskLists.entries()].sort(([a], [b]) => b - a);
66
+ for (const [taskListPos] of sortedEntries) {
67
+ // Re-resolve the taskList node from the current transaction doc (post-operation state).
68
+ const mappedTaskListPos = tr.mapping.map(taskListPos);
69
+ const currentTaskListNode = tr.doc.nodeAt(mappedTaskListPos);
70
+ if (!currentTaskListNode) {
71
+ continue;
72
+ }
73
+ if (!expValEqualsNoExposure('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
74
+ // Collect positions of all taskList nodes (at any depth) whose direct children
75
+ // include a taskList — this is the invalid structure. The taskList schema requires
76
+ // taskItem as children, not nested taskLists directly.
77
+ // Process in reverse order so higher-position insertions don't shift lower positions.
78
+ const invalidTaskListPositions = [];
79
+ currentTaskListNode.descendants((node, offsetPos) => {
80
+ if (node.type === taskList) {
81
+ // A taskList as the FIRST child of another taskList is invalid — it means a
82
+ // delete or paste removed the leading taskItem, leaving a bare nested list.
83
+ // A taskList that follows a taskItem is valid (that's normal indentation).
84
+ const firstChild = node.firstChild;
85
+ if (firstChild && firstChild.type === taskList) {
86
+ const pos = mappedTaskListPos + 1 + offsetPos + 1;
87
+ invalidTaskListPositions.push(pos);
88
+ }
89
+ }
90
+ return true;
91
+ });
92
+
93
+ // Process in reverse (highest positions first).
94
+ for (let i = invalidTaskListPositions.length - 1; i >= 0; i--) {
95
+ const remappedPos = tr.mapping.map(invalidTaskListPositions[i]);
96
+ const emptyTaskItem = taskItem.createAndFill();
97
+ if (emptyTaskItem) {
98
+ tr.insert(remappedPos, emptyTaskItem);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return tr;
104
+ }
@@ -11,12 +11,14 @@ import { getStepRange } from '@atlaskit/editor-common/utils';
11
11
  import { NodeSelection } from '@atlaskit/editor-prosemirror/state';
12
12
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
13
13
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
14
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
14
15
  import { DecisionItemNodeView } from '../nodeviews/DecisionItemNodeView';
15
16
  import { taskView } from '../nodeviews/task-node-view';
16
- import { focusTaskDecision, setProvider, openRequestEditPopup } from './actions';
17
+ import { focusTaskDecision, openRequestEditPopup, setProvider } from './actions';
17
18
  import { focusCheckbox, focusCheckboxAndUpdateSelection, getTaskItemDataAtPos, getTaskItemDataToFocus, isInsideTask, removeCheckboxFocus } from './helpers';
18
19
  import { stateKey } from './plugin-key';
19
20
  import { taskItemOnChange } from './taskItemOnChange';
21
+ import { applyTaskListNormalisationFixes } from './transforms';
20
22
  import { ACTIONS } from './types';
21
23
  import { tempTransformSliceToRemoveBlockTaskItem } from './utils/paste';
22
24
  function nodesBetweenChanged(tr, f, startPos) {
@@ -261,6 +263,26 @@ export function createPlugin(portalProviderAPI, eventDispatcher, dispatch, api,
261
263
  * Note: we currently do not handle the edge case where two nodes may have the same localId
262
264
  */
263
265
  appendTransaction: function appendTransaction(transactions, _oldState, newState) {
266
+ // Normalise taskList structure first — runs on its own transaction (without addToHistory: false)
267
+ // so it is included in history and correctly undone with the triggering operation.
268
+ if (expValEqualsNoExposure('platform_editor_flexible_list_schema', 'isEnabled', true) && transactions.some(function (t) {
269
+ return t.docChanged;
270
+ })) {
271
+ var normTr = applyTaskListNormalisationFixes({
272
+ tr: newState.tr,
273
+ transactions: transactions,
274
+ doc: newState.doc,
275
+ schema: newState.schema
276
+ });
277
+ if (normTr.docChanged) {
278
+ // Return the normalisation transaction — the next appendTransaction call will
279
+ // assign any missing localIds to newly inserted nodes via the localId path below.
280
+ return normTr;
281
+ }
282
+ }
283
+
284
+ // Assign unique localIds to any new nodes that don't have one.
285
+ // Runs with addToHistory: false so localId assignment is not part of the undo history.
264
286
  var tr = newState.tr;
265
287
  var modified = false;
266
288
  transactions.forEach(function (transaction) {
@@ -0,0 +1,143 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
+ 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; } } }; }
4
+ 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; } }
5
+ 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; }
6
+ import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
7
+ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
8
+ function getAffectedTaskListsFromTransactions(transactions, doc, schema) {
9
+ var taskList = schema.nodes.taskList;
10
+ if (!taskList) {
11
+ return new Map();
12
+ }
13
+ var result = new Map();
14
+ var _iterator = _createForOfIteratorHelper(transactions),
15
+ _step;
16
+ try {
17
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
18
+ var tr = _step.value;
19
+ var _iterator2 = _createForOfIteratorHelper(tr.steps),
20
+ _step2;
21
+ try {
22
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
23
+ var step = _step2.value;
24
+ // ReplaceStep and ReplaceAroundStep both have from/to — other step types are skipped.
25
+ if (!(step instanceof ReplaceStep) && !(step instanceof ReplaceAroundStep)) {
26
+ continue;
27
+ }
28
+ // Check both the start and end of each changed range, mapped to post-transaction positions.
29
+ for (var _i = 0, _arr = [step.from, step.to]; _i < _arr.length; _i++) {
30
+ var rawPos = _arr[_i];
31
+ var mappedPos = Math.min(tr.mapping.map(rawPos), doc.content.size - 1);
32
+ var $pos = doc.resolve(mappedPos);
33
+ // Walk ancestors from inner to outer, recording the outermost taskList node.
34
+ // Once we find a taskList and then exit list structure (hit a non-taskList ancestor),
35
+ // break early — prevents container nodes (e.g. panel) from causing us to
36
+ // return an outer taskList in a different structural context.
37
+ var rootTaskListPos = null;
38
+ var rootTaskListNode = null;
39
+ for (var depth = $pos.depth; depth >= 0; depth--) {
40
+ var node = $pos.node(depth);
41
+ if (node.type === taskList) {
42
+ rootTaskListPos = $pos.before(depth);
43
+ rootTaskListNode = node;
44
+ } else if (rootTaskListNode !== null && node.type !== schema.nodes.taskItem) {
45
+ // We've exited the taskList structure — stop walking.
46
+ break;
47
+ }
48
+ }
49
+ if (rootTaskListPos !== null && rootTaskListNode !== null) {
50
+ result.set(rootTaskListPos, rootTaskListNode);
51
+ }
52
+ }
53
+ }
54
+ } catch (err) {
55
+ _iterator2.e(err);
56
+ } finally {
57
+ _iterator2.f();
58
+ }
59
+ }
60
+ } catch (err) {
61
+ _iterator.e(err);
62
+ } finally {
63
+ _iterator.f();
64
+ }
65
+ return result;
66
+ }
67
+ export function applyTaskListNormalisationFixes(_ref) {
68
+ var doc = _ref.doc,
69
+ schema = _ref.schema,
70
+ tr = _ref.tr,
71
+ transactions = _ref.transactions;
72
+ var _schema$nodes = schema.nodes,
73
+ taskList = _schema$nodes.taskList,
74
+ taskItem = _schema$nodes.taskItem;
75
+ if (!taskList || !taskItem) {
76
+ return tr;
77
+ }
78
+ var affectedTaskLists = getAffectedTaskListsFromTransactions(transactions, doc, schema);
79
+ if (affectedTaskLists.size === 0) {
80
+ return tr;
81
+ }
82
+
83
+ // Sort by position descending so we process deeper/later positions first,
84
+ // preventing earlier insertions from shifting later positions.
85
+ var sortedEntries = _toConsumableArray(affectedTaskLists.entries()).sort(function (_ref2, _ref3) {
86
+ var _ref4 = _slicedToArray(_ref2, 1),
87
+ a = _ref4[0];
88
+ var _ref5 = _slicedToArray(_ref3, 1),
89
+ b = _ref5[0];
90
+ return b - a;
91
+ });
92
+ var _iterator3 = _createForOfIteratorHelper(sortedEntries),
93
+ _step3;
94
+ try {
95
+ var _loop = function _loop() {
96
+ var _step3$value = _slicedToArray(_step3.value, 1),
97
+ taskListPos = _step3$value[0];
98
+ // Re-resolve the taskList node from the current transaction doc (post-operation state).
99
+ var mappedTaskListPos = tr.mapping.map(taskListPos);
100
+ var currentTaskListNode = tr.doc.nodeAt(mappedTaskListPos);
101
+ if (!currentTaskListNode) {
102
+ return 1; // continue
103
+ }
104
+ if (!expValEqualsNoExposure('platform_editor_flexible_list_indentation', 'isEnabled', true)) {
105
+ // Collect positions of all taskList nodes (at any depth) whose direct children
106
+ // include a taskList — this is the invalid structure. The taskList schema requires
107
+ // taskItem as children, not nested taskLists directly.
108
+ // Process in reverse order so higher-position insertions don't shift lower positions.
109
+ var invalidTaskListPositions = [];
110
+ currentTaskListNode.descendants(function (node, offsetPos) {
111
+ if (node.type === taskList) {
112
+ // A taskList as the FIRST child of another taskList is invalid — it means a
113
+ // delete or paste removed the leading taskItem, leaving a bare nested list.
114
+ // A taskList that follows a taskItem is valid (that's normal indentation).
115
+ var firstChild = node.firstChild;
116
+ if (firstChild && firstChild.type === taskList) {
117
+ var pos = mappedTaskListPos + 1 + offsetPos + 1;
118
+ invalidTaskListPositions.push(pos);
119
+ }
120
+ }
121
+ return true;
122
+ });
123
+
124
+ // Process in reverse (highest positions first).
125
+ for (var i = invalidTaskListPositions.length - 1; i >= 0; i--) {
126
+ var remappedPos = tr.mapping.map(invalidTaskListPositions[i]);
127
+ var emptyTaskItem = taskItem.createAndFill();
128
+ if (emptyTaskItem) {
129
+ tr.insert(remappedPos, emptyTaskItem);
130
+ }
131
+ }
132
+ }
133
+ };
134
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
135
+ if (_loop()) continue;
136
+ }
137
+ } catch (err) {
138
+ _iterator3.e(err);
139
+ } finally {
140
+ _iterator3.f();
141
+ }
142
+ return tr;
143
+ }
@@ -0,0 +1,10 @@
1
+ import type { Node, Schema } from '@atlaskit/editor-prosemirror/model';
2
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
+ interface ApplyTaskListNormalisationFixesOptions {
4
+ doc: Node;
5
+ schema: Schema;
6
+ tr: Transaction;
7
+ transactions: readonly Transaction[];
8
+ }
9
+ export declare function applyTaskListNormalisationFixes({ doc, schema, tr, transactions, }: ApplyTaskListNormalisationFixesOptions): Transaction;
10
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { Node, Schema } from '@atlaskit/editor-prosemirror/model';
2
+ import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
+ interface ApplyTaskListNormalisationFixesOptions {
4
+ doc: Node;
5
+ schema: Schema;
6
+ tr: Transaction;
7
+ transactions: readonly Transaction[];
8
+ }
9
+ export declare function applyTaskListNormalisationFixes({ doc, schema, tr, transactions, }: ApplyTaskListNormalisationFixesOptions): Transaction;
10
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-tasks-and-decisions",
3
- "version": "13.0.1",
3
+ "version": "13.0.3",
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": "^19.0.0",
50
50
  "@atlaskit/prosemirror-input-rules": "^3.6.0",
51
51
  "@atlaskit/task-decision": "^20.0.0",
52
- "@atlaskit/tmp-editor-statsig": "^63.0.0",
52
+ "@atlaskit/tmp-editor-statsig": "^64.0.0",
53
53
  "@atlaskit/tokens": "^13.0.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": "^114.2.0",
59
+ "@atlaskit/editor-common": "^114.5.0",
60
60
  "react": "^18.2.0",
61
61
  "react-dom": "^18.2.0",
62
62
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"