@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 +20 -0
- package/dist/cjs/pm-plugins/main.js +22 -0
- package/dist/cjs/pm-plugins/transforms.js +150 -0
- package/dist/es2019/pm-plugins/main.js +21 -1
- package/dist/es2019/pm-plugins/transforms.js +104 -0
- package/dist/esm/pm-plugins/main.js +23 -1
- package/dist/esm/pm-plugins/transforms.js +143 -0
- package/dist/types/pm-plugins/transforms.d.ts +10 -0
- package/dist/types-ts4.5/pm-plugins/transforms.d.ts +10 -0
- package/package.json +3 -3
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,
|
|
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,
|
|
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.
|
|
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": "^
|
|
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.
|
|
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"
|