UNPKG

@atlaskit/editor-wikimarkup-transformer

Version:

Wiki markup transformer for JIRA and Confluence

325 lines (309 loc) 12 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ListBuilder = void 0; exports.getType = getType; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 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; } var supportedContentType = ['paragraph', 'orderedList', 'bulletList', 'mediaSingle', 'codeBlock']; /** * Return the type of a list from the bullets */ function getType(bullets) { // Ignored via go/ees005 // eslint-disable-next-line require-unicode-regexp return /#$/.test(bullets) ? 'orderedList' : 'bulletList'; } var ListBuilder = exports.ListBuilder = /*#__PURE__*/function () { function ListBuilder(schema, bullets) { var _this = this; (0, _classCallCheck2.default)(this, ListBuilder); /** * Build prosemirror bulletList or orderedList node * @param {List} list * @returns {PMNode} */ (0, _defineProperty2.default)(this, "parseList", function (list) { var listNode = _this.schema.nodes[list.type]; var output = []; var listItemsBuffer = []; for (var i = 0; i < list.children.length; i++) { var parsedContent = _this.parseListItem(list.children[i]); for (var j = 0; j < parsedContent.length; j++) { var parsedNode = parsedContent[j]; if (parsedNode.type.name === 'listItem') { listItemsBuffer.push(parsedNode); continue; } /** * If the node is not a listItem, then we need to * wrap exisintg list and break out */ if (listItemsBuffer.length) { var _list = listNode.createChecked({}, listItemsBuffer); output.push(_list); } output.push(parsedNode); // This is the break out node listItemsBuffer = []; } } if (listItemsBuffer.length) { var _list2 = listNode.createChecked({}, listItemsBuffer); output.push(_list2); } return output; }); /** * Build prosemirror listItem node * This function would possibly return non listItem nodes * which we need to break out later * @param {ListItem} item */ (0, _defineProperty2.default)(this, "parseListItem", function (item) { var _item$content; var output = []; if (!item.content) { item.content = []; } // Parse nested list var parsedChildren = item.children.reduce(function (result, list) { var parsedList = _this.parseList(list); result.push.apply(result, (0, _toConsumableArray2.default)(parsedList)); return result; }, []); // Append children to the content (_item$content = item.content).push.apply(_item$content, (0, _toConsumableArray2.default)(parsedChildren)); var contentBuffer = []; for (var i = 0; i < item.content.length; i++) { var pmNode = item.content[i]; /** * Skip empty paragraph */ if (pmNode.type.name === 'paragraph' && pmNode.childCount === 0) { continue; } /* Skip Empty spaces after rule */ if (_this.isParagraphEmptyTextNode(pmNode)) { continue; } if (supportedContentType.indexOf(pmNode.type.name) === -1) { var listItem = _this.createListItem(contentBuffer, _this.schema); output.push(listItem); output.push(pmNode); contentBuffer = []; continue; } contentBuffer.push(pmNode); } if (contentBuffer.length) { var _listItem = _this.createListItem(contentBuffer, _this.schema); output.push(_listItem); } return output; }); this.schema = schema; this.root = { children: [], type: getType(bullets) }; this.lastDepth = 1; this.lastList = this.root; } /** * Return the type of the base list * @returns {ListType} */ return (0, _createClass2.default)(ListBuilder, [{ key: "type", get: function get() { return this.root.type; } /** * Add a list item to the builder * @param {AddArgs[]} items */ }, { key: "add", value: function add(items) { var _iterator = _createForOfIteratorHelper(items), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var item = _step.value; var style = item.style, content = item.content; // If there's no style, add to previous list item as multiline if (style === null) { this.appendToLastItem(content); continue; } var depth = style.length; var type = getType(style); if (depth > this.lastDepth) { // Add children starting from last node this.createNest(depth - this.lastDepth, type); this.lastDepth = depth; this.lastList = this.addListItem(type, content); } else if (depth === this.lastDepth) { // Add list item to current node this.lastList = this.addListItem(type, content); } else { // Find node at depth and add list item this.lastList = this.findAncestor(this.lastDepth - depth); this.lastDepth = depth; this.lastList = this.addListItem(type, content); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } /** * Compile a prosemirror node from the root list * @returns {PMNode[]} */ }, { key: "buildPMNode", value: function buildPMNode() { return this.parseList(this.root); } }, { key: "isParagraphEmptyTextNode", value: /* Check if all paragraph's children nodes are text and empty */ function isParagraphEmptyTextNode(node) { if (node.type.name !== 'paragraph' || !node.childCount) { return false; } for (var i = 0; i < node.childCount; i++) { var n = node.content.child(i); if (n.type.name !== 'text') { // Paragraph contains non-text node, so not empty return false; } else if (n.textContent.trim() !== '') { return false; } } return true; } }, { key: "createListItem", value: function createListItem(content, schema) { if (content.length === 0 || ['paragraph', 'mediaSingle'].indexOf(content[0].type.name) === -1) { // If the first element is a list node, try to create a wrapper listItem // (list as first child, no paragraph) for flexible list indentation. // If the schema doesn't support this variant, fall back to prepending // an empty paragraph. var listTypes = ['bulletList', 'orderedList', 'taskList']; if (content.length > 0 && listTypes.indexOf(content[0].type.name) !== -1) { try { return schema.nodes.listItem.createChecked({}, content); } catch (_unused) { // Schema doesn't support list as first child of listItem, // fall back to prepending an empty paragraph } } // If the content is empty or the first element is not paragraph or mediaSingle, // this is likely a nested list where the top-level list item has no text content. // For example: *# item 1 // In this case we create an empty paragraph for the top level listNode. content.unshift(this.schema.nodes.paragraph.createChecked()); } return schema.nodes.listItem.createChecked({}, content); } /** * Add an item at the same level as the current list item * @param {ListType} type * @param {PMNode} content * @returns {PMNode} */ }, { key: "addListItem", value: function addListItem(type, content) { var list = this.lastList; // If the list is a different type, create a new list and add it to the parent node if (list.type !== type) { var parent = list.parent; var newList = { children: [], type: type, parent: parent }; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion parent.children.push(newList); this.lastList = list = newList; } var listItem = { content: content, parent: list, children: [] }; list.children = [].concat((0, _toConsumableArray2.default)(list.children), [listItem]); return list; } /** * Append the past content to the last accessed list node (multiline entries) * @param {PMNode[]} content */ }, { key: "appendToLastItem", value: function appendToLastItem(content) { var _ref; var children = this.lastList.children; var lastItem = children[children.length - 1]; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (_ref = lastItem.content).push.apply(_ref, (0, _toConsumableArray2.default)(content)); } /** * Created a nested list structure of N depth under the current node * @param {number} depth * @param {ListType} type */ }, { key: "createNest", value: function createNest(depth, type) { while (depth-- > 0) { if (this.lastList.children.length === 0) { var listItem = { parent: this.lastList, children: [] }; this.lastList.children = [listItem]; } var nextItem = this.lastList.children[this.lastList.children.length - 1]; nextItem.children = [{ children: [], parent: nextItem, type: type }]; this.lastList = nextItem.children[0]; } } /** * Find the Nth list ancestor of the current list * @param {number} depth */ }, { key: "findAncestor", value: function findAncestor(depth) { var list = this.lastList; while (depth-- > 0 && list.parent) { var listItem = list.parent; if (listItem && listItem.parent) { list = listItem.parent; } } return list; } }]); }();