This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

View File

@@ -0,0 +1,796 @@
'use strict';
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
Object.defineProperty(exports, '__esModule', {
value: true
});
var prosemirrorTransform = require('prosemirror-transform');
var prosemirrorModel = require('prosemirror-model');
var prosemirrorState = require('prosemirror-state');
var deleteSelection = function deleteSelection(state, dispatch) {
if (state.selection.empty) return false;
if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView());
return true;
};
function atBlockStart(state, view) {
var $cursor = state.selection.$cursor;
if (!$cursor || (view ? !view.endOfTextblock("backward", state) : $cursor.parentOffset > 0)) return null;
return $cursor;
}
var joinBackward = function joinBackward(state, dispatch, view) {
var $cursor = atBlockStart(state, view);
if (!$cursor) return false;
var $cut = findCutBefore($cursor);
if (!$cut) {
var range = $cursor.blockRange(),
target = range && prosemirrorTransform.liftTarget(range);
if (target == null) return false;
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
var before = $cut.nodeBefore;
if (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch)) return true;
if ($cursor.parent.content.size == 0 && (textblockAt(before, "end") || prosemirrorState.NodeSelection.isSelectable(before))) {
var delStep = prosemirrorTransform.replaceStep(state.doc, $cursor.before(), $cursor.after(), prosemirrorModel.Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
var tr = state.tr.step(delStep);
tr.setSelection(textblockAt(before, "end") ? prosemirrorState.Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1) : prosemirrorState.NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
}
}
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch) dispatch(state.tr["delete"]($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
return true;
}
return false;
};
var joinTextblockBackward = function joinTextblockBackward(state, dispatch, view) {
var $cursor = atBlockStart(state, view);
if (!$cursor) return false;
var $cut = findCutBefore($cursor);
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
};
var joinTextblockForward = function joinTextblockForward(state, dispatch, view) {
var $cursor = atBlockEnd(state, view);
if (!$cursor) return false;
var $cut = findCutAfter($cursor);
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
};
function joinTextblocksAround(state, $cut, dispatch) {
var before = $cut.nodeBefore,
beforeText = before,
beforePos = $cut.pos - 1;
for (; !beforeText.isTextblock; beforePos--) {
if (beforeText.type.spec.isolating) return false;
var child = beforeText.lastChild;
if (!child) return false;
beforeText = child;
}
var after = $cut.nodeAfter,
afterText = after,
afterPos = $cut.pos + 1;
for (; !afterText.isTextblock; afterPos++) {
if (afterText.type.spec.isolating) return false;
var _child = afterText.firstChild;
if (!_child) return false;
afterText = _child;
}
var step = prosemirrorTransform.replaceStep(state.doc, beforePos, afterPos, prosemirrorModel.Slice.empty);
if (!step || step.from != beforePos || step instanceof prosemirrorTransform.ReplaceStep && step.slice.size >= afterPos - beforePos) return false;
if (dispatch) {
var tr = state.tr.step(step);
tr.setSelection(prosemirrorState.TextSelection.create(tr.doc, beforePos));
dispatch(tr.scrollIntoView());
}
return true;
}
function textblockAt(node, side) {
var only = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
for (var scan = node; scan; scan = side == "start" ? scan.firstChild : scan.lastChild) {
if (scan.isTextblock) return true;
if (only && scan.childCount != 1) return false;
}
return false;
}
var selectNodeBackward = function selectNodeBackward(state, dispatch, view) {
var _state$selection = state.selection,
$head = _state$selection.$head,
empty = _state$selection.empty,
$cut = $head;
if (!empty) return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0) return false;
$cut = findCutBefore($head);
}
var node = $cut && $cut.nodeBefore;
if (!node || !prosemirrorState.NodeSelection.isSelectable(node)) return false;
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
return true;
};
function findCutBefore($pos) {
if (!$pos.parent.type.spec.isolating) for (var i = $pos.depth - 1; i >= 0; i--) {
if ($pos.index(i) > 0) return $pos.doc.resolve($pos.before(i + 1));
if ($pos.node(i).type.spec.isolating) break;
}
return null;
}
function atBlockEnd(state, view) {
var $cursor = state.selection.$cursor;
if (!$cursor || (view ? !view.endOfTextblock("forward", state) : $cursor.parentOffset < $cursor.parent.content.size)) return null;
return $cursor;
}
var joinForward = function joinForward(state, dispatch, view) {
var $cursor = atBlockEnd(state, view);
if (!$cursor) return false;
var $cut = findCutAfter($cursor);
if (!$cut) return false;
var after = $cut.nodeAfter;
if (deleteBarrier(state, $cut, dispatch)) return true;
if ($cursor.parent.content.size == 0 && (textblockAt(after, "start") || prosemirrorState.NodeSelection.isSelectable(after))) {
var delStep = prosemirrorTransform.replaceStep(state.doc, $cursor.before(), $cursor.after(), prosemirrorModel.Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
var tr = state.tr.step(delStep);
tr.setSelection(textblockAt(after, "start") ? prosemirrorState.Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1) : prosemirrorState.NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
dispatch(tr.scrollIntoView());
}
return true;
}
}
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch) dispatch(state.tr["delete"]($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
return true;
}
return false;
};
var selectNodeForward = function selectNodeForward(state, dispatch, view) {
var _state$selection2 = state.selection,
$head = _state$selection2.$head,
empty = _state$selection2.empty,
$cut = $head;
if (!empty) return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size) return false;
$cut = findCutAfter($head);
}
var node = $cut && $cut.nodeAfter;
if (!node || !prosemirrorState.NodeSelection.isSelectable(node)) return false;
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, $cut.pos)).scrollIntoView());
return true;
};
function findCutAfter($pos) {
if (!$pos.parent.type.spec.isolating) for (var i = $pos.depth - 1; i >= 0; i--) {
var parent = $pos.node(i);
if ($pos.index(i) + 1 < parent.childCount) return $pos.doc.resolve($pos.after(i + 1));
if (parent.type.spec.isolating) break;
}
return null;
}
var joinUp = function joinUp(state, dispatch) {
var sel = state.selection,
nodeSel = sel instanceof prosemirrorState.NodeSelection,
point;
if (nodeSel) {
if (sel.node.isTextblock || !prosemirrorTransform.canJoin(state.doc, sel.from)) return false;
point = sel.from;
} else {
point = prosemirrorTransform.joinPoint(state.doc, sel.from, -1);
if (point == null) return false;
}
if (dispatch) {
var tr = state.tr.join(point);
if (nodeSel) tr.setSelection(prosemirrorState.NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
};
var joinDown = function joinDown(state, dispatch) {
var sel = state.selection,
point;
if (sel instanceof prosemirrorState.NodeSelection) {
if (sel.node.isTextblock || !prosemirrorTransform.canJoin(state.doc, sel.to)) return false;
point = sel.to;
} else {
point = prosemirrorTransform.joinPoint(state.doc, sel.to, 1);
if (point == null) return false;
}
if (dispatch) dispatch(state.tr.join(point).scrollIntoView());
return true;
};
var lift = function lift(state, dispatch) {
var _state$selection3 = state.selection,
$from = _state$selection3.$from,
$to = _state$selection3.$to;
var range = $from.blockRange($to),
target = range && prosemirrorTransform.liftTarget(range);
if (target == null) return false;
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
var newlineInCode = function newlineInCode(state, dispatch) {
var _state$selection4 = state.selection,
$head = _state$selection4.$head,
$anchor = _state$selection4.$anchor;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false;
if (dispatch) dispatch(state.tr.insertText("\n").scrollIntoView());
return true;
};
function defaultBlockAt(match) {
for (var i = 0; i < match.edgeCount; i++) {
var _match$edge = match.edge(i),
type = _match$edge.type;
if (type.isTextblock && !type.hasRequiredAttrs()) return type;
}
return null;
}
var exitCode = function exitCode(state, dispatch) {
var _state$selection5 = state.selection,
$head = _state$selection5.$head,
$anchor = _state$selection5.$anchor;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false;
var above = $head.node(-1),
after = $head.indexAfter(-1),
type = defaultBlockAt(above.contentMatchAt(after));
if (!type || !above.canReplaceWith(after, after, type)) return false;
if (dispatch) {
var pos = $head.after(),
tr = state.tr.replaceWith(pos, pos, type.createAndFill());
tr.setSelection(prosemirrorState.Selection.near(tr.doc.resolve(pos), 1));
dispatch(tr.scrollIntoView());
}
return true;
};
var createParagraphNear = function createParagraphNear(state, dispatch) {
var sel = state.selection,
$from = sel.$from,
$to = sel.$to;
if (sel instanceof prosemirrorState.AllSelection || $from.parent.inlineContent || $to.parent.inlineContent) return false;
var type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()));
if (!type || !type.isTextblock) return false;
if (dispatch) {
var side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos;
var tr = state.tr.insert(side, type.createAndFill());
tr.setSelection(prosemirrorState.TextSelection.create(tr.doc, side + 1));
dispatch(tr.scrollIntoView());
}
return true;
};
var liftEmptyBlock = function liftEmptyBlock(state, dispatch) {
var $cursor = state.selection.$cursor;
if (!$cursor || $cursor.parent.content.size) return false;
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
var before = $cursor.before();
if (prosemirrorTransform.canSplit(state.doc, before)) {
if (dispatch) dispatch(state.tr.split(before).scrollIntoView());
return true;
}
}
var range = $cursor.blockRange(),
target = range && prosemirrorTransform.liftTarget(range);
if (target == null) return false;
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
function splitBlockAs(splitNode) {
return function (state, dispatch) {
var _state$selection6 = state.selection,
$from = _state$selection6.$from,
$to = _state$selection6.$to;
if (state.selection instanceof prosemirrorState.NodeSelection && state.selection.node.isBlock) {
if (!$from.parentOffset || !prosemirrorTransform.canSplit(state.doc, $from.pos)) return false;
if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView());
return true;
}
if (!$from.parent.isBlock) return false;
if (dispatch) {
var atEnd = $to.parentOffset == $to.parent.content.size;
var tr = state.tr;
if (state.selection instanceof prosemirrorState.TextSelection || state.selection instanceof prosemirrorState.AllSelection) tr.deleteSelection();
var deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
var splitType = splitNode && splitNode($to.parent, atEnd);
var types = splitType ? [splitType] : atEnd && deflt ? [{
type: deflt
}] : undefined;
var can = prosemirrorTransform.canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
if (!types && !can && prosemirrorTransform.canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{
type: deflt
}] : undefined)) {
if (deflt) types = [{
type: deflt
}];
can = true;
}
if (can) {
tr.split(tr.mapping.map($from.pos), 1, types);
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
var first = tr.mapping.map($from.before()),
$first = tr.doc.resolve(first);
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt)) tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
}
}
dispatch(tr.scrollIntoView());
}
return true;
};
}
var splitBlock = splitBlockAs();
var splitBlockKeepMarks = function splitBlockKeepMarks(state, dispatch) {
return splitBlock(state, dispatch && function (tr) {
var marks = state.storedMarks || state.selection.$to.parentOffset && state.selection.$from.marks();
if (marks) tr.ensureMarks(marks);
dispatch(tr);
});
};
var selectParentNode = function selectParentNode(state, dispatch) {
var _state$selection7 = state.selection,
$from = _state$selection7.$from,
to = _state$selection7.to,
pos;
var same = $from.sharedDepth(to);
if (same == 0) return false;
pos = $from.before(same);
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, pos)));
return true;
};
var selectAll = function selectAll(state, dispatch) {
if (dispatch) dispatch(state.tr.setSelection(new prosemirrorState.AllSelection(state.doc)));
return true;
};
function joinMaybeClear(state, $pos, dispatch) {
var before = $pos.nodeBefore,
after = $pos.nodeAfter,
index = $pos.index();
if (!before || !after || !before.type.compatibleContent(after.type)) return false;
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
if (dispatch) dispatch(state.tr["delete"]($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
return true;
}
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || prosemirrorTransform.canJoin(state.doc, $pos.pos))) return false;
if (dispatch) dispatch(state.tr.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount)).join($pos.pos).scrollIntoView());
return true;
}
function deleteBarrier(state, $cut, dispatch) {
var before = $cut.nodeBefore,
after = $cut.nodeAfter,
conn,
match;
if (before.type.spec.isolating || after.type.spec.isolating) return false;
if (joinMaybeClear(state, $cut, dispatch)) return true;
var canDelAfter = $cut.parent.canReplace($cut.index(), $cut.index() + 1);
if (canDelAfter && (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && match.matchType(conn[0] || after.type).validEnd) {
if (dispatch) {
var end = $cut.pos + after.nodeSize,
wrap = prosemirrorModel.Fragment.empty;
for (var i = conn.length - 1; i >= 0; i--) {
wrap = prosemirrorModel.Fragment.from(conn[i].create(null, wrap));
}
wrap = prosemirrorModel.Fragment.from(before.copy(wrap));
var tr = state.tr.step(new prosemirrorTransform.ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new prosemirrorModel.Slice(wrap, 1, 0), conn.length, true));
var joinAt = end + 2 * conn.length;
if (prosemirrorTransform.canJoin(tr.doc, joinAt)) tr.join(joinAt);
dispatch(tr.scrollIntoView());
}
return true;
}
var selAfter = prosemirrorState.Selection.findFrom($cut, 1);
var range = selAfter && selAfter.$from.blockRange(selAfter.$to),
target = range && prosemirrorTransform.liftTarget(range);
if (target != null && target >= $cut.depth) {
if (dispatch) dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
var at = before,
_wrap = [];
for (;;) {
_wrap.push(at);
if (at.isTextblock) break;
at = at.lastChild;
}
var afterText = after,
afterDepth = 1;
for (; !afterText.isTextblock; afterText = afterText.firstChild) {
afterDepth++;
}
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
if (dispatch) {
var _end = prosemirrorModel.Fragment.empty;
for (var _i = _wrap.length - 1; _i >= 0; _i--) {
_end = prosemirrorModel.Fragment.from(_wrap[_i].copy(_end));
}
var _tr = state.tr.step(new prosemirrorTransform.ReplaceAroundStep($cut.pos - _wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new prosemirrorModel.Slice(_end, _wrap.length, 0), 0, true));
dispatch(_tr.scrollIntoView());
}
return true;
}
}
return false;
}
function selectTextblockSide(side) {
return function (state, dispatch) {
var sel = state.selection,
$pos = side < 0 ? sel.$from : sel.$to;
var depth = $pos.depth;
while ($pos.node(depth).isInline) {
if (!depth) return false;
depth--;
}
if (!$pos.node(depth).isTextblock) return false;
if (dispatch) dispatch(state.tr.setSelection(prosemirrorState.TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
return true;
};
}
var selectTextblockStart = selectTextblockSide(-1);
var selectTextblockEnd = selectTextblockSide(1);
function wrapIn(nodeType) {
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return function (state, dispatch) {
var _state$selection8 = state.selection,
$from = _state$selection8.$from,
$to = _state$selection8.$to;
var range = $from.blockRange($to),
wrapping = range && prosemirrorTransform.findWrapping(range, nodeType, attrs);
if (!wrapping) return false;
if (dispatch) dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
return true;
};
}
function setBlockType(nodeType) {
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return function (state, dispatch) {
var applicable = false;
for (var i = 0; i < state.selection.ranges.length && !applicable; i++) {
var _state$selection$rang = state.selection.ranges[i],
from = _state$selection$rang.$from.pos,
to = _state$selection$rang.$to.pos;
state.doc.nodesBetween(from, to, function (node, pos) {
if (applicable) return false;
if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return;
if (node.type == nodeType) {
applicable = true;
} else {
var $pos = state.doc.resolve(pos),
index = $pos.index();
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
}
});
}
if (!applicable) return false;
if (dispatch) {
var tr = state.tr;
for (var _i2 = 0; _i2 < state.selection.ranges.length; _i2++) {
var _state$selection$rang2 = state.selection.ranges[_i2],
_from = _state$selection$rang2.$from.pos,
_to = _state$selection$rang2.$to.pos;
tr.setBlockType(_from, _to, nodeType, attrs);
}
dispatch(tr.scrollIntoView());
}
return true;
};
}
function markApplies(doc, ranges, type) {
var _loop = function _loop(i) {
var _ranges$i = ranges[i],
$from = _ranges$i.$from,
$to = _ranges$i.$to;
var can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false;
doc.nodesBetween($from.pos, $to.pos, function (node) {
if (can) return false;
can = node.inlineContent && node.type.allowsMarkType(type);
});
if (can) return {
v: true
};
};
for (var i = 0; i < ranges.length; i++) {
var _ret = _loop(i);
if (_typeof(_ret) === "object") return _ret.v;
}
return false;
}
function toggleMark(markType) {
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return function (state, dispatch) {
var _state$selection9 = state.selection,
empty = _state$selection9.empty,
$cursor = _state$selection9.$cursor,
ranges = _state$selection9.ranges;
if (empty && !$cursor || !markApplies(state.doc, ranges, markType)) return false;
if (dispatch) {
if ($cursor) {
if (markType.isInSet(state.storedMarks || $cursor.marks())) dispatch(state.tr.removeStoredMark(markType));else dispatch(state.tr.addStoredMark(markType.create(attrs)));
} else {
var has = false,
tr = state.tr;
for (var i = 0; !has && i < ranges.length; i++) {
var _ranges$i2 = ranges[i],
$from = _ranges$i2.$from,
$to = _ranges$i2.$to;
has = state.doc.rangeHasMark($from.pos, $to.pos, markType);
}
for (var _i3 = 0; _i3 < ranges.length; _i3++) {
var _ranges$_i = ranges[_i3],
_$from = _ranges$_i.$from,
_$to = _ranges$_i.$to;
if (has) {
tr.removeMark(_$from.pos, _$to.pos, markType);
} else {
var from = _$from.pos,
to = _$to.pos,
start = _$from.nodeAfter,
end = _$to.nodeBefore;
var spaceStart = start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
var spaceEnd = end && end.isText ? /\s*$/.exec(end.text)[0].length : 0;
if (from + spaceStart < to) {
from += spaceStart;
to -= spaceEnd;
}
tr.addMark(from, to, markType.create(attrs));
}
}
dispatch(tr.scrollIntoView());
}
}
return true;
};
}
function wrapDispatchForJoin(dispatch, isJoinable) {
return function (tr) {
if (!tr.isGeneric) return dispatch(tr);
var ranges = [];
for (var i = 0; i < tr.mapping.maps.length; i++) {
var map = tr.mapping.maps[i];
for (var j = 0; j < ranges.length; j++) {
ranges[j] = map.map(ranges[j]);
}
map.forEach(function (_s, _e, from, to) {
return ranges.push(from, to);
});
}
var joinable = [];
for (var _i4 = 0; _i4 < ranges.length; _i4 += 2) {
var from = ranges[_i4],
to = ranges[_i4 + 1];
var $from = tr.doc.resolve(from),
depth = $from.sharedDepth(to),
parent = $from.node(depth);
for (var index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
var after = parent.maybeChild(index);
if (!after) break;
if (index && joinable.indexOf(pos) == -1) {
var before = parent.child(index - 1);
if (before.type == after.type && isJoinable(before, after)) joinable.push(pos);
}
pos += after.nodeSize;
}
}
joinable.sort(function (a, b) {
return a - b;
});
for (var _i5 = joinable.length - 1; _i5 >= 0; _i5--) {
if (prosemirrorTransform.canJoin(tr.doc, joinable[_i5])) tr.join(joinable[_i5]);
}
dispatch(tr);
};
}
function autoJoin(command, isJoinable) {
var canJoin = Array.isArray(isJoinable) ? function (node) {
return isJoinable.indexOf(node.type.name) > -1;
} : isJoinable;
return function (state, dispatch, view) {
return command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view);
};
}
function chainCommands() {
for (var _len = arguments.length, commands = new Array(_len), _key = 0; _key < _len; _key++) {
commands[_key] = arguments[_key];
}
return function (state, dispatch, view) {
for (var i = 0; i < commands.length; i++) {
if (commands[i](state, dispatch, view)) return true;
}
return false;
};
}
var backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
var del = chainCommands(deleteSelection, joinForward, selectNodeForward);
var pcBaseKeymap = {
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
"Mod-Enter": exitCode,
"Backspace": backspace,
"Mod-Backspace": backspace,
"Shift-Backspace": backspace,
"Delete": del,
"Mod-Delete": del,
"Mod-a": selectAll
};
var macBaseKeymap = {
"Ctrl-h": pcBaseKeymap["Backspace"],
"Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
"Ctrl-d": pcBaseKeymap["Delete"],
"Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
"Alt-Delete": pcBaseKeymap["Mod-Delete"],
"Alt-d": pcBaseKeymap["Mod-Delete"],
"Ctrl-a": selectTextblockStart,
"Ctrl-e": selectTextblockEnd
};
for (var key in pcBaseKeymap) {
macBaseKeymap[key] = pcBaseKeymap[key];
}
var mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform) : typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false;
var baseKeymap = mac ? macBaseKeymap : pcBaseKeymap;
exports.autoJoin = autoJoin;
exports.baseKeymap = baseKeymap;
exports.chainCommands = chainCommands;
exports.createParagraphNear = createParagraphNear;
exports.deleteSelection = deleteSelection;
exports.exitCode = exitCode;
exports.joinBackward = joinBackward;
exports.joinDown = joinDown;
exports.joinForward = joinForward;
exports.joinTextblockBackward = joinTextblockBackward;
exports.joinTextblockForward = joinTextblockForward;
exports.joinUp = joinUp;
exports.lift = lift;
exports.liftEmptyBlock = liftEmptyBlock;
exports.macBaseKeymap = macBaseKeymap;
exports.newlineInCode = newlineInCode;
exports.pcBaseKeymap = pcBaseKeymap;
exports.selectAll = selectAll;
exports.selectNodeBackward = selectNodeBackward;
exports.selectNodeForward = selectNodeForward;
exports.selectParentNode = selectParentNode;
exports.selectTextblockEnd = selectTextblockEnd;
exports.selectTextblockStart = selectTextblockStart;
exports.setBlockType = setBlockType;
exports.splitBlock = splitBlock;
exports.splitBlockAs = splitBlockAs;
exports.splitBlockKeepMarks = splitBlockKeepMarks;
exports.toggleMark = toggleMark;
exports.wrapIn = wrapIn;

View File

@@ -0,0 +1,196 @@
import { Node, NodeType, Attrs, MarkType } from 'prosemirror-model';
import { Command } from 'prosemirror-state';
/**
Delete the selection, if there is one.
*/
declare const deleteSelection: Command;
/**
If the selection is empty and at the start of a textblock, try to
reduce the distance between that block and the one before it—if
there's a block directly before it that can be joined, join them.
If not, try to move the selected block closer to the next one in
the document structure by lifting it out of its parent or moving it
into a parent of the previous block. Will use the view for accurate
(bidi-aware) start-of-textblock detection if given.
*/
declare const joinBackward: Command;
/**
A more limited form of [`joinBackward`]($commands.joinBackward)
that only tries to join the current textblock to the one before
it, if the cursor is at the start of a textblock.
*/
declare const joinTextblockBackward: Command;
/**
A more limited form of [`joinForward`]($commands.joinForward)
that only tries to join the current textblock to the one after
it, if the cursor is at the end of a textblock.
*/
declare const joinTextblockForward: Command;
/**
When the selection is empty and at the start of a textblock, select
the node before that textblock, if possible. This is intended to be
bound to keys like backspace, after
[`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) or other deleting
commands, as a fall-back behavior when the schema doesn't allow
deletion at the selected point.
*/
declare const selectNodeBackward: Command;
/**
If the selection is empty and the cursor is at the end of a
textblock, try to reduce or remove the boundary between that block
and the one after it, either by joining them or by moving the other
block closer to this one in the tree structure. Will use the view
for accurate start-of-textblock detection if given.
*/
declare const joinForward: Command;
/**
When the selection is empty and at the end of a textblock, select
the node coming after that textblock, if possible. This is intended
to be bound to keys like delete, after
[`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) and similar deleting
commands, to provide a fall-back behavior when the schema doesn't
allow deletion at the selected point.
*/
declare const selectNodeForward: Command;
/**
Join the selected block or, if there is a text selection, the
closest ancestor block of the selection that can be joined, with
the sibling above it.
*/
declare const joinUp: Command;
/**
Join the selected block, or the closest ancestor of the selection
that can be joined, with the sibling after it.
*/
declare const joinDown: Command;
/**
Lift the selected block, or the closest ancestor block of the
selection that can be lifted, out of its parent node.
*/
declare const lift: Command;
/**
If the selection is in a node whose type has a truthy
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, replace the
selection with a newline character.
*/
declare const newlineInCode: Command;
/**
When the selection is in a node with a truthy
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, create a
default block after the code block, and move the cursor there.
*/
declare const exitCode: Command;
/**
If a block node is selected, create an empty paragraph before (if
it is its parent's first child) or after it.
*/
declare const createParagraphNear: Command;
/**
If the cursor is in an empty textblock that can be lifted, lift the
block.
*/
declare const liftEmptyBlock: Command;
/**
Create a variant of [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) that uses
a custom function to determine the type of the newly split off block.
*/
declare function splitBlockAs(splitNode?: (node: Node, atEnd: boolean) => {
type: NodeType;
attrs?: Attrs;
} | null): Command;
/**
Split the parent block of the selection. If the selection is a text
selection, also delete its content.
*/
declare const splitBlock: Command;
/**
Acts like [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock), but without
resetting the set of active marks at the cursor.
*/
declare const splitBlockKeepMarks: Command;
/**
Move the selection to the node wrapping the current selection, if
any. (Will not select the document node.)
*/
declare const selectParentNode: Command;
/**
Select the whole document.
*/
declare const selectAll: Command;
/**
Moves the cursor to the start of current text block.
*/
declare const selectTextblockStart: Command;
/**
Moves the cursor to the end of current text block.
*/
declare const selectTextblockEnd: Command;
/**
Wrap the selection in a node of the given type with the given
attributes.
*/
declare function wrapIn(nodeType: NodeType, attrs?: Attrs | null): Command;
/**
Returns a command that tries to set the selected textblocks to the
given node type with the given attributes.
*/
declare function setBlockType(nodeType: NodeType, attrs?: Attrs | null): Command;
/**
Create a command function that toggles the given mark with the
given attributes. Will return `false` when the current selection
doesn't support that mark. This will remove the mark if any marks
of that type exist in the selection, or add it otherwise. If the
selection is empty, this applies to the [stored
marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks) instead of a range of the
document.
*/
declare function toggleMark(markType: MarkType, attrs?: Attrs | null): Command;
/**
Wrap a command so that, when it produces a transform that causes
two joinable nodes to end up next to each other, those are joined.
Nodes are considered joinable when they are of the same type and
when the `isJoinable` predicate returns true for them or, if an
array of strings was passed, if their node type name is in that
array.
*/
declare function autoJoin(command: Command, isJoinable: ((before: Node, after: Node) => boolean) | readonly string[]): Command;
/**
Combine a number of command functions into a single function (which
calls them one by one until one returns true).
*/
declare function chainCommands(...commands: readonly Command[]): Command;
/**
A basic keymap containing bindings not specific to any schema.
Binds the following keys (when multiple commands are listed, they
are chained with [`chainCommands`](https://prosemirror.net/docs/ref/#commands.chainCommands)):
* **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
* **Mod-Enter** to `exitCode`
* **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
* **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
* **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
* **Mod-a** to `selectAll`
*/
declare const pcBaseKeymap: {
[key: string]: Command;
};
/**
A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
**Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
**Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
Ctrl-Delete.
*/
declare const macBaseKeymap: {
[key: string]: Command;
};
/**
Depending on the detected platform, this will hold
[`pcBasekeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) or
[`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
*/
declare const baseKeymap: {
[key: string]: Command;
};
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinTextblockBackward, joinTextblockForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockAs, splitBlockKeepMarks, toggleMark, wrapIn };

View File

@@ -0,0 +1,801 @@
import { liftTarget, replaceStep, ReplaceStep, canJoin, joinPoint, canSplit, ReplaceAroundStep, findWrapping } from 'prosemirror-transform';
import { Slice, Fragment } from 'prosemirror-model';
import { NodeSelection, Selection, TextSelection, AllSelection } from 'prosemirror-state';
/**
Delete the selection, if there is one.
*/
const deleteSelection = (state, dispatch) => {
if (state.selection.empty)
return false;
if (dispatch)
dispatch(state.tr.deleteSelection().scrollIntoView());
return true;
};
function atBlockStart(state, view) {
let { $cursor } = state.selection;
if (!$cursor || (view ? !view.endOfTextblock("backward", state)
: $cursor.parentOffset > 0))
return null;
return $cursor;
}
/**
If the selection is empty and at the start of a textblock, try to
reduce the distance between that block and the one before it—if
there's a block directly before it that can be joined, join them.
If not, try to move the selected block closer to the next one in
the document structure by lifting it out of its parent or moving it
into a parent of the previous block. Will use the view for accurate
(bidi-aware) start-of-textblock detection if given.
*/
const joinBackward = (state, dispatch, view) => {
let $cursor = atBlockStart(state, view);
if (!$cursor)
return false;
let $cut = findCutBefore($cursor);
// If there is no node before this, try to lift
if (!$cut) {
let range = $cursor.blockRange(), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
let before = $cut.nodeBefore;
// Apply the joining algorithm
if (!before.type.spec.isolating && deleteBarrier(state, $cut, dispatch))
return true;
// If the node below has no content and the node above is
// selectable, delete the node below and select the one above.
if ($cursor.parent.content.size == 0 &&
(textblockAt(before, "end") || NodeSelection.isSelectable(before))) {
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
let tr = state.tr.step(delStep);
tr.setSelection(textblockAt(before, "end") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1)
: NodeSelection.create(tr.doc, $cut.pos - before.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
}
}
// If the node before is an atom, delete it
if (before.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch)
dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView());
return true;
}
return false;
};
/**
A more limited form of [`joinBackward`]($commands.joinBackward)
that only tries to join the current textblock to the one before
it, if the cursor is at the start of a textblock.
*/
const joinTextblockBackward = (state, dispatch, view) => {
let $cursor = atBlockStart(state, view);
if (!$cursor)
return false;
let $cut = findCutBefore($cursor);
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
};
/**
A more limited form of [`joinForward`]($commands.joinForward)
that only tries to join the current textblock to the one after
it, if the cursor is at the end of a textblock.
*/
const joinTextblockForward = (state, dispatch, view) => {
let $cursor = atBlockEnd(state, view);
if (!$cursor)
return false;
let $cut = findCutAfter($cursor);
return $cut ? joinTextblocksAround(state, $cut, dispatch) : false;
};
function joinTextblocksAround(state, $cut, dispatch) {
let before = $cut.nodeBefore, beforeText = before, beforePos = $cut.pos - 1;
for (; !beforeText.isTextblock; beforePos--) {
if (beforeText.type.spec.isolating)
return false;
let child = beforeText.lastChild;
if (!child)
return false;
beforeText = child;
}
let after = $cut.nodeAfter, afterText = after, afterPos = $cut.pos + 1;
for (; !afterText.isTextblock; afterPos++) {
if (afterText.type.spec.isolating)
return false;
let child = afterText.firstChild;
if (!child)
return false;
afterText = child;
}
let step = replaceStep(state.doc, beforePos, afterPos, Slice.empty);
if (!step || step.from != beforePos ||
step instanceof ReplaceStep && step.slice.size >= afterPos - beforePos)
return false;
if (dispatch) {
let tr = state.tr.step(step);
tr.setSelection(TextSelection.create(tr.doc, beforePos));
dispatch(tr.scrollIntoView());
}
return true;
}
function textblockAt(node, side, only = false) {
for (let scan = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) {
if (scan.isTextblock)
return true;
if (only && scan.childCount != 1)
return false;
}
return false;
}
/**
When the selection is empty and at the start of a textblock, select
the node before that textblock, if possible. This is intended to be
bound to keys like backspace, after
[`joinBackward`](https://prosemirror.net/docs/ref/#commands.joinBackward) or other deleting
commands, as a fall-back behavior when the schema doesn't allow
deletion at the selected point.
*/
const selectNodeBackward = (state, dispatch, view) => {
let { $head, empty } = state.selection, $cut = $head;
if (!empty)
return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0)
return false;
$cut = findCutBefore($head);
}
let node = $cut && $cut.nodeBefore;
if (!node || !NodeSelection.isSelectable(node))
return false;
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos - node.nodeSize)).scrollIntoView());
return true;
};
function findCutBefore($pos) {
if (!$pos.parent.type.spec.isolating)
for (let i = $pos.depth - 1; i >= 0; i--) {
if ($pos.index(i) > 0)
return $pos.doc.resolve($pos.before(i + 1));
if ($pos.node(i).type.spec.isolating)
break;
}
return null;
}
function atBlockEnd(state, view) {
let { $cursor } = state.selection;
if (!$cursor || (view ? !view.endOfTextblock("forward", state)
: $cursor.parentOffset < $cursor.parent.content.size))
return null;
return $cursor;
}
/**
If the selection is empty and the cursor is at the end of a
textblock, try to reduce or remove the boundary between that block
and the one after it, either by joining them or by moving the other
block closer to this one in the tree structure. Will use the view
for accurate start-of-textblock detection if given.
*/
const joinForward = (state, dispatch, view) => {
let $cursor = atBlockEnd(state, view);
if (!$cursor)
return false;
let $cut = findCutAfter($cursor);
// If there is no node after this, there's nothing to do
if (!$cut)
return false;
let after = $cut.nodeAfter;
// Try the joining algorithm
if (deleteBarrier(state, $cut, dispatch))
return true;
// If the node above has no content and the node below is
// selectable, delete the node above and select the one below.
if ($cursor.parent.content.size == 0 &&
(textblockAt(after, "start") || NodeSelection.isSelectable(after))) {
let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty);
if (delStep && delStep.slice.size < delStep.to - delStep.from) {
if (dispatch) {
let tr = state.tr.step(delStep);
tr.setSelection(textblockAt(after, "start") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1)
: NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)));
dispatch(tr.scrollIntoView());
}
return true;
}
}
// If the next node is an atom, delete it
if (after.isAtom && $cut.depth == $cursor.depth - 1) {
if (dispatch)
dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView());
return true;
}
return false;
};
/**
When the selection is empty and at the end of a textblock, select
the node coming after that textblock, if possible. This is intended
to be bound to keys like delete, after
[`joinForward`](https://prosemirror.net/docs/ref/#commands.joinForward) and similar deleting
commands, to provide a fall-back behavior when the schema doesn't
allow deletion at the selected point.
*/
const selectNodeForward = (state, dispatch, view) => {
let { $head, empty } = state.selection, $cut = $head;
if (!empty)
return false;
if ($head.parent.isTextblock) {
if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size)
return false;
$cut = findCutAfter($head);
}
let node = $cut && $cut.nodeAfter;
if (!node || !NodeSelection.isSelectable(node))
return false;
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut.pos)).scrollIntoView());
return true;
};
function findCutAfter($pos) {
if (!$pos.parent.type.spec.isolating)
for (let i = $pos.depth - 1; i >= 0; i--) {
let parent = $pos.node(i);
if ($pos.index(i) + 1 < parent.childCount)
return $pos.doc.resolve($pos.after(i + 1));
if (parent.type.spec.isolating)
break;
}
return null;
}
/**
Join the selected block or, if there is a text selection, the
closest ancestor block of the selection that can be joined, with
the sibling above it.
*/
const joinUp = (state, dispatch) => {
let sel = state.selection, nodeSel = sel instanceof NodeSelection, point;
if (nodeSel) {
if (sel.node.isTextblock || !canJoin(state.doc, sel.from))
return false;
point = sel.from;
}
else {
point = joinPoint(state.doc, sel.from, -1);
if (point == null)
return false;
}
if (dispatch) {
let tr = state.tr.join(point);
if (nodeSel)
tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize));
dispatch(tr.scrollIntoView());
}
return true;
};
/**
Join the selected block, or the closest ancestor of the selection
that can be joined, with the sibling after it.
*/
const joinDown = (state, dispatch) => {
let sel = state.selection, point;
if (sel instanceof NodeSelection) {
if (sel.node.isTextblock || !canJoin(state.doc, sel.to))
return false;
point = sel.to;
}
else {
point = joinPoint(state.doc, sel.to, 1);
if (point == null)
return false;
}
if (dispatch)
dispatch(state.tr.join(point).scrollIntoView());
return true;
};
/**
Lift the selected block, or the closest ancestor block of the
selection that can be lifted, out of its parent node.
*/
const lift = (state, dispatch) => {
let { $from, $to } = state.selection;
let range = $from.blockRange($to), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
/**
If the selection is in a node whose type has a truthy
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, replace the
selection with a newline character.
*/
const newlineInCode = (state, dispatch) => {
let { $head, $anchor } = state.selection;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
return false;
if (dispatch)
dispatch(state.tr.insertText("\n").scrollIntoView());
return true;
};
function defaultBlockAt(match) {
for (let i = 0; i < match.edgeCount; i++) {
let { type } = match.edge(i);
if (type.isTextblock && !type.hasRequiredAttrs())
return type;
}
return null;
}
/**
When the selection is in a node with a truthy
[`code`](https://prosemirror.net/docs/ref/#model.NodeSpec.code) property in its spec, create a
default block after the code block, and move the cursor there.
*/
const exitCode = (state, dispatch) => {
let { $head, $anchor } = state.selection;
if (!$head.parent.type.spec.code || !$head.sameParent($anchor))
return false;
let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt(above.contentMatchAt(after));
if (!type || !above.canReplaceWith(after, after, type))
return false;
if (dispatch) {
let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill());
tr.setSelection(Selection.near(tr.doc.resolve(pos), 1));
dispatch(tr.scrollIntoView());
}
return true;
};
/**
If a block node is selected, create an empty paragraph before (if
it is its parent's first child) or after it.
*/
const createParagraphNear = (state, dispatch) => {
let sel = state.selection, { $from, $to } = sel;
if (sel instanceof AllSelection || $from.parent.inlineContent || $to.parent.inlineContent)
return false;
let type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()));
if (!type || !type.isTextblock)
return false;
if (dispatch) {
let side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos;
let tr = state.tr.insert(side, type.createAndFill());
tr.setSelection(TextSelection.create(tr.doc, side + 1));
dispatch(tr.scrollIntoView());
}
return true;
};
/**
If the cursor is in an empty textblock that can be lifted, lift the
block.
*/
const liftEmptyBlock = (state, dispatch) => {
let { $cursor } = state.selection;
if (!$cursor || $cursor.parent.content.size)
return false;
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
let before = $cursor.before();
if (canSplit(state.doc, before)) {
if (dispatch)
dispatch(state.tr.split(before).scrollIntoView());
return true;
}
}
let range = $cursor.blockRange(), target = range && liftTarget(range);
if (target == null)
return false;
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
};
/**
Create a variant of [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock) that uses
a custom function to determine the type of the newly split off block.
*/
function splitBlockAs(splitNode) {
return (state, dispatch) => {
let { $from, $to } = state.selection;
if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
if (!$from.parentOffset || !canSplit(state.doc, $from.pos))
return false;
if (dispatch)
dispatch(state.tr.split($from.pos).scrollIntoView());
return true;
}
if (!$from.parent.isBlock)
return false;
if (dispatch) {
let atEnd = $to.parentOffset == $to.parent.content.size;
let tr = state.tr;
if (state.selection instanceof TextSelection || state.selection instanceof AllSelection)
tr.deleteSelection();
let deflt = $from.depth == 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
let splitType = splitNode && splitNode($to.parent, atEnd);
let types = splitType ? [splitType] : atEnd && deflt ? [{ type: deflt }] : undefined;
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)) {
if (deflt)
types = [{ type: deflt }];
can = true;
}
if (can) {
tr.split(tr.mapping.map($from.pos), 1, types);
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt) {
let first = tr.mapping.map($from.before()), $first = tr.doc.resolve(first);
if (deflt && $from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt))
tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
}
}
dispatch(tr.scrollIntoView());
}
return true;
};
}
/**
Split the parent block of the selection. If the selection is a text
selection, also delete its content.
*/
const splitBlock = splitBlockAs();
/**
Acts like [`splitBlock`](https://prosemirror.net/docs/ref/#commands.splitBlock), but without
resetting the set of active marks at the cursor.
*/
const splitBlockKeepMarks = (state, dispatch) => {
return splitBlock(state, dispatch && (tr => {
let marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (marks)
tr.ensureMarks(marks);
dispatch(tr);
}));
};
/**
Move the selection to the node wrapping the current selection, if
any. (Will not select the document node.)
*/
const selectParentNode = (state, dispatch) => {
let { $from, to } = state.selection, pos;
let same = $from.sharedDepth(to);
if (same == 0)
return false;
pos = $from.before(same);
if (dispatch)
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)));
return true;
};
/**
Select the whole document.
*/
const selectAll = (state, dispatch) => {
if (dispatch)
dispatch(state.tr.setSelection(new AllSelection(state.doc)));
return true;
};
function joinMaybeClear(state, $pos, dispatch) {
let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index();
if (!before || !after || !before.type.compatibleContent(after.type))
return false;
if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
if (dispatch)
dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView());
return true;
}
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || canJoin(state.doc, $pos.pos)))
return false;
if (dispatch)
dispatch(state.tr
.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount))
.join($pos.pos)
.scrollIntoView());
return true;
}
function deleteBarrier(state, $cut, dispatch) {
let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match;
if (before.type.spec.isolating || after.type.spec.isolating)
return false;
if (joinMaybeClear(state, $cut, dispatch))
return true;
let canDelAfter = $cut.parent.canReplace($cut.index(), $cut.index() + 1);
if (canDelAfter &&
(conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) &&
match.matchType(conn[0] || after.type).validEnd) {
if (dispatch) {
let end = $cut.pos + after.nodeSize, wrap = Fragment.empty;
for (let i = conn.length - 1; i >= 0; i--)
wrap = Fragment.from(conn[i].create(null, wrap));
wrap = Fragment.from(before.copy(wrap));
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true));
let joinAt = end + 2 * conn.length;
if (canJoin(tr.doc, joinAt))
tr.join(joinAt);
dispatch(tr.scrollIntoView());
}
return true;
}
let selAfter = Selection.findFrom($cut, 1);
let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range);
if (target != null && target >= $cut.depth) {
if (dispatch)
dispatch(state.tr.lift(range, target).scrollIntoView());
return true;
}
if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
let at = before, wrap = [];
for (;;) {
wrap.push(at);
if (at.isTextblock)
break;
at = at.lastChild;
}
let afterText = after, afterDepth = 1;
for (; !afterText.isTextblock; afterText = afterText.firstChild)
afterDepth++;
if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
if (dispatch) {
let end = Fragment.empty;
for (let i = wrap.length - 1; i >= 0; i--)
end = Fragment.from(wrap[i].copy(end));
let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize, $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth, new Slice(end, wrap.length, 0), 0, true));
dispatch(tr.scrollIntoView());
}
return true;
}
}
return false;
}
function selectTextblockSide(side) {
return function (state, dispatch) {
let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to;
let depth = $pos.depth;
while ($pos.node(depth).isInline) {
if (!depth)
return false;
depth--;
}
if (!$pos.node(depth).isTextblock)
return false;
if (dispatch)
dispatch(state.tr.setSelection(TextSelection.create(state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))));
return true;
};
}
/**
Moves the cursor to the start of current text block.
*/
const selectTextblockStart = selectTextblockSide(-1);
/**
Moves the cursor to the end of current text block.
*/
const selectTextblockEnd = selectTextblockSide(1);
// Parameterized commands
/**
Wrap the selection in a node of the given type with the given
attributes.
*/
function wrapIn(nodeType, attrs = null) {
return function (state, dispatch) {
let { $from, $to } = state.selection;
let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs);
if (!wrapping)
return false;
if (dispatch)
dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
return true;
};
}
/**
Returns a command that tries to set the selected textblocks to the
given node type with the given attributes.
*/
function setBlockType(nodeType, attrs = null) {
return function (state, dispatch) {
let applicable = false;
for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
state.doc.nodesBetween(from, to, (node, pos) => {
if (applicable)
return false;
if (!node.isTextblock || node.hasMarkup(nodeType, attrs))
return;
if (node.type == nodeType) {
applicable = true;
}
else {
let $pos = state.doc.resolve(pos), index = $pos.index();
applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType);
}
});
}
if (!applicable)
return false;
if (dispatch) {
let tr = state.tr;
for (let i = 0; i < state.selection.ranges.length; i++) {
let { $from: { pos: from }, $to: { pos: to } } = state.selection.ranges[i];
tr.setBlockType(from, to, nodeType, attrs);
}
dispatch(tr.scrollIntoView());
}
return true;
};
}
function markApplies(doc, ranges, type) {
for (let i = 0; i < ranges.length; i++) {
let { $from, $to } = ranges[i];
let can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false;
doc.nodesBetween($from.pos, $to.pos, node => {
if (can)
return false;
can = node.inlineContent && node.type.allowsMarkType(type);
});
if (can)
return true;
}
return false;
}
/**
Create a command function that toggles the given mark with the
given attributes. Will return `false` when the current selection
doesn't support that mark. This will remove the mark if any marks
of that type exist in the selection, or add it otherwise. If the
selection is empty, this applies to the [stored
marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks) instead of a range of the
document.
*/
function toggleMark(markType, attrs = null) {
return function (state, dispatch) {
let { empty, $cursor, ranges } = state.selection;
if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType))
return false;
if (dispatch) {
if ($cursor) {
if (markType.isInSet(state.storedMarks || $cursor.marks()))
dispatch(state.tr.removeStoredMark(markType));
else
dispatch(state.tr.addStoredMark(markType.create(attrs)));
}
else {
let has = false, tr = state.tr;
for (let i = 0; !has && i < ranges.length; i++) {
let { $from, $to } = ranges[i];
has = state.doc.rangeHasMark($from.pos, $to.pos, markType);
}
for (let i = 0; i < ranges.length; i++) {
let { $from, $to } = ranges[i];
if (has) {
tr.removeMark($from.pos, $to.pos, markType);
}
else {
let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore;
let spaceStart = start && start.isText ? /^\s*/.exec(start.text)[0].length : 0;
let spaceEnd = end && end.isText ? /\s*$/.exec(end.text)[0].length : 0;
if (from + spaceStart < to) {
from += spaceStart;
to -= spaceEnd;
}
tr.addMark(from, to, markType.create(attrs));
}
}
dispatch(tr.scrollIntoView());
}
}
return true;
};
}
function wrapDispatchForJoin(dispatch, isJoinable) {
return (tr) => {
if (!tr.isGeneric)
return dispatch(tr);
let ranges = [];
for (let i = 0; i < tr.mapping.maps.length; i++) {
let map = tr.mapping.maps[i];
for (let j = 0; j < ranges.length; j++)
ranges[j] = map.map(ranges[j]);
map.forEach((_s, _e, from, to) => ranges.push(from, to));
}
// Figure out which joinable points exist inside those ranges,
// by checking all node boundaries in their parent nodes.
let joinable = [];
for (let i = 0; i < ranges.length; i += 2) {
let from = ranges[i], to = ranges[i + 1];
let $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth);
for (let index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
let after = parent.maybeChild(index);
if (!after)
break;
if (index && joinable.indexOf(pos) == -1) {
let before = parent.child(index - 1);
if (before.type == after.type && isJoinable(before, after))
joinable.push(pos);
}
pos += after.nodeSize;
}
}
// Join the joinable points
joinable.sort((a, b) => a - b);
for (let i = joinable.length - 1; i >= 0; i--) {
if (canJoin(tr.doc, joinable[i]))
tr.join(joinable[i]);
}
dispatch(tr);
};
}
/**
Wrap a command so that, when it produces a transform that causes
two joinable nodes to end up next to each other, those are joined.
Nodes are considered joinable when they are of the same type and
when the `isJoinable` predicate returns true for them or, if an
array of strings was passed, if their node type name is in that
array.
*/
function autoJoin(command, isJoinable) {
let canJoin = Array.isArray(isJoinable) ? (node) => isJoinable.indexOf(node.type.name) > -1
: isJoinable;
return (state, dispatch, view) => command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view);
}
/**
Combine a number of command functions into a single function (which
calls them one by one until one returns true).
*/
function chainCommands(...commands) {
return function (state, dispatch, view) {
for (let i = 0; i < commands.length; i++)
if (commands[i](state, dispatch, view))
return true;
return false;
};
}
let backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
let del = chainCommands(deleteSelection, joinForward, selectNodeForward);
/**
A basic keymap containing bindings not specific to any schema.
Binds the following keys (when multiple commands are listed, they
are chained with [`chainCommands`](https://prosemirror.net/docs/ref/#commands.chainCommands)):
* **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
* **Mod-Enter** to `exitCode`
* **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
* **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
* **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
* **Mod-a** to `selectAll`
*/
const pcBaseKeymap = {
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
"Mod-Enter": exitCode,
"Backspace": backspace,
"Mod-Backspace": backspace,
"Shift-Backspace": backspace,
"Delete": del,
"Mod-Delete": del,
"Mod-a": selectAll
};
/**
A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
**Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
**Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
Ctrl-Delete.
*/
const macBaseKeymap = {
"Ctrl-h": pcBaseKeymap["Backspace"],
"Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
"Ctrl-d": pcBaseKeymap["Delete"],
"Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
"Alt-Delete": pcBaseKeymap["Mod-Delete"],
"Alt-d": pcBaseKeymap["Mod-Delete"],
"Ctrl-a": selectTextblockStart,
"Ctrl-e": selectTextblockEnd
};
for (let key in pcBaseKeymap)
macBaseKeymap[key] = pcBaseKeymap[key];
const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform)
// @ts-ignore
: typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false;
/**
Depending on the detected platform, this will hold
[`pcBasekeymap`](https://prosemirror.net/docs/ref/#commands.pcBaseKeymap) or
[`macBaseKeymap`](https://prosemirror.net/docs/ref/#commands.macBaseKeymap).
*/
const baseKeymap = mac ? macBaseKeymap : pcBaseKeymap;
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinTextblockBackward, joinTextblockForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockAs, splitBlockKeepMarks, toggleMark, wrapIn };