UNPKG

@atlaskit/editor-plugin-tasks-and-decisions

Version:

Tasks and decisions plugin for @atlaskit/editor-core

221 lines (210 loc) 9.67 kB
"use strict"; 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 }; }