prosemirror-commands
Advanced tools
Comparing version 0.22.0 to 0.23.0
@@ -16,3 +16,3 @@ # How to contribute | ||
[GitHub issue tracker](http://github.com/prosemirror/prosemirror/issues). | ||
Before reporting a bug, read these pointers. | ||
Before reporting a bug, please read these pointers. | ||
@@ -26,6 +26,7 @@ - The issue tracker is for *bugs*, not requests for help. Questions | ||
- Mention very precisely what went wrong. "X is broken" is not a good bug | ||
report. What did you expect to happen? What happened instead? Describe the | ||
exact steps a maintainer has to take to make the problem occur. We can not | ||
fix something that we can not observe. | ||
- Mention very precisely what went wrong. "X is broken" is not a good | ||
bug report. What did you expect to happen? What happened instead? | ||
Describe the exact steps a maintainer has to take to make the | ||
problem occur. A screencast can be useful, but is no substitute for | ||
a textual description. | ||
@@ -50,4 +51,4 @@ - A great way to make it easy to reproduce your problem, if it can not | ||
- Follow the code style of the rest of the project (see below). Run | ||
`npm run lint` (in the main repository checkout) that the linter is | ||
happy. | ||
`npm run lint` (in the main repository checkout) to make sure that | ||
the linter is happy. | ||
@@ -54,0 +55,0 @@ - If your changes are easy to test or likely to regress, add tests in |
@@ -1,17 +0,9 @@ | ||
var ref = require("prosemirror-transform"); | ||
var joinPoint = ref.joinPoint; | ||
var canJoin = ref.canJoin; | ||
var findWrapping = ref.findWrapping; | ||
var liftTarget = ref.liftTarget; | ||
var canSplit = ref.canSplit; | ||
var ReplaceAroundStep = ref.ReplaceAroundStep; | ||
var ref$1 = require("prosemirror-model"); | ||
var Slice = ref$1.Slice; | ||
var Fragment = ref$1.Fragment; | ||
var ref$2 = require("prosemirror-state"); | ||
var Selection = ref$2.Selection; | ||
var TextSelection = ref$2.TextSelection; | ||
var NodeSelection = ref$2.NodeSelection; | ||
var AllSelection = ref$2.AllSelection; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var prosemirrorTransform = require('prosemirror-transform'); | ||
var prosemirrorModel = require('prosemirror-model'); | ||
var prosemirrorState = require('prosemirror-state'); | ||
// :: (EditorState, ?(tr: Transaction)) → bool | ||
@@ -21,14 +13,14 @@ // Delete the selection, if there is one. | ||
if (state.selection.empty) { return false } | ||
if (dispatch) { dispatch(state.tr.deleteSelection().scrollIntoView()) } | ||
if (dispatch) { dispatch(state.tr.deleteSelection().scrollIntoView()); } | ||
return true | ||
} | ||
exports.deleteSelection = deleteSelection | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// If the selection is empty and at the start of a textblock, move | ||
// that block closer to the block before it, by lifting it out of its | ||
// parent or, if it has no parent it doesn't share with the node | ||
// before it, moving it into a parent of that node, or joining it with | ||
// that. Will use the view for accurate start-of-textblock detection | ||
// if given. | ||
// 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. | ||
function joinBackward(state, dispatch, view) { | ||
@@ -41,28 +33,26 @@ var ref = state.selection; | ||
// Find the node before this one | ||
var before, cut, cutDepth | ||
if (!$cursor.parent.type.spec.isolating) { for (var i = $cursor.depth - 1; !before && i >= 0; i--) { | ||
if ($cursor.index(i) > 0) { | ||
cut = $cursor.before(i + 1) | ||
before = $cursor.node(i).child($cursor.index(i) - 1) | ||
cutDepth = i | ||
} | ||
if ($cursor.node(i).type.spec.isolating) { break } | ||
} } | ||
var $cut = findCutBefore($cursor); | ||
// If there is no node before this, try to lift | ||
if (!before) { | ||
var range = $cursor.blockRange(), target = range && liftTarget(range) | ||
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()) } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()); } | ||
return true | ||
} | ||
var 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 (before.isAtom && NodeSelection.isSelectable(before) && $cursor.parent.content.size == 0) { | ||
if ($cursor.parent.content.size == 0 && | ||
(textblockAt(before, "end") || prosemirrorState.NodeSelection.isSelectable(before))) { | ||
if (dispatch) { | ||
var tr = state.tr.delete(cut, cut + $cursor.parent.nodeSize) | ||
tr.setSelection(NodeSelection.create(tr.doc, cut - before.nodeSize)) | ||
dispatch(tr.scrollIntoView()) | ||
var tr = state.tr.deleteRange($cursor.before(), $cursor.after()); | ||
tr.setSelection(textblockAt(before, "end") ? prosemirrorState.Selection.findFrom($cut, -1) | ||
: prosemirrorState.NodeSelection.create(tr.doc, $cut.pos - before.nodeSize)); | ||
dispatch(tr.scrollIntoView()); | ||
} | ||
@@ -72,21 +62,52 @@ return true | ||
// If the node doesn't allow children, delete it | ||
if (before.isLeaf && cutDepth == $cursor.depth - 1) { | ||
if (dispatch) { dispatch(state.tr.delete(cut - before.nodeSize, cut).scrollIntoView()) } | ||
// 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 | ||
} | ||
// Apply the joining algorithm | ||
return !before.type.spec.isolating && deleteBarrier(state, cut, dispatch) || | ||
selectNextNode(state, cut, -1, dispatch) | ||
return false | ||
} | ||
exports.joinBackward = joinBackward | ||
function textblockAt(node, side) { | ||
for (; node; node = (side == "start" ? node.firstChild : node.lastChild)) | ||
{ if (node.isTextblock) { return true } } | ||
return false | ||
} | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// 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`](#commands.joinBackward) or other deleting | ||
// commands, as a fall-back behavior when the schema doesn't allow | ||
// deletion at the selected point. | ||
function selectNodeBackward(state, dispatch, view) { | ||
var ref = state.selection; | ||
var $cursor = ref.$cursor; | ||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
: $cursor.parentOffset > 0)) | ||
{ return false } | ||
var $cut = findCutBefore($cursor), 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 | ||
} | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// If the selection is empty and the cursor is at the end of a | ||
// textblock, move the node after it closer to the node with the | ||
// cursor (lifting it out of parents that aren't shared, moving it | ||
// into parents of the cursor block, or joining the two when they are | ||
// siblings). Will use the view for accurate start-of-textblock | ||
// detection if given. | ||
// 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. | ||
function joinForward(state, dispatch, view) { | ||
@@ -99,27 +120,63 @@ var ref = state.selection; | ||
// Find the node after this one | ||
var after, cut, cutDepth | ||
if (!$cursor.parent.type.spec.isolating) { for (var i = $cursor.depth - 1; !after && i >= 0; i--) { | ||
var parent = $cursor.node(i) | ||
if ($cursor.index(i) + 1 < parent.childCount) { | ||
after = parent.child($cursor.index(i) + 1) | ||
cut = $cursor.after(i + 1) | ||
cutDepth = i | ||
} | ||
if (parent.type.spec.isolating) { break } | ||
} } | ||
var $cut = findCutAfter($cursor); | ||
// If there is no node after this, there's nothing to do | ||
if (!after) { return false } | ||
if (!$cut) { return false } | ||
// If the node doesn't allow children, delete it | ||
if (after.isLeaf && cutDepth == $cursor.depth - 1) { | ||
if (dispatch) { dispatch(state.tr.delete(cut, cut + after.nodeSize).scrollIntoView()) } | ||
var 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") || prosemirrorState.NodeSelection.isSelectable(after))) { | ||
if (dispatch) { | ||
var tr = state.tr.deleteRange($cursor.before(), $cursor.after()); | ||
tr.setSelection(textblockAt(after, "start") ? prosemirrorState.Selection.findFrom($cut, 1) | ||
: prosemirrorState.NodeSelection.create(tr.doc, tr.mapping.map($cut.pos))); | ||
dispatch(tr.scrollIntoView()); | ||
} | ||
return true | ||
} | ||
// Apply the joining algorithm | ||
return deleteBarrier(state, cut, dispatch) || selectNextNode(state, cut, 1, dispatch) | ||
// 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 | ||
} | ||
exports.joinForward = joinForward | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// 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`](#commands.joinForward) and similar deleting | ||
// commands, to provide a fall-back behavior when the schema doesn't | ||
// allow deletion at the selected point. | ||
function selectNodeForward(state, dispatch, view) { | ||
var ref = state.selection; | ||
var $cursor = ref.$cursor; | ||
if (!$cursor || (view ? !view.endOfTextblock("forward", state) | ||
: $cursor.parentOffset < $cursor.parent.content.size)) | ||
{ return false } | ||
var $cut = findCutAfter($cursor), 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 | ||
} | ||
// :: (EditorState, ?(tr: Transaction)) → bool | ||
@@ -130,18 +187,17 @@ // Join the selected block or, if there is a text selection, the | ||
function joinUp(state, dispatch) { | ||
var sel = state.selection, nodeSel = sel instanceof NodeSelection, point | ||
var sel = state.selection, nodeSel = sel instanceof prosemirrorState.NodeSelection, point; | ||
if (nodeSel) { | ||
if (sel.node.isTextblock || !canJoin(state.doc, sel.from)) { return false } | ||
point = sel.from | ||
if (sel.node.isTextblock || !prosemirrorTransform.canJoin(state.doc, sel.from)) { return false } | ||
point = sel.from; | ||
} else { | ||
point = joinPoint(state.doc, sel.from, -1) | ||
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(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore.nodeSize)) } | ||
dispatch(tr.scrollIntoView()) | ||
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 | ||
} | ||
exports.joinUp = joinUp | ||
@@ -152,15 +208,14 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
function joinDown(state, dispatch) { | ||
var sel = state.selection, point | ||
if (sel instanceof NodeSelection) { | ||
if (sel.node.isTextblock || !canJoin(state.doc, sel.to)) { return false } | ||
point = sel.to | ||
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 = joinPoint(state.doc, sel.to, 1) | ||
point = prosemirrorTransform.joinPoint(state.doc, sel.to, 1); | ||
if (point == null) { return false } | ||
} | ||
if (dispatch) | ||
{ dispatch(state.tr.join(point).scrollIntoView()) } | ||
{ dispatch(state.tr.join(point).scrollIntoView()); } | ||
return true | ||
} | ||
exports.joinDown = joinDown | ||
@@ -174,8 +229,7 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
var $to = ref.$to; | ||
var range = $from.blockRange($to), target = range && liftTarget(range) | ||
var range = $from.blockRange($to), target = range && prosemirrorTransform.liftTarget(range); | ||
if (target == null) { return false } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()) } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()); } | ||
return true | ||
} | ||
exports.lift = lift | ||
@@ -191,6 +245,5 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) { return false } | ||
if (dispatch) { dispatch(state.tr.insertText("\n").scrollIntoView()) } | ||
if (dispatch) { dispatch(state.tr.insertText("\n").scrollIntoView()); } | ||
return true | ||
} | ||
exports.newlineInCode = newlineInCode | ||
@@ -206,12 +259,11 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) { return false } | ||
var above = $head.node(-1), after = $head.indexAfter(-1), type = above.defaultContentType(after) | ||
var above = $head.node(-1), after = $head.indexAfter(-1), type = above.defaultContentType(after); | ||
if (!above.canReplaceWith(after, after, type)) { return false } | ||
if (dispatch) { | ||
var pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill()) | ||
tr.setSelection(Selection.near(tr.doc.resolve(pos), 1)) | ||
dispatch(tr.scrollIntoView()) | ||
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 | ||
} | ||
exports.exitCode = exitCode | ||
@@ -226,13 +278,12 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
if ($from.parent.inlineContent || $to.parent.inlineContent) { return false } | ||
var type = $from.parent.defaultContentType($to.indexAfter()) | ||
var type = $from.parent.defaultContentType($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(TextSelection.create(tr.doc, side + 1)) | ||
dispatch(tr.scrollIntoView()) | ||
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 | ||
} | ||
exports.createParagraphNear = createParagraphNear | ||
@@ -247,14 +298,13 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) { | ||
var before = $cursor.before() | ||
if (canSplit(state.doc, before)) { | ||
if (dispatch) { dispatch(state.tr.split(before).scrollIntoView()) } | ||
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 && liftTarget(range) | ||
var range = $cursor.blockRange(), target = range && prosemirrorTransform.liftTarget(range); | ||
if (target == null) { return false } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()) } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()); } | ||
return true | ||
} | ||
exports.liftEmptyBlock = liftEmptyBlock | ||
@@ -268,5 +318,5 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
var $to = ref.$to; | ||
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()) } | ||
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 | ||
@@ -276,23 +326,22 @@ } | ||
if (dispatch) { | ||
var atEnd = $to.parentOffset == $to.parent.content.size | ||
var tr = state.tr | ||
if (state.selection instanceof TextSelection) { tr.deleteSelection() } | ||
var deflt = $from.depth == 0 ? null : $from.node(-1).defaultContentType($from.indexAfter(-1)) | ||
var types = atEnd ? [{type: deflt}] : null | ||
var can = canSplit(tr.doc, $from.pos, 1, types) | ||
if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, [{type: deflt}])) { | ||
types = [{type: deflt}] | ||
can = true | ||
var atEnd = $to.parentOffset == $to.parent.content.size; | ||
var tr = state.tr; | ||
if (state.selection instanceof prosemirrorState.TextSelection) { tr.deleteSelection(); } | ||
var deflt = $from.depth == 0 ? null : $from.node(-1).defaultContentType($from.indexAfter(-1)); | ||
var types = atEnd ? [{type: deflt}] : null; | ||
var can = prosemirrorTransform.canSplit(tr.doc, $from.pos, 1, types); | ||
if (!types && !can && prosemirrorTransform.canSplit(tr.doc, tr.mapping.map($from.pos), 1, [{type: deflt}])) { | ||
types = [{type: deflt}]; | ||
can = true; | ||
} | ||
if (can) { | ||
tr.split(tr.mapping.map($from.pos), 1, types) | ||
tr.split(tr.mapping.map($from.pos), 1, types); | ||
if (!atEnd && !$from.parentOffset && $from.parent.type != deflt && | ||
$from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt.create(), $from.parent))) | ||
{ tr.setNodeType(tr.mapping.map($from.before()), deflt) } | ||
$from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), prosemirrorModel.Fragment.from(deflt.create(), $from.parent))) | ||
{ tr.setNodeType(tr.mapping.map($from.before()), deflt); } | ||
} | ||
dispatch(tr.scrollIntoView()) | ||
dispatch(tr.scrollIntoView()); | ||
} | ||
return true | ||
} | ||
exports.splitBlock = splitBlock | ||
@@ -304,8 +353,7 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
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 marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); | ||
if (marks) { tr.ensureMarks(marks); } | ||
dispatch(tr); | ||
})) | ||
} | ||
exports.splitBlockKeepMarks = splitBlockKeepMarks | ||
@@ -319,10 +367,9 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
var to = ref.to; | ||
var pos | ||
var same = $from.sharedDepth(to) | ||
var pos; | ||
var same = $from.sharedDepth(to); | ||
if (same == 0) { return false } | ||
pos = $from.before(same) | ||
if (dispatch) { dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos))) } | ||
pos = $from.before(same); | ||
if (dispatch) { dispatch(state.tr.setSelection(prosemirrorState.NodeSelection.create(state.doc, pos))); } | ||
return true | ||
} | ||
exports.selectParentNode = selectParentNode | ||
@@ -332,40 +379,39 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
function selectAll(state, dispatch) { | ||
if (dispatch) { dispatch(state.tr.setSelection(new AllSelection(state.doc))) } | ||
if (dispatch) { dispatch(state.tr.setSelection(new prosemirrorState.AllSelection(state.doc))); } | ||
return true | ||
} | ||
exports.selectAll = selectAll | ||
function joinMaybeClear(state, $pos, dispatch) { | ||
var before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index() | ||
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()) } | ||
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))) | ||
if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || prosemirrorTransform.canJoin(state.doc, $pos.pos))) | ||
{ return false } | ||
if (dispatch) | ||
{ dispatch(state.tr | ||
.clearNonMatching($pos.pos, before.contentMatchAt(before.childCount)) | ||
.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount)) | ||
.join($pos.pos) | ||
.scrollIntoView()) } | ||
.scrollIntoView()); } | ||
return true | ||
} | ||
function deleteBarrier(state, cut, dispatch) { | ||
var $cut = state.doc.resolve(cut), before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match | ||
function deleteBarrier(state, $cut, dispatch) { | ||
var before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match; | ||
if (joinMaybeClear(state, $cut, dispatch)) { return true } | ||
if ($cut.parent.canReplace($cut.index(), $cut.index() + 1) && | ||
(conn = (match = before.contentMatchAt(before.childCount)).findWrappingFor(after))&& | ||
match.matchType((conn[0] || after).type, (conn[0] || after).attrs).validEnd()) { | ||
(conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && | ||
match.matchType(conn[0] || after.type).validEnd) { | ||
if (dispatch) { | ||
var end = cut + after.nodeSize, wrap = Fragment.empty | ||
var end = $cut.pos + after.nodeSize, wrap = prosemirrorModel.Fragment.empty; | ||
for (var i = conn.length - 1; i >= 0; i--) | ||
{ wrap = Fragment.from(conn[i].type.create(conn[i].attrs, wrap)) } | ||
wrap = Fragment.from(before.copy(wrap)) | ||
var tr = state.tr.step(new ReplaceAroundStep(cut - 1, end, cut, end, new Slice(wrap, 1, 0), conn.length, true)) | ||
var joinAt = end + 2 * conn.length | ||
if (canJoin(tr.doc, joinAt)) { tr.join(joinAt) } | ||
dispatch(tr.scrollIntoView()) | ||
{ 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()); | ||
} | ||
@@ -375,6 +421,6 @@ return true | ||
var selAfter = Selection.findFrom($cut, 1) | ||
var range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range) | ||
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()) } | ||
if (dispatch) { dispatch(state.tr.lift(range, target).scrollIntoView()); } | ||
return true | ||
@@ -386,11 +432,2 @@ } | ||
function selectNextNode(state, cut, dir, dispatch) { | ||
var $cut = state.doc.resolve(cut) | ||
var node = dir > 0 ? $cut.nodeAfter : $cut.nodeBefore | ||
if (!node || !NodeSelection.isSelectable(node)) { return false } | ||
if (dispatch) | ||
{ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, cut - (dir > 0 ? 0 : node.nodeSize))).scrollIntoView()) } | ||
return true | ||
} | ||
// Parameterized commands | ||
@@ -406,9 +443,8 @@ | ||
var $to = ref.$to; | ||
var range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs) | ||
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()) } | ||
if (dispatch) { dispatch(state.tr.wrap(range, wrapping).scrollIntoView()); } | ||
return true | ||
} | ||
} | ||
exports.wrapIn = wrapIn | ||
@@ -423,20 +459,20 @@ // :: (NodeType, ?Object) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool | ||
var $to = ref.$to; | ||
var depth, target | ||
if (state.selection instanceof NodeSelection) { | ||
depth = $from.depth | ||
target = state.selection.node | ||
var depth, target; | ||
if (state.selection instanceof prosemirrorState.NodeSelection) { | ||
depth = $from.depth; | ||
target = state.selection.node; | ||
} else { | ||
if (!$from.depth || $to.pos > $from.end()) { return false } | ||
depth = $from.depth - 1 | ||
target = $from.parent | ||
depth = $from.depth - 1; | ||
target = $from.parent; | ||
} | ||
if (!target.isTextblock || target.hasMarkup(nodeType, attrs)) { return false } | ||
var index = $from.index(depth) | ||
var index = $from.index(depth); | ||
if (!$from.node(depth).canReplaceWith(index, index + 1, nodeType)) { return false } | ||
if (dispatch) { | ||
var where = $from.before(depth + 1) | ||
var where = $from.before(depth + 1); | ||
dispatch(state.tr | ||
.clearNonMatching(where, nodeType.contentExpr.start(attrs)) | ||
.clearIncompatible(where, nodeType) | ||
.setNodeType(where, nodeType, attrs) | ||
.scrollIntoView()) | ||
.scrollIntoView()); | ||
} | ||
@@ -446,3 +482,2 @@ return true | ||
} | ||
exports.setBlockType = setBlockType | ||
@@ -454,7 +489,7 @@ function markApplies(doc, ranges, type) { | ||
var $to = ref.$to; | ||
var can = $from.depth == 0 ? doc.contentMatchAt(0).allowsMark(type) : false | ||
var can = $from.depth == 0 ? doc.type.allowsMarkType(type) : false; | ||
doc.nodesBetween($from.pos, $to.pos, function (node) { | ||
if (can) { return false } | ||
can = node.inlineContent && node.contentMatchAt(0).allowsMark(type) | ||
}) | ||
can = node.inlineContent && node.type.allowsMarkType(type); | ||
}); | ||
if (can) { return { v: true } } | ||
@@ -489,7 +524,7 @@ }; | ||
if (markType.isInSet(state.storedMarks || $cursor.marks())) | ||
{ dispatch(state.tr.removeStoredMark(markType)) } | ||
{ dispatch(state.tr.removeStoredMark(markType)); } | ||
else | ||
{ dispatch(state.tr.addStoredMark(markType.create(attrs))) } | ||
{ dispatch(state.tr.addStoredMark(markType.create(attrs))); } | ||
} else { | ||
var has = false, tr = state.tr | ||
var has = false, tr = state.tr; | ||
for (var i = 0; !has && i < ranges.length; i++) { | ||
@@ -499,3 +534,3 @@ var ref$1 = ranges[i]; | ||
var $to = ref$1.$to; | ||
has = state.doc.rangeHasMark($from.pos, $to.pos, markType) | ||
has = state.doc.rangeHasMark($from.pos, $to.pos, markType); | ||
} | ||
@@ -506,6 +541,6 @@ for (var i$1 = 0; i$1 < ranges.length; i$1++) { | ||
var $to$1 = ref$2.$to; | ||
if (has) { tr.removeMark($from$1.pos, $to$1.pos, markType) } | ||
else { tr.addMark($from$1.pos, $to$1.pos, markType.create(attrs)) } | ||
if (has) { tr.removeMark($from$1.pos, $to$1.pos, markType); } | ||
else { tr.addMark($from$1.pos, $to$1.pos, markType.create(attrs)); } | ||
} | ||
dispatch(tr.scrollIntoView()) | ||
dispatch(tr.scrollIntoView()); | ||
} | ||
@@ -516,3 +551,2 @@ } | ||
} | ||
exports.toggleMark = toggleMark | ||
@@ -523,8 +557,8 @@ function wrapDispatchForJoin(dispatch, isJoinable) { | ||
var ranges = [] | ||
var ranges = []; | ||
for (var i = 0; i < tr.mapping.maps.length; i++) { | ||
var map = tr.mapping.maps[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); }) | ||
{ ranges[j] = map.map(ranges[j]); } | ||
map.forEach(function (_s, _e, from, to) { return ranges.push(from, to); }); | ||
} | ||
@@ -534,23 +568,23 @@ | ||
// by checking all node boundaries in their parent nodes. | ||
var joinable = [] | ||
var joinable = []; | ||
for (var i$1 = 0; i$1 < ranges.length; i$1 += 2) { | ||
var from = ranges[i$1], to = ranges[i$1 + 1] | ||
var $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth) | ||
var from = ranges[i$1], to = ranges[i$1 + 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) | ||
var after = parent.maybeChild(index); | ||
if (!after) { break } | ||
if (index && joinable.indexOf(pos) == -1) { | ||
var before = parent.child(index - 1) | ||
var before = parent.child(index - 1); | ||
if (before.type == after.type && isJoinable(before, after)) | ||
{ joinable.push(pos) } | ||
{ joinable.push(pos); } | ||
} | ||
pos += after.nodeSize | ||
pos += after.nodeSize; | ||
} | ||
} | ||
// Join the joinable points | ||
joinable.sort(function (a, b) { return a - b; }) | ||
joinable.sort(function (a, b) { return a - b; }); | ||
for (var i$2 = joinable.length - 1; i$2 >= 0; i$2--) { | ||
if (canJoin(tr.doc, joinable[i$2])) { tr.join(joinable[i$2]) } | ||
if (prosemirrorTransform.canJoin(tr.doc, joinable[i$2])) { tr.join(joinable[i$2]); } | ||
} | ||
dispatch(tr) | ||
dispatch(tr); | ||
} | ||
@@ -568,10 +602,9 @@ } | ||
if (Array.isArray(isJoinable)) { | ||
var types = isJoinable | ||
isJoinable = function (node) { return types.indexOf(node.type.name) > -1; } | ||
var types = isJoinable; | ||
isJoinable = function (node) { return types.indexOf(node.type.name) > -1; }; | ||
} | ||
return function (state, dispatch) { return command(state, dispatch && wrapDispatchForJoin(dispatch, isJoinable)); } | ||
} | ||
exports.autoJoin = autoJoin | ||
// :: (...[(EditorState, ?(tr: Transaction)) → bool]) → (EditorState, ?(tr: Transaction)) → bool | ||
// :: (...[(EditorState, ?(tr: Transaction), ?EditorView) → bool]) → (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// Combine a number of command functions into a single function (which | ||
@@ -589,38 +622,30 @@ // calls them one by one until one returns true). | ||
} | ||
exports.chainCommands = chainCommands | ||
var backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); | ||
var del = chainCommands(deleteSelection, joinForward, selectNodeForward); | ||
// :: Object | ||
// A basic keymap containing bindings not specific to any schema. | ||
// Binds the following keys (when multiple commands are listed, they | ||
// are chained with [`chainCommands`](#commands.chainCommands): | ||
// are chained with [`chainCommands`](#commands.chainCommands)): | ||
// | ||
// * **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock` | ||
// * **Mod-Enter** to `exitCode` | ||
// * **Backspace** to `deleteSelection`, `joinBackward` | ||
// * **Mod-Backspace** to `deleteSelection`, `joinBackward` | ||
// * **Delete** to `deleteSelection`, `joinForward` | ||
// * **Mod-Delete** to `deleteSelection`, `joinForward` | ||
// * **Alt-ArrowUp** to `joinUp` | ||
// * **Alt-ArrowDown** to `joinDown` | ||
// * **Mod-BracketLeft** to `lift` | ||
// * **Escape** to `selectParentNode` | ||
// * **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` | ||
var baseKeymap = { | ||
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock), | ||
"Mod-Enter": exitCode, | ||
"Backspace": chainCommands(deleteSelection, joinBackward), | ||
"Mod-Backspace": chainCommands(deleteSelection, joinBackward), | ||
"Delete": chainCommands(deleteSelection, joinForward), | ||
"Mod-Delete": chainCommands(deleteSelection, joinForward), | ||
"Alt-ArrowUp": joinUp, | ||
"Alt-ArrowDown": joinDown, | ||
"Mod-BracketLeft": lift, | ||
"Escape": selectParentNode, | ||
"Backspace": backspace, | ||
"Mod-Backspace": backspace, | ||
"Delete": del, | ||
"Mod-Delete": del, | ||
"Mod-a": selectAll | ||
} | ||
}; | ||
// declare global: os, navigator | ||
var mac = typeof navigator != "undefined" ? /Mac/.test(navigator.platform) | ||
: typeof os != "undefined" ? os.platform() == "darwin" : false | ||
: typeof os != "undefined" ? os.platform() == "darwin" : false; | ||
@@ -635,6 +660,28 @@ if (mac) { | ||
"Alt-d": baseKeymap["Mod-Delete"] | ||
} | ||
for (var prop in extra) { baseKeymap[prop] = extra[prop] } | ||
}; | ||
for (var prop in extra) { baseKeymap[prop] = extra[prop]; } | ||
} | ||
exports.baseKeymap = baseKeymap | ||
exports.deleteSelection = deleteSelection; | ||
exports.joinBackward = joinBackward; | ||
exports.selectNodeBackward = selectNodeBackward; | ||
exports.joinForward = joinForward; | ||
exports.selectNodeForward = selectNodeForward; | ||
exports.joinUp = joinUp; | ||
exports.joinDown = joinDown; | ||
exports.lift = lift; | ||
exports.newlineInCode = newlineInCode; | ||
exports.exitCode = exitCode; | ||
exports.createParagraphNear = createParagraphNear; | ||
exports.liftEmptyBlock = liftEmptyBlock; | ||
exports.splitBlock = splitBlock; | ||
exports.splitBlockKeepMarks = splitBlockKeepMarks; | ||
exports.selectParentNode = selectParentNode; | ||
exports.selectAll = selectAll; | ||
exports.wrapIn = wrapIn; | ||
exports.setBlockType = setBlockType; | ||
exports.toggleMark = toggleMark; | ||
exports.autoJoin = autoJoin; | ||
exports.chainCommands = chainCommands; | ||
exports.baseKeymap = baseKeymap; | ||
//# sourceMappingURL=commands.js.map |
{ | ||
"name": "prosemirror-commands", | ||
"version": "0.22.0", | ||
"version": "0.23.0", | ||
"description": "Editing commands for ProseMirror", | ||
@@ -19,18 +19,19 @@ "main": "dist/commands.js", | ||
"dependencies": { | ||
"prosemirror-model": "^0.22.0", | ||
"prosemirror-transform": "^0.22.0", | ||
"prosemirror-state": "^0.22.0" | ||
"prosemirror-model": "^0.23.0", | ||
"prosemirror-transform": "^0.23.0", | ||
"prosemirror-state": "^0.23.0" | ||
}, | ||
"devDependencies": { | ||
"buble": "^0.15.1", | ||
"ist": "^1.0.0", | ||
"mocha": "^3.0.2", | ||
"rimraf": "^2.5.4", | ||
"prosemirror-test-builder": "^0.22.0" | ||
"prosemirror-test-builder": "^0.23.0", | ||
"rollup": "^0.49.0", | ||
"rollup-plugin-buble": "^0.15.0" | ||
}, | ||
"scripts": { | ||
"test": "mocha test/test-*.js", | ||
"build": "rimraf dist && buble -i src -o dist", | ||
"prepublish": "npm run build" | ||
"build": "rollup -c", | ||
"watch": "rollup -c -w", | ||
"prepare": "npm run build" | ||
} | ||
} |
@@ -9,3 +9,3 @@ # prosemirror-commands | ||
This [module](http://prosemirror.net/ref.html#commands) implements a | ||
This [module](http://prosemirror.net/docs/ref/#commands) implements a | ||
number of editing commands, which are functions that abstract editing | ||
@@ -15,9 +15,5 @@ actions which can be bound to keys. | ||
The [project page](http://prosemirror.net) has more information, a | ||
number of [demos](http://prosemirror.net/#demos) and the | ||
[documentation](http://prosemirror.net/docs.html). | ||
number of [examples](http://prosemirror.net/examples/) and the | ||
[documentation](http://prosemirror.net/docs/). | ||
**NOTE:** This project is in *BETA* stage. It isn't thoroughly tested, | ||
and the API might still change across `0.x` releases. You are welcome | ||
to use it, but don't expect it to be very stable yet. | ||
This code is released under an | ||
@@ -24,0 +20,0 @@ [MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE). |
@@ -1,8 +0,8 @@ | ||
const {joinPoint, canJoin, findWrapping, liftTarget, canSplit, ReplaceAroundStep} = require("prosemirror-transform") | ||
const {Slice, Fragment} = require("prosemirror-model") | ||
const {Selection, TextSelection, NodeSelection, AllSelection} = require("prosemirror-state") | ||
import {joinPoint, canJoin, findWrapping, liftTarget, canSplit, ReplaceAroundStep} from "prosemirror-transform" | ||
import {Slice, Fragment} from "prosemirror-model" | ||
import {Selection, TextSelection, NodeSelection, AllSelection} from "prosemirror-state" | ||
// :: (EditorState, ?(tr: Transaction)) → bool | ||
// Delete the selection, if there is one. | ||
function deleteSelection(state, dispatch) { | ||
export function deleteSelection(state, dispatch) { | ||
if (state.selection.empty) return false | ||
@@ -12,12 +12,12 @@ if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView()) | ||
} | ||
exports.deleteSelection = deleteSelection | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// If the selection is empty and at the start of a textblock, move | ||
// that block closer to the block before it, by lifting it out of its | ||
// parent or, if it has no parent it doesn't share with the node | ||
// before it, moving it into a parent of that node, or joining it with | ||
// that. Will use the view for accurate start-of-textblock detection | ||
// if given. | ||
function joinBackward(state, dispatch, view) { | ||
// 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. | ||
export function joinBackward(state, dispatch, view) { | ||
let {$cursor} = state.selection | ||
@@ -28,15 +28,6 @@ if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
// Find the node before this one | ||
let before, cut, cutDepth | ||
if (!$cursor.parent.type.spec.isolating) for (let i = $cursor.depth - 1; !before && i >= 0; i--) { | ||
if ($cursor.index(i) > 0) { | ||
cut = $cursor.before(i + 1) | ||
before = $cursor.node(i).child($cursor.index(i) - 1) | ||
cutDepth = i | ||
} | ||
if ($cursor.node(i).type.spec.isolating) break | ||
} | ||
let $cut = findCutBefore($cursor) | ||
// If there is no node before this, try to lift | ||
if (!before) { | ||
if (!$cut) { | ||
let range = $cursor.blockRange(), target = range && liftTarget(range) | ||
@@ -48,8 +39,15 @@ if (target == null) return false | ||
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 (before.isAtom && NodeSelection.isSelectable(before) && $cursor.parent.content.size == 0) { | ||
if ($cursor.parent.content.size == 0 && | ||
(textblockAt(before, "end") || NodeSelection.isSelectable(before))) { | ||
if (dispatch) { | ||
let tr = state.tr.delete(cut, cut + $cursor.parent.nodeSize) | ||
tr.setSelection(NodeSelection.create(tr.doc, cut - before.nodeSize)) | ||
let tr = state.tr.deleteRange($cursor.before(), $cursor.after()) | ||
tr.setSelection(textblockAt(before, "end") ? Selection.findFrom($cut, -1) | ||
: NodeSelection.create(tr.doc, $cut.pos - before.nodeSize)) | ||
dispatch(tr.scrollIntoView()) | ||
@@ -60,22 +58,52 @@ } | ||
// If the node doesn't allow children, delete it | ||
if (before.isLeaf && cutDepth == $cursor.depth - 1) { | ||
if (dispatch) dispatch(state.tr.delete(cut - before.nodeSize, cut).scrollIntoView()) | ||
// 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 | ||
} | ||
// Apply the joining algorithm | ||
return !before.type.spec.isolating && deleteBarrier(state, cut, dispatch) || | ||
selectNextNode(state, cut, -1, dispatch) | ||
return false | ||
} | ||
exports.joinBackward = joinBackward | ||
function textblockAt(node, side) { | ||
for (; node; node = (side == "start" ? node.firstChild : node.lastChild)) | ||
if (node.isTextblock) return true | ||
return false | ||
} | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// 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`](#commands.joinBackward) or other deleting | ||
// commands, as a fall-back behavior when the schema doesn't allow | ||
// deletion at the selected point. | ||
export function selectNodeBackward(state, dispatch, view) { | ||
let {$cursor} = state.selection | ||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
: $cursor.parentOffset > 0)) | ||
return false | ||
let $cut = findCutBefore($cursor), 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 | ||
} | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// If the selection is empty and the cursor is at the end of a | ||
// textblock, move the node after it closer to the node with the | ||
// cursor (lifting it out of parents that aren't shared, moving it | ||
// into parents of the cursor block, or joining the two when they are | ||
// siblings). Will use the view for accurate start-of-textblock | ||
// detection if given. | ||
function joinForward(state, dispatch, view) { | ||
// 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. | ||
export function joinForward(state, dispatch, view) { | ||
let {$cursor} = state.selection | ||
@@ -86,27 +114,62 @@ if (!$cursor || (view ? !view.endOfTextblock("forward", state) | ||
// Find the node after this one | ||
let after, cut, cutDepth | ||
if (!$cursor.parent.type.spec.isolating) for (let i = $cursor.depth - 1; !after && i >= 0; i--) { | ||
let parent = $cursor.node(i) | ||
if ($cursor.index(i) + 1 < parent.childCount) { | ||
after = parent.child($cursor.index(i) + 1) | ||
cut = $cursor.after(i + 1) | ||
cutDepth = i | ||
} | ||
if (parent.type.spec.isolating) break | ||
} | ||
let $cut = findCutAfter($cursor) | ||
// If there is no node after this, there's nothing to do | ||
if (!after) return false | ||
if (!$cut) return false | ||
// If the node doesn't allow children, delete it | ||
if (after.isLeaf && cutDepth == $cursor.depth - 1) { | ||
if (dispatch) dispatch(state.tr.delete(cut, cut + after.nodeSize).scrollIntoView()) | ||
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))) { | ||
if (dispatch) { | ||
let tr = state.tr.deleteRange($cursor.before(), $cursor.after()) | ||
tr.setSelection(textblockAt(after, "start") ? Selection.findFrom($cut, 1) | ||
: NodeSelection.create(tr.doc, tr.mapping.map($cut.pos))) | ||
dispatch(tr.scrollIntoView()) | ||
} | ||
return true | ||
} | ||
// Apply the joining algorithm | ||
return deleteBarrier(state, cut, dispatch) || selectNextNode(state, cut, 1, dispatch) | ||
// 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 | ||
} | ||
exports.joinForward = joinForward | ||
// :: (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// 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`](#commands.joinForward) and similar deleting | ||
// commands, to provide a fall-back behavior when the schema doesn't | ||
// allow deletion at the selected point. | ||
export function selectNodeForward(state, dispatch, view) { | ||
let {$cursor} = state.selection | ||
if (!$cursor || (view ? !view.endOfTextblock("forward", state) | ||
: $cursor.parentOffset < $cursor.parent.content.size)) | ||
return false | ||
let $cut = findCutAfter($cursor), 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 | ||
} | ||
// :: (EditorState, ?(tr: Transaction)) → bool | ||
@@ -116,3 +179,3 @@ // Join the selected block or, if there is a text selection, the | ||
// the sibling above it. | ||
function joinUp(state, dispatch) { | ||
export function joinUp(state, dispatch) { | ||
let sel = state.selection, nodeSel = sel instanceof NodeSelection, point | ||
@@ -133,3 +196,2 @@ if (nodeSel) { | ||
} | ||
exports.joinUp = joinUp | ||
@@ -139,3 +201,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// that can be joined, with the sibling after it. | ||
function joinDown(state, dispatch) { | ||
export function joinDown(state, dispatch) { | ||
let sel = state.selection, point | ||
@@ -153,3 +215,2 @@ if (sel instanceof NodeSelection) { | ||
} | ||
exports.joinDown = joinDown | ||
@@ -159,3 +220,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// selection that can be lifted, out of its parent node. | ||
function lift(state, dispatch) { | ||
export function lift(state, dispatch) { | ||
let {$from, $to} = state.selection | ||
@@ -167,3 +228,2 @@ let range = $from.blockRange($to), target = range && liftTarget(range) | ||
} | ||
exports.lift = lift | ||
@@ -174,3 +234,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// selection with a newline character. | ||
function newlineInCode(state, dispatch) { | ||
export function newlineInCode(state, dispatch) { | ||
let {$head, $anchor} = state.selection | ||
@@ -181,3 +241,2 @@ if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false | ||
} | ||
exports.newlineInCode = newlineInCode | ||
@@ -188,3 +247,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// default block after the code block, and move the cursor there. | ||
function exitCode(state, dispatch) { | ||
export function exitCode(state, dispatch) { | ||
let {$head, $anchor} = state.selection | ||
@@ -201,3 +260,2 @@ if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false | ||
} | ||
exports.exitCode = exitCode | ||
@@ -207,3 +265,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// it is its parent's first child) or after it. | ||
function createParagraphNear(state, dispatch) { | ||
export function createParagraphNear(state, dispatch) { | ||
let {$from, $to} = state.selection | ||
@@ -221,3 +279,2 @@ if ($from.parent.inlineContent || $to.parent.inlineContent) return false | ||
} | ||
exports.createParagraphNear = createParagraphNear | ||
@@ -227,3 +284,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// block. | ||
function liftEmptyBlock(state, dispatch) { | ||
export function liftEmptyBlock(state, dispatch) { | ||
let {$cursor} = state.selection | ||
@@ -243,3 +300,2 @@ if (!$cursor || $cursor.parent.content.size) return false | ||
} | ||
exports.liftEmptyBlock = liftEmptyBlock | ||
@@ -249,3 +305,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// selection, also delete its content. | ||
function splitBlock(state, dispatch) { | ||
export function splitBlock(state, dispatch) { | ||
let {$from, $to} = state.selection | ||
@@ -279,3 +335,2 @@ if (state.selection instanceof NodeSelection && state.selection.node.isBlock) { | ||
} | ||
exports.splitBlock = splitBlock | ||
@@ -285,3 +340,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// resetting the set of active marks at the cursor. | ||
function splitBlockKeepMarks(state, dispatch) { | ||
export function splitBlockKeepMarks(state, dispatch) { | ||
return splitBlock(state, dispatch && (tr => { | ||
@@ -293,3 +348,2 @@ let marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()) | ||
} | ||
exports.splitBlockKeepMarks = splitBlockKeepMarks | ||
@@ -299,3 +353,3 @@ // :: (EditorState, ?(tr: Transaction)) → bool | ||
// any. (Will not select the document node.) | ||
function selectParentNode(state, dispatch) { | ||
export function selectParentNode(state, dispatch) { | ||
let {$from, to} = state.selection, pos | ||
@@ -308,11 +362,9 @@ let same = $from.sharedDepth(to) | ||
} | ||
exports.selectParentNode = selectParentNode | ||
// :: (EditorState, ?(tr: Transaction)) → bool | ||
// Select the whole document. | ||
function selectAll(state, dispatch) { | ||
export function selectAll(state, dispatch) { | ||
if (dispatch) dispatch(state.tr.setSelection(new AllSelection(state.doc))) | ||
return true | ||
} | ||
exports.selectAll = selectAll | ||
@@ -330,3 +382,3 @@ function joinMaybeClear(state, $pos, dispatch) { | ||
dispatch(state.tr | ||
.clearNonMatching($pos.pos, before.contentMatchAt(before.childCount)) | ||
.clearIncompatible($pos.pos, before.type, before.contentMatchAt(before.childCount)) | ||
.join($pos.pos) | ||
@@ -337,15 +389,15 @@ .scrollIntoView()) | ||
function deleteBarrier(state, cut, dispatch) { | ||
let $cut = state.doc.resolve(cut), before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match | ||
function deleteBarrier(state, $cut, dispatch) { | ||
let before = $cut.nodeBefore, after = $cut.nodeAfter, conn, match | ||
if (joinMaybeClear(state, $cut, dispatch)) return true | ||
if ($cut.parent.canReplace($cut.index(), $cut.index() + 1) && | ||
(conn = (match = before.contentMatchAt(before.childCount)).findWrappingFor(after))&& | ||
match.matchType((conn[0] || after).type, (conn[0] || after).attrs).validEnd()) { | ||
(conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) && | ||
match.matchType(conn[0] || after.type).validEnd) { | ||
if (dispatch) { | ||
let end = cut + after.nodeSize, wrap = Fragment.empty | ||
let end = $cut.pos + after.nodeSize, wrap = Fragment.empty | ||
for (let i = conn.length - 1; i >= 0; i--) | ||
wrap = Fragment.from(conn[i].type.create(conn[i].attrs, wrap)) | ||
wrap = Fragment.from(conn[i].create(null, wrap)) | ||
wrap = Fragment.from(before.copy(wrap)) | ||
let tr = state.tr.step(new ReplaceAroundStep(cut - 1, end, cut, end, new Slice(wrap, 1, 0), conn.length, true)) | ||
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 | ||
@@ -368,11 +420,2 @@ if (canJoin(tr.doc, joinAt)) tr.join(joinAt) | ||
function selectNextNode(state, cut, dir, dispatch) { | ||
let $cut = state.doc.resolve(cut) | ||
let node = dir > 0 ? $cut.nodeAfter : $cut.nodeBefore | ||
if (!node || !NodeSelection.isSelectable(node)) return false | ||
if (dispatch) | ||
dispatch(state.tr.setSelection(NodeSelection.create(state.doc, cut - (dir > 0 ? 0 : node.nodeSize))).scrollIntoView()) | ||
return true | ||
} | ||
// Parameterized commands | ||
@@ -383,3 +426,3 @@ | ||
// attributes. | ||
function wrapIn(nodeType, attrs) { | ||
export function wrapIn(nodeType, attrs) { | ||
return function(state, dispatch) { | ||
@@ -393,3 +436,2 @@ let {$from, $to} = state.selection | ||
} | ||
exports.wrapIn = wrapIn | ||
@@ -399,3 +441,3 @@ // :: (NodeType, ?Object) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool | ||
// selection to the given node type with the given attributes. | ||
function setBlockType(nodeType, attrs) { | ||
export function setBlockType(nodeType, attrs) { | ||
return function(state, dispatch) { | ||
@@ -417,3 +459,3 @@ let {$from, $to} = state.selection, depth, target | ||
dispatch(state.tr | ||
.clearNonMatching(where, nodeType.contentExpr.start(attrs)) | ||
.clearIncompatible(where, nodeType) | ||
.setNodeType(where, nodeType, attrs) | ||
@@ -425,3 +467,2 @@ .scrollIntoView()) | ||
} | ||
exports.setBlockType = setBlockType | ||
@@ -431,6 +472,6 @@ function markApplies(doc, ranges, type) { | ||
let {$from, $to} = ranges[i] | ||
let can = $from.depth == 0 ? doc.contentMatchAt(0).allowsMark(type) : false | ||
let can = $from.depth == 0 ? doc.type.allowsMarkType(type) : false | ||
doc.nodesBetween($from.pos, $to.pos, node => { | ||
if (can) return false | ||
can = node.inlineContent && node.contentMatchAt(0).allowsMark(type) | ||
can = node.inlineContent && node.type.allowsMarkType(type) | ||
}) | ||
@@ -450,3 +491,3 @@ if (can) return true | ||
// document. | ||
function toggleMark(markType, attrs) { | ||
export function toggleMark(markType, attrs) { | ||
return function(state, dispatch) { | ||
@@ -478,3 +519,2 @@ let {empty, $cursor, ranges} = state.selection | ||
} | ||
exports.toggleMark = toggleMark | ||
@@ -526,3 +566,3 @@ function wrapDispatchForJoin(dispatch, isJoinable) { | ||
// array. | ||
function autoJoin(command, isJoinable) { | ||
export function autoJoin(command, isJoinable) { | ||
if (Array.isArray(isJoinable)) { | ||
@@ -534,8 +574,7 @@ let types = isJoinable | ||
} | ||
exports.autoJoin = autoJoin | ||
// :: (...[(EditorState, ?(tr: Transaction)) → bool]) → (EditorState, ?(tr: Transaction)) → bool | ||
// :: (...[(EditorState, ?(tr: Transaction), ?EditorView) → bool]) → (EditorState, ?(tr: Transaction), ?EditorView) → bool | ||
// Combine a number of command functions into a single function (which | ||
// calls them one by one until one returns true). | ||
function chainCommands(...commands) { | ||
export function chainCommands(...commands) { | ||
return function(state, dispatch, view) { | ||
@@ -547,32 +586,24 @@ for (let i = 0; i < commands.length; i++) | ||
} | ||
exports.chainCommands = chainCommands | ||
let backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward) | ||
let del = chainCommands(deleteSelection, joinForward, selectNodeForward) | ||
// :: Object | ||
// A basic keymap containing bindings not specific to any schema. | ||
// Binds the following keys (when multiple commands are listed, they | ||
// are chained with [`chainCommands`](#commands.chainCommands): | ||
// are chained with [`chainCommands`](#commands.chainCommands)): | ||
// | ||
// * **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock` | ||
// * **Mod-Enter** to `exitCode` | ||
// * **Backspace** to `deleteSelection`, `joinBackward` | ||
// * **Mod-Backspace** to `deleteSelection`, `joinBackward` | ||
// * **Delete** to `deleteSelection`, `joinForward` | ||
// * **Mod-Delete** to `deleteSelection`, `joinForward` | ||
// * **Alt-ArrowUp** to `joinUp` | ||
// * **Alt-ArrowDown** to `joinDown` | ||
// * **Mod-BracketLeft** to `lift` | ||
// * **Escape** to `selectParentNode` | ||
let baseKeymap = { | ||
// * **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` | ||
export let baseKeymap = { | ||
"Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock), | ||
"Mod-Enter": exitCode, | ||
"Backspace": chainCommands(deleteSelection, joinBackward), | ||
"Mod-Backspace": chainCommands(deleteSelection, joinBackward), | ||
"Delete": chainCommands(deleteSelection, joinForward), | ||
"Mod-Delete": chainCommands(deleteSelection, joinForward), | ||
"Alt-ArrowUp": joinUp, | ||
"Alt-ArrowDown": joinDown, | ||
"Mod-BracketLeft": lift, | ||
"Escape": selectParentNode, | ||
"Backspace": backspace, | ||
"Mod-Backspace": backspace, | ||
"Delete": del, | ||
"Mod-Delete": del, | ||
"Mod-a": selectAll | ||
@@ -596,3 +627,1 @@ } | ||
} | ||
exports.baseKeymap = baseKeymap |
@@ -1,10 +0,10 @@ | ||
This module exports a number of ‘commands‘, which are building block | ||
This module exports a number of _commands_, which are building block | ||
functions that encapsulate an editing action. A command function takes | ||
an editor state and _optionally_ an `onAction` function that it can | ||
use to take an action. It should return a boolean that indicates | ||
whether it could perform any action. When no `onAction` callback is | ||
an editor state and _optionally_ a `dispatch` function that it can use | ||
to dispatch a transaction. It should return a boolean that indicates | ||
whether it could perform any action. When no `dispatch` callback is | ||
passed, the command should do a 'dry run', determining whether it is | ||
applicable, but not actually doing anything. | ||
These are mostly used to bind keys to, and to define menu items. | ||
These are mostly used to bind keys and define menu items. | ||
@@ -14,3 +14,5 @@ @chainCommands | ||
@joinBackward | ||
@selectNodeBackward | ||
@joinForward | ||
@selectNodeForward | ||
@joinUp | ||
@@ -17,0 +19,0 @@ @joinDown |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
104361
10
1138
23
+ Addedprosemirror-model@0.23.1(transitive)
+ Addedprosemirror-state@0.23.0(transitive)
+ Addedprosemirror-transform@0.23.0(transitive)
- Removedprosemirror-model@0.22.0(transitive)
- Removedprosemirror-state@0.22.0(transitive)
- Removedprosemirror-transform@0.22.2(transitive)
Updatedprosemirror-model@^0.23.0
Updatedprosemirror-state@^0.23.0