UNPKG

@atlaskit/editor-core

Version:

A package contains Atlassian editor core functionality

427 lines • 16.4 kB
"use strict"; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); var prosemirror_1 = require("../prosemirror"); var baseCommand = require("../prosemirror/prosemirror-commands"); var baseListCommand = require("../prosemirror/prosemirror-schema-list"); __export(require("../prosemirror/prosemirror-commands")); var blockTypes = require("../plugins/block-type/types"); var transform_to_code_block_1 = require("../plugins/block-type/transform-to-code-block"); var utils_1 = require("../utils"); var plugin_key_1 = require("../plugins/hyperlink/plugin-key"); function toggleBlockType(view, name) { var nodes = view.state.schema.nodes; switch (name) { case blockTypes.NORMAL_TEXT.name: if (nodes.paragraph) { return setNormalText()(view.state, view.dispatch); } break; case blockTypes.HEADING_1.name: if (nodes.heading) { return toggleHeading(1)(view.state, view.dispatch); } break; case blockTypes.HEADING_2.name: if (nodes.heading) { return toggleHeading(2)(view.state, view.dispatch); } break; case blockTypes.HEADING_3.name: if (nodes.heading) { return toggleHeading(3)(view.state, view.dispatch); } break; case blockTypes.HEADING_4.name: if (nodes.heading) { return toggleHeading(4)(view.state, view.dispatch); } break; case blockTypes.HEADING_5.name: if (nodes.heading) { return toggleHeading(5)(view.state, view.dispatch); } break; } return false; } exports.toggleBlockType = toggleBlockType; function setNormalText() { return function (state, dispatch) { var tr = state.tr, _a = state.selection, $from = _a.$from, $to = _a.$to, schema = state.schema; dispatch(tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph)); return true; }; } exports.setNormalText = setNormalText; function toggleHeading(level) { return function (state, dispatch) { var tr = state.tr, _a = state.selection, $from = _a.$from, $to = _a.$to, schema = state.schema; var currentBlock = $from.parent; if (currentBlock.type !== schema.nodes.heading || currentBlock.attrs['level'] !== level) { dispatch(tr.setBlockType($from.pos, $to.pos, schema.nodes.heading, { level: level })); } else { dispatch(tr.setBlockType($from.pos, $to.pos, schema.nodes.paragraph)); } return true; }; } exports.toggleHeading = toggleHeading; /** * Sometimes a selection in the editor can be slightly offset, for example: * it's possible for a selection to start or end at an empty node at the very end of * a line. This isn't obvious by looking at the editor and it's likely not what the * user intended - so we need to adjust the seletion a bit in scenarios like that. */ function adjustSelectionInList(doc, selection) { var $from = selection.$from, $to = selection.$to; var isSameLine = $from.pos === $to.pos; if (isSameLine) { $from = doc.resolve($from.start($from.depth)); $to = doc.resolve($from.end($from.depth)); } var startPos = $from.pos; var endPos = $to.pos; if (isSameLine && startPos === doc.nodeSize - 3) { return selection; } // Selection started at the very beginning of a line and therefor points to the previous line. if ($from.nodeBefore && !isSameLine) { startPos++; var node = doc.nodeAt(startPos); while (!node || (node && !node.isText)) { startPos++; node = doc.nodeAt(startPos); } } if (endPos === startPos) { return new prosemirror_1.TextSelection(doc.resolve(startPos)); } return new prosemirror_1.TextSelection(doc.resolve(startPos), doc.resolve(endPos)); } exports.adjustSelectionInList = adjustSelectionInList; function preventDefault() { return function (state, dispatch) { return true; }; } exports.preventDefault = preventDefault; function toggleList(listType) { return function (state, dispatch, view) { dispatch(state.tr.setSelection(adjustSelectionInList(state.doc, state.selection))); state = view.state; var _a = state.selection, $from = _a.$from, $to = _a.$to; var parent = $from.node(-2); var grandgrandParent = $from.node(-3); var isRangeOfSingleType = utils_1.isRangeOfType(state.doc, $from, $to, state.schema.nodes[listType]); if ((parent && parent.type === state.schema.nodes[listType] || grandgrandParent && grandgrandParent.type === state.schema.nodes[listType]) && isRangeOfSingleType) { // Untoggles list return liftListItems()(state, dispatch); } else { // Wraps selection in list and converts list type e.g. bullet_list -> ordered_list if needed if (!isRangeOfSingleType) { liftListItems()(state, dispatch); state = view.state; } return wrapInList(state.schema.nodes[listType])(state, dispatch); } }; } exports.toggleList = toggleList; function toggleBulletList() { return toggleList('bulletList'); } exports.toggleBulletList = toggleBulletList; function toggleOrderedList() { return toggleList('orderedList'); } exports.toggleOrderedList = toggleOrderedList; function wrapInList(nodeType) { return baseCommand.autoJoin(baseListCommand.wrapInList(nodeType), function (before, after) { return before.type === after.type && before.type === nodeType; }); } exports.wrapInList = wrapInList; function liftListItems() { return function (state, dispatch) { var tr = state.tr; var _a = state.selection, $from = _a.$from, $to = _a.$to; tr.doc.nodesBetween($from.pos, $to.pos, function (node, pos) { // Following condition will ensure that block types paragraph, heading, codeBlock, blockquote, panel are lifted. // isTextblock is true for paragraph, heading, codeBlock. if (node.isTextblock || node.type.name === 'blockquote' || node.type.name === 'panel') { var sel = new prosemirror_1.NodeSelection(tr.doc.resolve(tr.mapping.map(pos))); var range = sel.$from.blockRange(sel.$to); if (!range || sel.$from.parent.type !== state.schema.nodes.listItem) { return false; } var target = range && prosemirror_1.liftTarget(range); if (target === undefined) { return false; } tr.lift(range, target); } }); dispatch(tr); return true; }; } exports.liftListItems = liftListItems; function insertBlockType(view, name) { var nodes = view.state.schema.nodes; switch (name) { case blockTypes.BLOCK_QUOTE.name: if (nodes.paragraph && nodes.blockquote) { return wrapSelectionIn(nodes.blockquote)(view.state, view.dispatch); } break; case blockTypes.CODE_BLOCK.name: if (nodes.codeBlock) { return insertCodeBlock()(view.state, view.dispatch); } break; case blockTypes.PANEL.name: if (nodes.panel && nodes.paragraph) { return wrapSelectionIn(nodes.panel)(view.state, view.dispatch); } break; } return false; } exports.insertBlockType = insertBlockType; /** * Function will add wraping node. * 1. If currently selected blocks can be wrapped in the warpper type it will wrap them. * 2. If current block can not be wrapped inside wrapping block it will create a new block below selection, * and set selection on it. */ function wrapSelectionIn(type) { return function (state, dispatch) { var tr = state.tr; var _a = state.selection, $from = _a.$from, $to = _a.$to; var paragraph = state.schema.nodes.paragraph; var range = $from.blockRange($to); var wrapping = range && prosemirror_1.findWrapping(range, type); if (range && wrapping) { tr.wrap(range, wrapping).scrollIntoView(); } else { tr.replaceRangeWith($to.pos, $to.pos, type.createAndFill({}, paragraph.create())); tr.setSelection(prosemirror_1.Selection.near(tr.doc.resolve(state.selection.to + 1))); } dispatch(tr); return true; }; } /** * Function will insert code block at current selection if block is empty or below current selection and set focus on it. */ function insertCodeBlock() { return function (state, dispatch) { var tr = state.tr; var $to = state.selection.$to; var codeBlock = state.schema.nodes.codeBlock; var moveSel = $to.node($to.depth).textContent ? 1 : 0; tr.replaceRangeWith($to.pos, $to.pos, codeBlock.createAndFill()); tr.setSelection(prosemirror_1.Selection.near(tr.doc.resolve(state.selection.to + moveSel))); dispatch(tr); return true; }; } exports.insertCodeBlock = insertCodeBlock; function createCodeBlockFromFenceFormat() { return function (state, dispatch) { var codeBlock = state.schema.nodes.codeBlock; var $from = state.selection.$from; var parentBlock = $from.parent; if (!parentBlock.isTextblock || parentBlock.type === codeBlock) { return false; } var startPos = $from.start($from.depth); var textOnly = true; state.doc.nodesBetween(startPos, $from.pos, function (node) { if (node.childCount === 0 && !node.isText && !node.isTextblock) { textOnly = false; } }); if (!textOnly) { return false; } if (!state.schema.nodes.codeBlock) { return false; } var fencePart = parentBlock.textContent.slice(0, $from.pos - startPos).trim(); var matches = /^```(`+)?([^\s]+)?/.exec(fencePart); if (matches && transform_to_code_block_1.isConvertableToCodeBlock(state)) { dispatch(transform_to_code_block_1.transformToCodeBlockAction(state, { language: matches[2] }).delete(startPos, $from.pos)); return true; } return false; }; } exports.createCodeBlockFromFenceFormat = createCodeBlockFromFenceFormat; function showLinkPanel() { return function (state, dispatch, view) { var pluginState = plugin_key_1.default.getState(state); pluginState.showLinkPanel(view); return true; }; } exports.showLinkPanel = showLinkPanel; function insertNewLine() { return function (state, dispatch) { var $from = state.selection.$from; var node = $from.parent; var hardBreak = state.schema.nodes.hardBreak; if (hardBreak) { var hardBreakNode = hardBreak.create(); if (node.type.validContent(prosemirror_1.Fragment.from(hardBreakNode))) { dispatch(state.tr.replaceSelectionWith(hardBreakNode)); return true; } } dispatch(state.tr.insertText('\n')); return true; }; } exports.insertNewLine = insertNewLine; function insertRule() { return function (state, dispatch) { var to = state.selection.to; var rule = state.schema.nodes.rule; if (rule) { var ruleNode = rule.create(); dispatch(state.tr.insert(to + 1, ruleNode)); return true; } return false; }; } exports.insertRule = insertRule; function indentList() { return function (state, dispatch) { var listItem = state.schema.nodes.listItem; var $from = state.selection.$from; if ($from.node(-1).type === listItem) { return baseListCommand.sinkListItem(listItem)(state, dispatch); } return false; }; } exports.indentList = indentList; function outdentList() { return function (state, dispatch) { var listItem = state.schema.nodes.listItem; var $from = state.selection.$from; if ($from.node(-1).type === listItem) { return baseListCommand.liftListItem(listItem)(state, dispatch); } return false; }; } exports.outdentList = outdentList; function createNewParagraphAbove(view) { return function (state, dispatch) { var append = false; if (!utils_1.canMoveUp(state) && canCreateParagraphNear(state)) { createParagraphNear(view, append); return true; } return false; }; } exports.createNewParagraphAbove = createNewParagraphAbove; function createNewParagraphBelow(view) { return function (state, dispatch) { var append = true; if (!utils_1.canMoveDown(state) && canCreateParagraphNear(state)) { createParagraphNear(view, append); return true; } return false; }; } exports.createNewParagraphBelow = createNewParagraphBelow; function canCreateParagraphNear(state) { var $from = state.selection.$from; var node = $from.node($from.depth); var insideCodeBlock = !!node && node.type === state.schema.nodes.codeBlock; var isNodeSelection = state.selection instanceof prosemirror_1.NodeSelection; return $from.depth > 1 || isNodeSelection || insideCodeBlock; } function createParagraphNear(view, append) { if (append === void 0) { append = true; } var state = view.state, dispatch = view.dispatch; var paragraph = state.schema.nodes.paragraph; if (!paragraph) { return; } var insertPos; if (state.selection instanceof prosemirror_1.TextSelection) { if (topLevelNodeIsEmptyTextBlock(state)) { return; } insertPos = getInsertPosFromTextBlock(state, append); } else { insertPos = getInsertPosFromNonTextBlock(state, append); } dispatch(state.tr.insert(insertPos, paragraph.create())); utils_1.setTextSelection(view, insertPos + 1); } exports.createParagraphNear = createParagraphNear; function getInsertPosFromTextBlock(state, append) { var _a = state.selection, $from = _a.$from, $to = _a.$to; var pos; var nodeType = $to.node($to.depth - 1).type; if (!append) { pos = $from.start($from.depth) - 1; pos = $from.depth > 1 ? pos - 1 : pos; // Same theory as comment below. if (nodeType === state.schema.nodes.listItem) { pos = pos - 1; } if (nodeType === state.schema.nodes.tableCell || nodeType === state.schema.nodes.tableHeader) { pos = pos - 2; } } else { pos = $to.end($to.depth) + 1; pos = $to.depth > 1 ? pos + 1 : pos; // List is a special case. Because from user point of view, the whole list is a unit, // which has 3 level deep (ul, li, p), all the other block types has maxium two levels as a unit. // eg. block type (bq, p/other), code block (cb) and panel (panel, p/other). if (nodeType === state.schema.nodes.listItem) { pos = pos + 1; } // table has 4 level depth if (nodeType === state.schema.nodes.tableCell || nodeType === state.schema.nodes.tableHeader) { pos = pos + 2; } } return pos; } function getInsertPosFromNonTextBlock(state, append) { var _a = state.selection, $from = _a.$from, $to = _a.$to; var pos; if (!append) { // The start position is different with text block because it starts from 0 pos = $from.start($from.depth); // The depth is different with text block because it starts from 0 pos = $from.depth > 0 ? pos - 1 : pos; } else { pos = $to.end($to.depth); pos = $to.depth > 0 ? pos + 1 : pos; } return pos; } function topLevelNodeIsEmptyTextBlock(state) { var topLevelNode = state.selection.$from.node(1); return topLevelNode.isTextblock && topLevelNode.type !== state.schema.nodes.codeBlock && topLevelNode.nodeSize === 2; } //# sourceMappingURL=index.js.map