prosemirror-commands
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -0,1 +1,11 @@ | ||
## 1.4.0 (2022-12-01) | ||
### Bug fixes | ||
Make `setBlockType` act on all selection ranges in selections that have them. | ||
### New features | ||
The new `joinTextblockForward` and `joinTextblockBackward` commands provide a more primitive command for delete/backspace behavior when you don't want the extra strategies implemented by `joinForward`/`joinBackward`. | ||
## 1.3.1 (2022-09-08) | ||
@@ -2,0 +12,0 @@ |
@@ -19,2 +19,14 @@ import { NodeType, Attrs, MarkType, Node } from 'prosemirror-model'; | ||
/** | ||
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 | ||
@@ -177,2 +189,2 @@ the node before that textblock, if possible. This is intended to be | ||
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockKeepMarks, toggleMark, wrapIn }; | ||
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, splitBlockKeepMarks, toggleMark, wrapIn }; |
import { liftTarget, replaceStep, canJoin, joinPoint, canSplit, ReplaceAroundStep, findWrapping } from 'prosemirror-transform'; | ||
import { Slice, Fragment } from 'prosemirror-model'; | ||
import { NodeSelection, Selection, AllSelection, TextSelection } from 'prosemirror-state'; | ||
import { NodeSelection, Selection, TextSelection, AllSelection } from 'prosemirror-state'; | ||
@@ -15,2 +15,9 @@ /** | ||
}; | ||
function atBlockStart(state, view) { | ||
let { $cursor } = state.selection; | ||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
: $cursor.parentOffset > 0)) | ||
return null; | ||
return $cursor; | ||
} | ||
/** | ||
@@ -26,5 +33,4 @@ If the selection is empty and at the start of a textblock, try to | ||
const joinBackward = (state, dispatch, view) => { | ||
let { $cursor } = state.selection; | ||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
: $cursor.parentOffset > 0)) | ||
let $cursor = atBlockStart(state, view); | ||
if (!$cursor) | ||
return false; | ||
@@ -68,2 +74,55 @@ let $cut = findCutBefore($cursor); | ||
}; | ||
/** | ||
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.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) { | ||
@@ -112,2 +171,9 @@ for (let scan = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) { | ||
} | ||
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; | ||
} | ||
/** | ||
@@ -121,5 +187,4 @@ If the selection is empty and the cursor is at the end of a | ||
const joinForward = (state, dispatch, view) => { | ||
let { $cursor } = state.selection; | ||
if (!$cursor || (view ? !view.endOfTextblock("forward", state) | ||
: $cursor.parentOffset < $cursor.parent.content.size)) | ||
let $cursor = atBlockEnd(state, view); | ||
if (!$cursor) | ||
return false; | ||
@@ -524,21 +589,29 @@ let $cut = findCutAfter($cursor); | ||
return function (state, dispatch) { | ||
let { from, to } = state.selection; | ||
let applicable = false; | ||
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); | ||
} | ||
}); | ||
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) | ||
dispatch(state.tr.setBlockType(from, to, nodeType, attrs).scrollIntoView()); | ||
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; | ||
@@ -725,2 +798,2 @@ }; | ||
export { autoJoin, baseKeymap, chainCommands, createParagraphNear, deleteSelection, exitCode, joinBackward, joinDown, joinForward, joinUp, lift, liftEmptyBlock, macBaseKeymap, newlineInCode, pcBaseKeymap, selectAll, selectNodeBackward, selectNodeForward, selectParentNode, selectTextblockEnd, selectTextblockStart, setBlockType, splitBlock, splitBlockKeepMarks, toggleMark, wrapIn }; | ||
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, splitBlockKeepMarks, toggleMark, wrapIn }; |
{ | ||
"name": "prosemirror-commands", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "Editing commands for ProseMirror", | ||
@@ -5,0 +5,0 @@ "type": "module", |
# prosemirror-commands | ||
[ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-commands/blob/master/CHANGELOG.md) ] | ||
[ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-commands/blob/master/CHANGELOG.md) ] | ||
@@ -5,0 +5,0 @@ This is a [core module](https://prosemirror.net/docs/ref/#commands) of [ProseMirror](https://prosemirror.net). |
@@ -6,2 +6,3 @@ import {joinPoint, canJoin, findWrapping, liftTarget, canSplit, | ||
SelectionRange, AllSelection, Command} from "prosemirror-state" | ||
import {EditorView} from "prosemirror-view" | ||
@@ -15,2 +16,10 @@ /// Delete the selection, if there is one. | ||
function atBlockStart(state: EditorState, view?: EditorView): ResolvedPos | null { | ||
let {$cursor} = state.selection as TextSelection | ||
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 | ||
@@ -24,6 +33,4 @@ /// reduce the distance between that block and the one before it—if | ||
export const joinBackward: Command = (state, dispatch, view) => { | ||
let {$cursor} = state.selection as TextSelection | ||
if (!$cursor || (view ? !view.endOfTextblock("backward", state) | ||
: $cursor.parentOffset > 0)) | ||
return false | ||
let $cursor = atBlockStart(state, view) | ||
if (!$cursor) return false | ||
@@ -70,2 +77,48 @@ let $cut = findCutBefore($cursor) | ||
/// 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. | ||
export const joinTextblockBackward: Command = (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. | ||
export const joinTextblockForward: Command = (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: EditorState, $cut: ResolvedPos, dispatch?: (tr: Transaction) => void) { | ||
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) as ReplaceStep | null | ||
if (!step || step.from != beforePos || 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: Node, side: "start" | "end", only = false) { | ||
@@ -108,2 +161,10 @@ for (let scan: Node | null = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) { | ||
function atBlockEnd(state: EditorState, view?: EditorView): ResolvedPos | null { | ||
let {$cursor} = state.selection as TextSelection | ||
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 | ||
@@ -115,9 +176,6 @@ /// textblock, try to reduce or remove the boundary between that block | ||
export const joinForward: Command = (state, dispatch, view) => { | ||
let {$cursor} = state.selection as TextSelection | ||
if (!$cursor || (view ? !view.endOfTextblock("forward", state) | ||
: $cursor.parentOffset < $cursor.parent.content.size)) | ||
return false | ||
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 | ||
@@ -473,16 +531,25 @@ if (!$cut) return false | ||
return function(state, dispatch) { | ||
let {from, to} = state.selection | ||
let applicable = false | ||
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) | ||
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) | ||
} | ||
}) | ||
if (!applicable) return false | ||
if (dispatch) dispatch(state.tr.setBlockType(from, to, nodeType, attrs).scrollIntoView()) | ||
dispatch(tr.scrollIntoView()) | ||
} | ||
return true | ||
@@ -489,0 +556,0 @@ } |
@@ -16,4 +16,6 @@ This module exports a number of _commands_, which are building block | ||
@selectNodeBackward | ||
@joinTextblockBackward | ||
@joinForward | ||
@selectNodeForward | ||
@joinTextblockForward | ||
@joinUp | ||
@@ -20,0 +22,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
112967
2278