slate#SplitNodeOperation TypeScript Examples
The following examples show how to use
slate#SplitNodeOperation.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From slate-ot with MIT License | 6 votes |
makeOp = {
insertText: (
path: Path,
offset: number,
text: string
): InsertTextOperation => {
return {
type: 'insert_text',
path,
offset,
text,
};
},
removeText: (
path: Path,
offset: number,
text: string
): RemoveTextOperation => {
return {
type: 'remove_text',
path,
offset,
text,
};
},
insertNode: (path: Path, node: Node): InsertNodeOperation => {
return {
type: 'insert_node',
path,
node,
};
},
removeNode: (path: Path, node: Node): RemoveNodeOperation => {
return {
type: 'remove_node',
path,
node,
};
},
splitNode: (path: Path, position: number): SplitNodeOperation => {
return {
type: 'split_node',
path,
position,
target: null,
properties: {},
};
},
mergeNode: (path: Path, position: number): MergeNodeOperation => {
return {
type: 'merge_node',
path,
position,
target: null,
properties: {},
};
},
moveNode: (path: Path, newPath: Path): MoveNodeOperation => {
return {
type: 'move_node',
path,
newPath,
};
},
setNode: (path: Path, newProperties: Partial<Node>): SetNodeOperation => {
return {
type: 'set_node',
path,
properties: {},
newProperties,
};
},
}
Example #2
Source File: transInsertNode.ts From slate-ot with MIT License | 4 votes |
transInsertNode = (
leftOp: InsertNodeOperation,
rightOp: Operation,
side: 'left' | 'right'
): (InsertNodeOperation | SplitNodeOperation | MergeNodeOperation)[] => {
switch (rightOp.type) {
case 'insert_node': {
if (Path.equals(leftOp.path, rightOp.path) && side === 'left') {
return [leftOp];
}
return <InsertNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'remove_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
return [leftOp];
}
return <InsertNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'split_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
return [leftOp];
}
return <InsertNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'merge_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
const offset = Text.isText(leftOp.node)
? leftOp.node.text.length
: leftOp.node.children.length;
return [
{
...rightOp,
type: 'split_node',
path: Path.previous(rightOp.path),
},
leftOp,
rightOp,
{
...rightOp,
position: rightOp.position + offset,
},
];
}
return <InsertNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'move_node': {
if (Path.equals(rightOp.path, rightOp.newPath)) {
return [leftOp];
}
// the anchor node is moved, but we don't want to move with the anchor
// hence the next of anchor is chosen as the new anchor
if (Path.equals(leftOp.path, rightOp.path)) {
return [
{
...leftOp,
path: Path.transform(Path.next(leftOp.path), rightOp)!,
},
];
}
const [rr, ri] = decomposeMove(rightOp);
const [l] = xTransformMxN([leftOp], [rr, ri], side);
if (l.length == 1) return <InsertNodeOperation[]>l;
// l.length == 0, the parent node is moved
// in this case rr and ri will not be transformed by leftOp
return [
{
...leftOp,
path: ri.path.concat(leftOp.path.slice(rr.path.length)),
},
];
}
// insert_text
// remove_text
// set_node
default:
return <InsertNodeOperation[]>pathTransform(leftOp, rightOp);
}
}
Example #3
Source File: transMoveNode.ts From slate-ot with MIT License | 4 votes |
transMoveNode = (
leftOp: MoveNodeOperation,
rightOp: Operation,
side: 'left' | 'right'
): (MoveNodeOperation | RemoveNodeOperation | SplitNodeOperation)[] => {
if (Path.equals(leftOp.path, leftOp.newPath)) {
return [];
}
let [lr, li] = decomposeMove(leftOp);
switch (rightOp.type) {
case 'insert_node': {
let [l] = xTransformMxN([lr, li], [rightOp], side);
return [
composeMove(<RemoveNodeOperation>l[0], <InsertNodeOperation>l[1]),
];
}
case 'remove_node': {
let [l] = xTransformMxN([lr, li], [rightOp], side);
// normal case
if (l.length === 2) {
return [
{
...leftOp,
...composeMove(
<RemoveNodeOperation>l[0],
<InsertNodeOperation>l[1]
),
},
];
}
// leftOp moves a branch into the removed zone
else if (l.length === 1 && l[0].type === 'remove_node') {
return [l[0]];
}
// leftOp moves a branch out of the removed zone
// we choose NOT to keep it
else if (l.length === 1 && l[0].type === 'insert_node') {
return [];
}
// l.length === 0, move within the removed zone
else {
return [];
}
}
case 'split_node': {
const after: boolean =
Path.isSibling(leftOp.path, leftOp.newPath) &&
Path.endsBefore(leftOp.path, leftOp.newPath);
// the split nodes have to move separately
if (Path.equals(leftOp.path, rightOp.path)) {
const newPath = Path.transform(leftOp.newPath, rightOp)!;
// the split nodes are moved AFTER newPath
if (after) {
return [
{
...leftOp, // move first node
newPath,
},
{
...leftOp, // move second node
newPath,
},
];
}
// the split nodes are moved BEFORE newPath
else {
const firstMove: MoveNodeOperation = {
...leftOp, // move second node
path: Path.next(leftOp.path),
newPath,
};
const secondMove: MoveNodeOperation = {
...leftOp, // move first node
path: Path.transform(leftOp.path, firstMove)!,
newPath: Path.previous(Path.transform(newPath, firstMove)!),
};
return [firstMove, secondMove];
}
}
let newPath = Path.transform(leftOp.newPath, rightOp)!;
// the newPath is between the split nodes
// note that it is impossible for newPath == rightOp.path
if (Path.equals(newPath, Path.next(rightOp.path)) && !after) {
newPath = rightOp.path;
}
// a tricky case:
// when after is true, and the splitOp separated path and newPath
// to no-longer be siblings, the after becomes false
// in this case we should move one step after
else if (after && !Path.isSibling(leftOp.path, newPath)) {
newPath = Path.next(newPath);
}
// finally, the normal case
return [
{
...leftOp,
path: Path.transform(leftOp.path, rightOp)!,
newPath,
},
];
}
case 'merge_node': {
let path = rightOp.path;
let prevPath = Path.previous(path);
path = Path.transform(path, leftOp)!;
prevPath = Path.transform(prevPath, leftOp)!;
// ops conflict with each other, discard merge
// Note that the merge-and-split node cannot keep properties,
// so we have to remove it.
if (!Path.equals(path, Path.next(prevPath))) {
return [
{
...rightOp,
type: 'split_node',
path: Path.previous(rightOp.path),
},
leftOp,
{
type: 'remove_node',
path,
node: { text: '' },
},
];
}
// a tricky case:
// if leftOp.path is a child of rightOp.prevPath,
// and leftOp.newPath is a child of rightOp.path.
// intentionally, leftOp wants to insert BEFORE leftOp.newPath;
// but after merging, leftOp's path and newPath become siblings.
// the move dst turns out to be AFTER the transformed newPath.
// therefore we should move one step ahead
if (
Path.isParent(Path.previous(rightOp.path), leftOp.path) &&
Path.isParent(rightOp.path, leftOp.newPath)
) {
return [
{
...leftOp,
path: Path.transform(leftOp.path, rightOp)!,
newPath: Path.previous(Path.transform(leftOp.newPath, rightOp)!),
},
];
}
return [
{
...leftOp,
path: Path.transform(leftOp.path, rightOp)!,
newPath: Path.transform(leftOp.newPath, rightOp)!,
},
];
}
case 'move_node': {
// the other side didn't do anything
if (Path.equals(rightOp.path, rightOp.newPath)) {
return [leftOp];
}
let [rr, ri] = decomposeMove(rightOp);
let [l, r] = xTransformMxN([lr, li], [rr, ri], side);
// normal case
if (l.length === 2) {
return [
composeMove(<RemoveNodeOperation>l[0], <InsertNodeOperation>l[1]),
];
}
// handling conflict
if (r.length === 1) {
// must have l.length === 1 && l[0].type === r[0].type)
return side === 'left' ? [reverseMove(rr, ri), leftOp] : [];
}
// for the rest we have r.length === 2
if (l.length === 0) {
l[0] = {
type: 'remove_node',
path: ri.path.concat(lr.path.slice(rr.path.length)),
node: { text: '' },
};
l[1] = {
type: 'insert_node',
path: ri.path.concat(li.path.slice(rr.path.length)),
node: { text: '' },
};
}
// for the rest we have l.length === 1
else if (l[0].type === 'remove_node') {
l[1] = {
type: 'insert_node',
path: (<InsertNodeOperation>r[1]).path.concat(
li.path.slice((<RemoveNodeOperation>r[0]).path.length)
),
node: { text: '' },
};
}
// for the rest we have l[0].type === 'insert_node'
else {
l[1] = l[0];
l[0] = {
type: 'remove_node',
path: ri.path.concat(lr.path.slice(rr.path.length)),
node: { text: '' },
};
}
return [
composeMove(<RemoveNodeOperation>l[0], <InsertNodeOperation>l[1]),
];
}
// insert_text
// remove_text
// set_node
default:
return [leftOp];
}
}
Example #4
Source File: transRemoveNode.ts From slate-ot with MIT License | 4 votes |
transRemoveNode = (
leftOp: RemoveNodeOperation,
rightOp: Operation,
side: 'left' | 'right'
): (RemoveNodeOperation | SplitNodeOperation)[] => {
switch (rightOp.type) {
case 'split_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
// should remove the target node && the split node
// however, after removing the target node,
// the split node becomes the same path.
// TODO: node within op should be split.
return [leftOp, leftOp];
}
return <RemoveNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'merge_node': {
// One of the to-merge nodes are removed, we have to discard merging.
// It might be better to keep the unremoved node,
// but it is tricky to keep properties by merge-and-split,
// therefore we choose to remove the entired merged node
if (
Path.equals(leftOp.path, Path.previous(rightOp.path)) ||
Path.equals(leftOp.path, rightOp.path)
) {
return [
{
...leftOp,
path: Path.previous(rightOp.path),
},
];
}
return <RemoveNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'move_node': {
if (Path.equals(rightOp.path, rightOp.newPath)) {
return [leftOp];
}
let [rr, ri] = decomposeMove(rightOp);
let [l, r] = xTransformMxN([leftOp], [rr, ri], side);
// leftOp removes a node within the moved zone
// ri must have survived and not been changed
// we use ri's path to compute the true path to remove
if (l.length === 0) {
return [
{
...leftOp,
path: ri.path.concat(leftOp.path.slice(rr.path.length)),
},
];
}
// now we have l.length === 1
// in most cases we can return, but to be consist with move-remove
// we need to handle the case that rightOp moved a branch out
if (r.length === 1 && r[0].type === 'insert_node') {
l = [
...l,
{
type: 'remove_node',
path: r[0].path,
node: { text: '' },
},
];
}
return <RemoveNodeOperation[]>l;
}
// insert_text
// remove_text
// insert_node
// remove_node
// set_node
default:
return <RemoveNodeOperation[]>pathTransform(leftOp, rightOp);
}
}
Example #5
Source File: transSplitNode.ts From slate-ot with MIT License | 4 votes |
transSplitNode = (
leftOp: SplitNodeOperation,
rightOp: Operation,
_side: 'left' | 'right'
): SplitNodeOperation[] => {
switch (rightOp.type) {
case 'insert_text': {
if (!Path.equals(leftOp.path, rightOp.path)) {
return [leftOp];
}
if (leftOp.position < rightOp.offset) {
return [leftOp];
}
return [
{
...leftOp,
position: leftOp.position + rightOp.text.length,
},
];
}
case 'remove_text': {
if (!Path.equals(leftOp.path, rightOp.path)) {
return [leftOp];
}
if (leftOp.position < rightOp.offset) {
return [leftOp];
}
if (leftOp.position >= rightOp.offset + rightOp.text.length) {
return [
{
...leftOp,
position: leftOp.position - rightOp.text.length,
},
];
}
return [
{
...leftOp,
position: rightOp.offset,
},
];
}
case 'insert_node': {
if (Path.isParent(leftOp.path, rightOp.path)) {
let offset = rightOp.path[rightOp.path.length - 1];
if (leftOp.position <= offset) {
return [leftOp];
}
return [
{
...leftOp,
position: leftOp.position + 1,
},
];
}
return <SplitNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'remove_node': {
if (Path.isParent(leftOp.path, rightOp.path)) {
let offset = rightOp.path[rightOp.path.length - 1];
if (leftOp.position <= offset) {
return [leftOp];
}
return [
{
...leftOp,
position: leftOp.position - 1,
},
];
}
return <SplitNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'split_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
if (leftOp.position < rightOp.position) {
return [leftOp];
}
if (leftOp.position === rightOp.position) {
return [];
}
return [
{
...leftOp,
path: Path.next(leftOp.path),
position: leftOp.position - rightOp.position,
},
];
}
if (Path.isParent(leftOp.path, rightOp.path)) {
const offset = rightOp.path[rightOp.path.length - 1];
if (leftOp.position <= offset) {
return [leftOp];
}
return [
{
...leftOp,
position: leftOp.position + 1,
},
];
}
return <SplitNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'merge_node': {
if (Path.equals(leftOp.path, rightOp.path)) {
return [];
}
if (Path.isParent(leftOp.path, rightOp.path)) {
const offset = rightOp.path[rightOp.path.length - 1];
if (leftOp.position < offset) {
return [leftOp];
}
if (leftOp.position > offset) {
return [
{
...leftOp,
position: leftOp.position - 1,
},
];
}
// conflicting ops, discard split
return [];
}
return <SplitNodeOperation[]>pathTransform(leftOp, rightOp);
}
case 'move_node': {
if (Path.equals(rightOp.path, rightOp.newPath)) {
return [leftOp];
}
let position = leftOp.position;
let offset = rightOp.path[rightOp.path.length - 1];
let newOffset = rightOp.newPath[rightOp.newPath.length - 1];
// only src path is leftOp's child
if (
Path.isParent(leftOp.path, rightOp.path) &&
offset < leftOp.position
) {
position--;
}
// only dst path is leftOp's child
if (
Path.isParent(leftOp.path, rightOp.newPath) &&
newOffset < leftOp.position
) {
position++;
}
return [
{
...leftOp,
path: Path.transform(leftOp.path, rightOp)!,
position,
},
];
}
// set_node
default:
return <SplitNodeOperation[]>pathTransform(leftOp, rightOp);
}
}