@atlaskit/editor-wikimarkup-transformer
Version:
Wiki markup transformer for JIRA and Confluence
325 lines (309 loc) • 12 kB
JavaScript
"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;
}
}]);
}();