@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
427 lines • 16.4 kB
JavaScript
"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