@atlaskit/editor-plugin-tasks-and-decisions
Version:
Tasks and decisions plugin for @atlaskit/editor-core
221 lines (210 loc) • 9.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.flattenTaskList = flattenTaskList;
exports.rebuildTaskList = rebuildTaskList;
var _adfSchema = require("@atlaskit/adf-schema");
var _lists = require("@atlaskit/editor-common/lists");
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; } } }; }
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; } }
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; }
/**
* Flattens a taskList tree into an array of task items with computed depths.
* Only selected items have their depth adjusted by indentDelta.
*
* Delegates to the shared `flattenList` with task-list-specific callbacks.
*/
function flattenTaskList(options) {
var _options$doc$type$sch = options.doc.type.schema.nodes,
taskList = _options$doc$type$sch.taskList,
taskItem = _options$doc$type$sch.taskItem,
blockTaskItem = _options$doc$type$sch.blockTaskItem;
return (0, _lists.flattenList)(options, {
isContentNode: function isContentNode(node, parent) {
var isTaskItemType = node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
return isTaskItemType && parent != null && parent.type === taskList;
},
getSelectionBounds: function getSelectionBounds(node, pos) {
return {
start: pos,
end: pos + node.nodeSize
};
},
getDepth: function getDepth(resolvedDepth, rootDepth) {
return resolvedDepth - rootDepth - 1;
}
});
}
// Each stack entry collects children for a taskList at a given depth.
// The root entry (depth -1) is special: its children become the content
// of the outermost taskList directly (not wrapped in another taskList).
// Entries at depth >= 0 each produce a nested taskList when popped.
/**
* Rebuilds a taskList tree from a flattened array of task items.
* Uses a stack-based approach to create proper nesting.
*
* Preserves original taskList attributes (e.g. localId) for wrappers
* of unselected items. Only generates fresh UUIDs for newly-created
* nesting levels (i.e. when items were moved via indent/outdent).
*
* Given items with depths [A:0, B:1, C:2, D:1, E:0], produces:
*
* taskList
* taskItem 'A'
* taskList
* taskItem 'B'
* taskList
* taskItem 'C'
* taskItem 'D'
* taskItem 'E'
*
* Also computes `contentStartOffsets`: for each item (by index),
* the offset within the returned fragment where the item's content
* begins. This is used for accurate selection restoration.
*/
function rebuildTaskList(items, schema) {
var _stack$0$listAttrs;
var taskList = schema.nodes.taskList;
var contentStartOffsets = new Array(items.length);
if (items.length === 0) {
return null;
}
// Start with the root level (depth -1 represents the root taskList wrapper)
var stack = [{
depth: -1,
children: [],
hasSelectedItems: false,
listAttrs: items[0].parentListAttrs,
sourceParentAttrs: items[0].parentListAttrs
}];
var _iterator = _createForOfIteratorHelper(items),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var item = _step.value;
var targetDepth = item.depth;
// Pop stack entries strictly deeper than target, wrapping them into taskLists
while (stack.length > 1 && stack[stack.length - 1].depth > targetDepth) {
var _popped$listAttrs2;
var _popped = stack.pop();
if (!_popped) {
break;
}
var _attrs = (_popped$listAttrs2 = _popped.listAttrs) !== null && _popped$listAttrs2 !== void 0 ? _popped$listAttrs2 : {
localId: _adfSchema.uuid.generate()
};
var _wrappedList = taskList.create(_attrs, _popped.children);
stack[stack.length - 1].children.push(_wrappedList);
}
// If the stack top is at the same depth as the target, decide whether
// to merge into the existing entry or close it and start a new one.
// Unselected items from different original parent taskLists get
// separate wrappers (preserving original nesting). Selected (moved)
// items always merge with whatever is already at the target depth,
// since they're being placed into a new structural context.
// However, if the current entry already contains selected (moved) items,
// we do NOT split — the unselected sibling should join the same wrapper
// because the selected item established the new structural context.
if (stack.length > 1 && stack[stack.length - 1].depth === targetDepth && !item.isSelected) {
var top = stack[stack.length - 1];
if (top.sourceParentAttrs !== item.parentListAttrs && !top.hasSelectedItems) {
var _popped2$listAttrs;
var _popped2 = stack.pop();
if (!_popped2) {
break;
}
var _attrs2 = (_popped2$listAttrs = _popped2.listAttrs) !== null && _popped2$listAttrs !== void 0 ? _popped2$listAttrs : {
localId: _adfSchema.uuid.generate()
};
var _wrappedList2 = taskList.create(_attrs2, _popped2.children);
stack[stack.length - 1].children.push(_wrappedList2);
}
}
// Push new stack entries to reach the target depth.
// Skip intermediate entries at depth 0 — depth-0 items belong
// directly in the root entry (depth -1), so we only need
// intermediates for depths > 1.
while (stack[stack.length - 1].depth < targetDepth - 1) {
var nextDepth = Math.max(stack[stack.length - 1].depth + 1, 1);
stack.push({
depth: nextDepth,
children: [],
hasSelectedItems: false,
listAttrs: item.isSelected ? null : item.parentListAttrs,
sourceParentAttrs: item.parentListAttrs
});
}
// Add the item at the target depth
if (targetDepth === 0) {
// Depth 0 items go directly into the root taskList
stack[0].children.push(item.node);
if (item.isSelected) {
stack[0].hasSelectedItems = true;
}
} else {
// Ensure there's a stack entry at depth targetDepth to hold this item
if (stack[stack.length - 1].depth < targetDepth) {
stack.push({
depth: targetDepth,
children: [],
hasSelectedItems: false,
listAttrs: item.isSelected ? null : item.parentListAttrs,
sourceParentAttrs: item.parentListAttrs
});
}
stack[stack.length - 1].children.push(item.node);
if (item.isSelected) {
stack[stack.length - 1].hasSelectedItems = true;
}
}
}
// Close remaining stack entries
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
while (stack.length > 1) {
var _popped$listAttrs;
var popped = stack.pop();
if (!popped) {
break;
}
var attrs = (_popped$listAttrs = popped.listAttrs) !== null && _popped$listAttrs !== void 0 ? _popped$listAttrs : {
localId: _adfSchema.uuid.generate()
};
var wrappedList = taskList.create(attrs, popped.children);
stack[stack.length - 1].children.push(wrappedList);
}
// The root entry's children form the root taskList.
// Preserve the root's original attrs when available.
var rootChildren = stack[0].children;
var rootAttrs = (_stack$0$listAttrs = stack[0].listAttrs) !== null && _stack$0$listAttrs !== void 0 ? _stack$0$listAttrs : {
localId: _adfSchema.uuid.generate()
};
var rootList = taskList.create(rootAttrs, rootChildren);
// Compute contentStartOffsets by walking the rebuilt tree.
// Each taskItem's content starts at (pos_within_root + 1) for the taskItem opening tag.
// We add +1 for the root taskList's opening tag.
var isTaskItemType = function isTaskItemType(node) {
var _schema$nodes = schema.nodes,
taskItem = _schema$nodes.taskItem,
blockTaskItem = _schema$nodes.blockTaskItem;
return node.type === taskItem || blockTaskItem != null && node.type === blockTaskItem;
};
var itemIdx = 0;
rootList.descendants(function (node, pos) {
if (isTaskItemType(node) && itemIdx < items.length) {
// pos is relative to rootList content start;
// +1 for rootList's opening tag, +1 for taskItem's opening tag
contentStartOffsets[itemIdx] = 1 + pos + 1;
itemIdx++;
}
return true;
});
return {
node: rootList,
contentStartOffsets: contentStartOffsets
};
}