UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

466 lines (444 loc) 21.5 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _defineProperty from "@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; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { findChildrenByType, findParentNodeOfType, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { mapSlice } from '../utils/slice'; import { getSupportedListTypesSet, isBulletOrOrderedList, isTaskList, convertBlockToInlineContent } from './list-utils'; var getContentSupportChecker = function getContentSupportChecker(targetNodeType) { return function (node) { try { return targetNodeType.validContent(Fragment.from(node)); } catch (_unused) { return false; } }; }; export var createBlockTaskItem = function createBlockTaskItem(_ref) { var attrs = _ref.attrs, content = _ref.content, marks = _ref.marks, schema = _ref.schema; var _schema$nodes = schema.nodes, blockTaskItem = _schema$nodes.blockTaskItem, paragraph = _schema$nodes.paragraph; var newParagraph = paragraph.createChecked(null, content, marks === null || marks === void 0 ? void 0 : marks.filter(function (mark) { return blockTaskItem.allowsMarkType(mark.type); })); return blockTaskItem.create(attrs !== null && attrs !== void 0 ? attrs : null, newParagraph); }; var _transformListRecursively = function transformListRecursively(props, onhandleUnsupportedContent) { var transformedItems = []; var listNode = props.listNode, isSourceBulletOrOrdered = props.isSourceBulletOrOrdered, isTargetBulletOrOrdered = props.isTargetBulletOrOrdered, isSourceTask = props.isSourceTask, isTargetTask = props.isTargetTask, supportedListTypes = props.supportedListTypes, schema = props.schema, targetNodeType = props.targetNodeType; var _schema$nodes2 = schema.nodes, taskList = _schema$nodes2.taskList, listItem = _schema$nodes2.listItem, taskItem = _schema$nodes2.taskItem, paragraph = _schema$nodes2.paragraph, blockTaskItem = _schema$nodes2.blockTaskItem; // gating behind platform_editor_small_font_size to support task lists with font size applied, // but keep this solution general var isBlockTaskEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true); /** * Extracts paragraph children from a blockTaskItem, preserving their marks. */ var extractParagraphsFromBlockTaskItem = function extractParagraphsFromBlockTaskItem(node) { var paragraphs = []; node.forEach(function (child) { if (child.type === paragraph) { paragraphs.push(child); } }); return paragraphs; }; listNode.forEach(function (child) { if (isSourceBulletOrOrdered && isTargetTask) { // Convert bullet/ordered => task if (child.type === listItem) { var inlineContent = []; var nestedTaskLists = []; var blockMarks = []; child.forEach(function (grandChild) { if (supportedListTypes.has(grandChild.type) && grandChild.type !== taskList) { nestedTaskLists.push(_transformListRecursively(_objectSpread(_objectSpread({}, props), {}, { listNode: grandChild }), onhandleUnsupportedContent)); } else if (!getContentSupportChecker(taskItem)(grandChild) && !grandChild.isTextblock) { onhandleUnsupportedContent === null || onhandleUnsupportedContent === void 0 || onhandleUnsupportedContent(grandChild); } else { if (isBlockTaskEnabled && grandChild.type === paragraph && grandChild.marks.length > 0) { blockMarks = grandChild.marks; } inlineContent.push.apply(inlineContent, _toConsumableArray(convertBlockToInlineContent(grandChild, schema))); } }); if (isBlockTaskEnabled && blockMarks.length > 0) { transformedItems.push(createBlockTaskItem({ content: inlineContent, marks: blockMarks, schema: schema })); } else { transformedItems.push(taskItem.create(null, inlineContent.length > 0 ? inlineContent : null)); } transformedItems.push.apply(transformedItems, nestedTaskLists); } } else if (isSourceTask && isTargetBulletOrOrdered) { // Convert task => bullet/ordered if (child.type === taskItem) { var _inlineContent = _toConsumableArray(child.content.content); // Transfer taskItem's block marks to the paragraph. // Use listItem.allowsMarkType since the paragraph will be inside a listItem // (which uses ParagraphWithFontSizeStage0 that allows fontSize). var paragraphMarks = isBlockTaskEnabled && child.marks.length > 0 ? child.marks.filter(function (mark) { return listItem.allowsMarkType(mark.type); }) : undefined; var paragraphNode = paragraph.create(null, _inlineContent.length > 0 ? _inlineContent : null, paragraphMarks); transformedItems.push(listItem.create(null, [paragraphNode])); } else if (isBlockTaskEnabled && child.type === blockTaskItem) { // blockTaskItem wraps content in paragraphs — extract them directly, // preserving their fontSize marks var paragraphs = extractParagraphsFromBlockTaskItem(child); if (paragraphs.length > 0) { transformedItems.push(listItem.create(null, paragraphs)); } } else if (child.type === taskList) { var transformedNestedList = _transformListRecursively(_objectSpread(_objectSpread({}, props), {}, { listNode: child }), onhandleUnsupportedContent); var lastItem = transformedItems[transformedItems.length - 1]; if ((lastItem === null || lastItem === void 0 ? void 0 : lastItem.type) === listItem) { // Attach nested list to previous item var updatedContent = [].concat(_toConsumableArray(lastItem.content.content), [transformedNestedList]); transformedItems[transformedItems.length - 1] = listItem.create(lastItem.attrs, updatedContent); } else { // No previous item, flatten nested items transformedItems.push.apply(transformedItems, _toConsumableArray(transformedNestedList.content.content)); } } } else if (isSourceBulletOrOrdered && isTargetBulletOrOrdered) { if (child.type === listItem) { var convertedNestedLists = []; child.forEach(function (grandChild) { if (supportedListTypes.has(grandChild.type) && grandChild.type !== targetNodeType) { var convertedNode = _transformListRecursively(_objectSpread(_objectSpread({}, props), {}, { listNode: grandChild }), onhandleUnsupportedContent); convertedNestedLists.push(convertedNode); } else { convertedNestedLists.push(grandChild); } }); transformedItems.push(listItem.create(null, convertedNestedLists)); } } }); return targetNodeType.create(null, transformedItems); }; /** * Transform list structure between different list types */ export { _transformListRecursively as transformListRecursively }; export var transformListStructure = function transformListStructure(context) { var tr = context.tr, sourceNode = context.sourceNode, sourcePos = context.sourcePos, targetNodeType = context.targetNodeType; var nodes = tr.doc.type.schema.nodes; var unsupportedContent = []; var onhandleUnsupportedContent = function onhandleUnsupportedContent(content) { unsupportedContent.push(content); }; try { var listNode = { node: sourceNode, pos: sourcePos }; var sourceList = listNode.node, listPos = listNode.pos; // const { taskList, listItem, taskItem, paragraph } = nodes; var isSourceBulletOrOrdered = isBulletOrOrderedList(sourceList.type); var isTargetTask = isTaskList(targetNodeType); var isSourceTask = isTaskList(sourceList.type); var isTargetBulletOrOrdered = isBulletOrOrderedList(targetNodeType); var supportedListTypes = getSupportedListTypesSet(nodes); var newList = _transformListRecursively({ isSourceBulletOrOrdered: isSourceBulletOrOrdered, isSourceTask: isSourceTask, isTargetBulletOrOrdered: isTargetBulletOrOrdered, isTargetTask: isTargetTask, listNode: sourceList, schema: tr.doc.type.schema, supportedListTypes: supportedListTypes, targetNodeType: targetNodeType }, onhandleUnsupportedContent); tr.replaceWith(listPos, listPos + sourceList.nodeSize, [newList].concat(unsupportedContent)); return tr; } catch (_unused2) { return tr; } }; /** * Transform between different list types */ export var transformBetweenListTypes = function transformBetweenListTypes(context) { var tr = context.tr, sourceNode = context.sourceNode, sourcePos = context.sourcePos, targetNodeType = context.targetNodeType; var nodes = tr.doc.type.schema.nodes; var sourceListType = sourceNode.type; var isSourceBulletOrOrdered = isBulletOrOrderedList(sourceListType); var isTargetTask = isTaskList(targetNodeType); var isSourceTask = isTaskList(sourceListType); var isTargetBulletOrOrdered = isBulletOrOrderedList(targetNodeType); // Check if we need structure transformation var needsStructureTransform = isSourceBulletOrOrdered && isTargetTask || isSourceTask && isTargetBulletOrOrdered; try { if (!needsStructureTransform) { // Simple type change for same structure lists (bullet <-> ordered) // Apply to the main list tr.setNodeMarkup(sourcePos, targetNodeType); // Apply to nested lists var listStart = sourcePos; var listEnd = sourcePos + sourceNode.nodeSize; var supportedListTypesSet = getSupportedListTypesSet(nodes); tr.doc.nodesBetween(listStart, listEnd, function (node, pos, parent) { // Only process nested lists (not the root list we already handled) if (supportedListTypesSet.has(node.type) && pos !== sourcePos) { var isNestedList = parent && (supportedListTypesSet.has(parent.type) || parent.type === nodes.listItem); if (isNestedList) { var shouldTransformNode = node.type === sourceListType || isBulletOrOrderedList(node.type) && isTargetBulletOrOrdered; if (shouldTransformNode) { tr.setNodeMarkup(pos, targetNodeType); } } } return true; // Continue traversing }); return tr; } else { return transformListStructure(context); } } catch (_unused3) { return null; } }; /** * Transform selection to task list * Handles the special structure where taskItem contains text directly (no paragraph wrapper) */ export var transformToTaskList = function transformToTaskList(tr, range, targetNodeType, targetAttrs, nodes) { try { var taskItem = nodes.taskItem, paragraph = nodes.paragraph, blockTaskItem = nodes.blockTaskItem; // gating behind platform_editor_small_font_size to support task lists with font size applied, // but keep this solution general var isBlockTaskItemEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true); var listItems = []; // Process each block in the range tr.doc.nodesBetween(range.start, range.end, function (node) { if (node.isBlock) { var inlineContent = _toConsumableArray(node.content.content); if (inlineContent.length > 0) { if (isBlockTaskItemEnabled && node.type === paragraph && node.marks.length > 0) { listItems.push(createBlockTaskItem({ attrs: targetAttrs, content: inlineContent, marks: node.marks, schema: tr.doc.type.schema })); } else { listItems.push(taskItem.create(targetAttrs, inlineContent)); } } } return false; }); if (listItems.length === 0) { return null; } // Create the new task list var newList = targetNodeType.create(targetAttrs, listItems); // Replace the range with the new list tr.replaceWith(range.start, range.end, newList); return tr; } catch (_unused4) { return null; } }; export var transformTaskListToBlockNodes = function transformTaskListToBlockNodes(context) { var tr = context.tr, targetNodeType = context.targetNodeType, targetAttrs = context.targetAttrs, sourceNode = context.sourceNode, sourcePos = context.sourcePos; var selection = tr.selection; var schema = selection.$from.doc.type.schema; var blockTaskItem = schema.nodes.blockTaskItem; // gating behind platform_editor_small_font_size to support task lists with font size applied, // but keep this solution general var isBlockTaskItemEnabled = !!blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true); if (isBlockTaskItemEnabled) { var blockTaskItemsResult = findChildrenByType(sourceNode, blockTaskItem); if (blockTaskItemsResult.length > 0 && targetNodeType === schema.nodes.paragraph) { // blockTaskItem content is (paragraph | extension)+ // Extract paragraph children directly — they may carry block marks (e.g. fontSize) var _targetNodes = []; var _iterator = _createForOfIteratorHelper(blockTaskItemsResult), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var blockItem = _step.value.node; blockItem.forEach(function (child) { if (child.type === schema.nodes.paragraph) { _targetNodes.push(child); } }); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } if (_targetNodes.length === 0) { return null; } var _slice = new Slice(Fragment.fromArray(_targetNodes), 0, 0); var _rangeStart = sourcePos !== null ? sourcePos : selection.from; tr.replaceRange(_rangeStart, _rangeStart + sourceNode.nodeSize, _slice); return tr; } } // Original logic for regular taskItem children var taskItemsResult = findChildrenByType(sourceNode, schema.nodes.taskItem); var taskItems = taskItemsResult.map(function (item) { return item.node; }); var taskItemFragments = taskItems.map(function (taskItem) { return taskItem.content; }); var targetNodes = []; // Convert fragments to headings if target is heading if (targetNodeType === schema.nodes.heading && targetAttrs) { // convert the fragments to headings var targetHeadingLevel = targetAttrs.level; targetNodes = taskItemFragments.map(function (fragment) { return schema.nodes.heading.createChecked({ level: targetHeadingLevel }, fragment.content); }); } // Convert fragments to paragraphs if target is paragraphs if (targetNodeType === schema.nodes.paragraph) { // convert the fragments to paragraphs targetNodes = taskItemFragments.map(function (fragment) { return schema.nodes.paragraph.createChecked({}, fragment.content); }); } // Convert fragments to code block if target is code block if (targetNodeType === schema.nodes.codeBlock) { // convert the fragments to one code block var codeBlockContent = taskItemFragments.map(function (fragment) { return fragment.textBetween(0, fragment.size, '\n'); }).join('\n'); targetNodes = [schema.nodes.codeBlock.createChecked({}, schema.text(codeBlockContent))]; } // Replace the task list node with the new content in the transaction var slice = new Slice(Fragment.fromArray(targetNodes), 0, 0); var rangeStart = sourcePos !== null ? sourcePos : selection.from; tr.replaceRange(rangeStart, rangeStart + sourceNode.nodeSize, slice); return tr; }; export var getFormattedNode = function getFormattedNode(tr) { var selection = tr.selection; var nodes = tr.doc.type.schema.nodes; // gating behind platform_editor_small_font_size to support task lists with font size applied, // but keep this solution general var isBlockTaskItemEnabled = !!nodes.blockTaskItem && expValEquals('platform_editor_small_font_size', 'isEnabled', true); // Find the node to format from the current selection var nodeToFormat; var nodePos = selection.from; // Try to find the current node from selection var selectedNode = findSelectedNodeOfType([nodes.paragraph, nodes.heading, nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.bulletList, nodes.orderedList, nodes.taskList, nodes.layoutSection])(selection); if (selectedNode) { nodeToFormat = selectedNode.node; nodePos = selectedNode.pos; } else { // Try to find parent node (including list parents) var parentNodeTypes = [nodes.blockquote, nodes.panel, nodes.expand, nodes.codeBlock, nodes.listItem, nodes.taskItem, nodes.layoutSection]; if (isBlockTaskItemEnabled) { parentNodeTypes.push(nodes.blockTaskItem); } var parentNode = findParentNodeOfType(parentNodeTypes)(selection); if (parentNode) { nodeToFormat = parentNode.node; nodePos = parentNode.pos; var paragraphOrHeadingNode = findParentNodeOfType([nodes.paragraph, nodes.heading])(selection); // Special case: if we found a listItem/taskItem/blockTaskItem, check if we need the parent list instead if (parentNode.node.type === nodes.listItem || parentNode.node.type === nodes.taskItem || isBlockTaskItemEnabled && parentNode.node.type === nodes.blockTaskItem) { var listParent = findParentNodeOfType([nodes.bulletList, nodes.orderedList, nodes.taskList])(selection); if (listParent) { // For list transformations, we want the list parent, not the listItem nodeToFormat = listParent.node; nodePos = listParent.pos; } } else if (parentNode.node.type !== nodes.blockquote && paragraphOrHeadingNode) { nodeToFormat = paragraphOrHeadingNode.node; nodePos = paragraphOrHeadingNode.pos; } } } if (!nodeToFormat) { nodeToFormat = selection.$from.node(); nodePos = selection.$from.pos; } return { node: nodeToFormat, pos: nodePos }; }; /** * Ensures every `listItem` in a slice starts with a paragraph. * * @param slice - The slice to transform * @param schema - The editor schema, used to create new paragraph nodes * @returns A new slice with the transformation applied */ export var transformSliceEnsureListItemParagraphFirst = function transformSliceEnsureListItemParagraphFirst(slice, schema) { var _schema$nodes3 = schema.nodes, listItem = _schema$nodes3.listItem, paragraph = _schema$nodes3.paragraph; if (!listItem || !paragraph) { return slice; } return mapSlice(slice, function (node) { if (node.type === listItem) { var firstChild = node.firstChild; if (firstChild && firstChild.type !== paragraph) { var emptyParagraph = paragraph.createAndFill(); if (emptyParagraph) { var children = [emptyParagraph]; for (var i = 0; i < node.childCount; i++) { children.push(node.child(i)); } return node.copy(Fragment.from(children)); } } } return node; }); };