UNPKG

@atlaskit/editor-plugin-list

Version:

List plugin for @atlaskit/editor-core

299 lines (287 loc) 12.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.listBackspace = exports.calcJoinListScenario = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _analytics = require("@atlaskit/editor-common/analytics"); var _lists = require("@atlaskit/editor-common/lists"); var _utils = require("@atlaskit/editor-common/utils"); var _utils2 = require("@atlaskit/editor-prosemirror/utils"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _selection = require("../utils/selection"); //Cases below refer to the cases found in this document: https://product-fabric.atlassian.net/wiki/spaces/E/pages/1146954996/List+Backspace+and+Delete+Behaviour //Case for two adjacent list items of the same indentation var listBackspaceCase2 = function listBackspaceCase2(tr, dispatch, $prev, $head) { /* CASE 2 * Initial Structure: * * List A { * ListItem B { * ...Children C * Paragraph D { text1 |textInsertPos| } //Cant have children since that would be Case 4 * |$prev||childrenGInsertPos| } * ListItem E { * Paragraph F { |$head| text2 } * ...Children G * } * } * * Converts to: * * List A { * ListItem B { * ...Children C * Paragraph C { text1text2 } * ...Children G * } * } * */ var listItemE = $head.node(-1); //Head is inside listItem E so it must have a first and last child var fontSize = tr.doc.type.schema.marks.fontSize; if (!listItemE.firstChild) { return false; } var beforeListItemE = $head.before(-1); var afterListItemE = $head.after(-1); var textInsertPos = $prev.pos - 1; //Paragraph D must be directly behind $prev otherwise it would be case 4 var childrenGInsertPos = $prev.pos; var textContent = $head.parent.content; var childrenGContent = listItemE.content.cut(listItemE.firstChild.nodeSize); (0, _utils.insertContentDeleteRange)(tr, function (tr) { return tr.doc.resolve(textInsertPos); }, [[textContent, textInsertPos], [childrenGContent, childrenGInsertPos]], [[beforeListItemE, afterListItemE]]); if (fontSize && (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true)) { var targetParagraphFontSizeAttrs = (0, _lists.getBlockMarkAttrs)($prev.parent.lastChild, fontSize); (0, _lists.reconcileBlockMarkForParagraphAtPos)(tr, tr.mapping.map(textInsertPos), fontSize, targetParagraphFontSizeAttrs); } if (dispatch) { dispatch(tr); } return true; }; //Case for two adjacent list items with the first being of lower indentation var listBackspaceCase3 = function listBackspaceCase3(tr, dispatch, $prev, $head) { /* CASE 3 * Initial Structure: * * List A { * ListItem B { * ...Children C * Paragraph D { text1 |$prev||textInsertPos| } |childrenHInsertPos| * List E { |childrenJInsertPos| * ListItem F { * Paragraph G { |$head| text2 } * ...Children H * List? I { * ...Children J * } * } * ...Children K * } * } * } * * Converts to: * * List A { * ListItem B { * ...Children C * Paragraph D { text1text2 } * ...Children H * List E { * ...Children J * ...Children K * } * } * } * */ var listE = $head.node(-2); var listItemF = $head.node(-1); //Head is inside listItem F so it must have a first and last child var fontSize = tr.doc.type.schema.marks.fontSize; if (!listItemF.firstChild || !listItemF.lastChild) { return false; } var beforeListE = $head.before(-2); var beforeListItemF = $head.before(-1); var afterParagraphD = $prev.after(); var afterListE = $head.after(-2); var afterListItemF = $head.after(-1); var startListE = $head.start(-2); var containsChildrenJ = (0, _utils.isListNode)(listItemF.lastChild); var shouldRemoveListE = listE.childCount === 1 && !containsChildrenJ; //Assures no Children J and K var textInsertPos = $prev.pos; var childrenHInsertPos = afterParagraphD; var childrenJInsertPos = startListE; var textContent = $head.parent.content; var childrenHContent = containsChildrenJ ? listItemF.content.cut(listItemF.firstChild.nodeSize, listItemF.nodeSize - listItemF.lastChild.nodeSize - 2) : listItemF.content.cut(listItemF.firstChild.nodeSize); //If Children J doesn't exist then Children H will include the last node var childrenJContent = listItemF.lastChild.content; //Will be invalid if there are no Children J but it will be unused (0, _utils.insertContentDeleteRange)(tr, function (tr) { return tr.doc.resolve(textInsertPos); }, containsChildrenJ ? [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos], [childrenJContent, childrenJInsertPos]] : [[textContent, textInsertPos], [childrenHContent, childrenHInsertPos]], [shouldRemoveListE ? [beforeListE, afterListE] : [beforeListItemF, afterListItemF]]); if (fontSize && (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true)) { var targetParagraphFontSizeAttrs = (0, _lists.getBlockMarkAttrs)($prev.parent, fontSize); (0, _lists.reconcileBlockMarkForParagraphAtPos)(tr, tr.mapping.map(textInsertPos), fontSize, targetParagraphFontSizeAttrs); } if (dispatch) { dispatch(tr); } return true; }; //Case for two adjacent list items with the first being of greater indentation var listBackspaceCase4 = function listBackspaceCase4(tr, dispatch, $prev, $head, $last) { /* CASE 4 * Initial Structure: * * List A { * ListItem B { * Paragraph C { text1 } * ...Children D * List E { * ... * List F { //May be multiple levels of lists * ...Children G * ListItem H { //Last node of the block * ...Children I * Paragraph J { text2 |$last||textInsertPos| } |childrenMInsertPos| //Cant have children since this ListItem is the last of the block * } * } * ... * |childrenOInsertPosition| } * |$prev| } * ListItem K { * Paragraph L { |$head| text3 } * ...Children M * List? N { * ...Children O * } * } * } * * Converts to: * * List A { * ListItem B { * Paragraph C { text1 } * ...Children D * List E { * ... * List F { * ...Children G * ListItem H { * ...Children I * Paragraph J { text2text3 } * ...Children M * } * } * ... * ...Children O * } * } * } * */ if (!$last) { //Exit if an invalid last was given as a parameter return false; } var listItemK = $head.node(-1); //Head is inside listItem K so it must have a first and last child var fontSize = tr.doc.type.schema.marks.fontSize; if (!listItemK.firstChild || !listItemK.lastChild) { return false; } var paragraphL = $head.parent; var beforeListItemK = $head.before(-1); var afterParagraphJ = $last.after(); var afterListItemK = $head.after(-1); var containsChildrenO = (0, _utils.isListNode)(listItemK.lastChild); var textInsertPos = $last.pos; var childrenMInsertPos = afterParagraphJ; var childrenOInsertPos = $prev.pos - 1; //Last item of listItem B must be a list therefore we can simply decrement $prev to get there var textContent = paragraphL.content; var childrenMContent = containsChildrenO ? listItemK.content.cut(listItemK.firstChild.nodeSize, listItemK.nodeSize - listItemK.lastChild.nodeSize - 2) : listItemK.content.cut(listItemK.firstChild.nodeSize); var childrenOContent = listItemK.lastChild.content; (0, _utils.insertContentDeleteRange)(tr, function (tr) { return tr.doc.resolve(textInsertPos); }, containsChildrenO ? [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos], [childrenOContent, childrenOInsertPos]] : [[textContent, textInsertPos], [childrenMContent, childrenMInsertPos]], [[beforeListItemK, afterListItemK]]); if (fontSize && (0, _expValEquals.expValEquals)('platform_editor_small_font_size', 'isEnabled', true)) { var targetParagraphFontSizeAttrs = (0, _lists.getBlockMarkAttrs)($last.parent, fontSize); (0, _lists.reconcileBlockMarkForParagraphAtPos)(tr, tr.mapping.map(textInsertPos), fontSize, targetParagraphFontSizeAttrs); } if (dispatch) { dispatch(tr); } return true; }; var BACKSPACE_COMMANDS = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _analytics.LIST_TEXT_SCENARIOS.JOIN_SIBLINGS, listBackspaceCase2), _analytics.LIST_TEXT_SCENARIOS.JOIN_DESCENDANT_TO_PARENT, listBackspaceCase3), _analytics.LIST_TEXT_SCENARIOS.JOIN_TO_SIBLING_DESCENDANT, listBackspaceCase4); var calcJoinListScenario = exports.calcJoinListScenario = function calcJoinListScenario(walkNode, $head, tr) { var $prev = walkNode.$pos, prevFoundNode = walkNode.foundNode; var prevInList = (0, _selection.isPosInsideList)($prev); var headInParagraph = (0, _selection.isPosInsideParagraph)($head); var headInFirstChild = $head.index(-1) === 0; var headInList = (0, _selection.isPosInsideList)($head); //Must be at the start of the selection of the first child in the listItem if (!prevFoundNode || !prevInList || !headInParagraph || !headInFirstChild || !headInList) { return false; } var prevInParagraph = (0, _selection.isPosInsideParagraph)($prev); if (prevInParagraph) { return [_analytics.LIST_TEXT_SCENARIOS.JOIN_DESCENDANT_TO_PARENT, null]; } var prevParentLastChildIsList = $prev.parent.lastChild && (0, _utils.isListNode)($prev.parent.lastChild); var prevParentLastChildIsParagraph = (0, _utils.isParagraphNode)($prev.parent.lastChild); // Will search for the possible last node for case 4 (where the list could be indented multiple times) // $last is required to determine whether we are in case 2 or 4 var $last = tr.doc.resolve($prev.pos); var lastFoundNode; do { var _walkNode = (0, _utils.walkPrevNode)($last); $last = _walkNode.$pos; lastFoundNode = _walkNode.foundNode; } while (lastFoundNode && !$last.parent.isTextblock); var lastInParagraph = (0, _selection.isPosInsideParagraph)($last); if (lastFoundNode && prevParentLastChildIsList && lastInParagraph) { return [_analytics.LIST_TEXT_SCENARIOS.JOIN_TO_SIBLING_DESCENDANT, $last]; } else if (prevParentLastChildIsParagraph) { return [_analytics.LIST_TEXT_SCENARIOS.JOIN_SIBLINGS, null]; } return false; }; var listBackspace = exports.listBackspace = function listBackspace(editorAnalyticsAPI) { return function (state, dispatch) { var tr = state.tr, $head = state.selection.$head; var walkNode = (0, _utils.walkPrevNode)($head); if (!(0, _utils.isEmptySelectionAtStart)(state)) { return false; } var scenario = calcJoinListScenario(walkNode, $head, tr); if (!scenario) { return false; } var _state$schema$nodes = state.schema.nodes, bulletList = _state$schema$nodes.bulletList, orderedList = _state$schema$nodes.orderedList; var listParent = (0, _utils2.findParentNodeOfType)([bulletList, orderedList])(tr.selection); var actionSubjectId = _analytics.ACTION_SUBJECT_ID.FORMAT_LIST_BULLET; if (listParent && listParent.node.type === orderedList) { actionSubjectId = _analytics.ACTION_SUBJECT_ID.FORMAT_LIST_NUMBER; } editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent({ action: _analytics.ACTION.LIST_ITEM_JOINED, actionSubject: _analytics.ACTION_SUBJECT.LIST, actionSubjectId: actionSubjectId, eventType: _analytics.EVENT_TYPE.TRACK, attributes: { inputMethod: _analytics.INPUT_METHOD.KEYBOARD, direction: _analytics.DELETE_DIRECTION.BACKWARD, scenario: scenario[0] } })(tr); return BACKSPACE_COMMANDS[scenario[0]](tr, dispatch, walkNode.$pos, $head, scenario[1]); }; };