Compare commits

...

6 Commits

Author SHA1 Message Date
01zulfi
fc8ea74d39 editor: improve move node logic
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-01 11:17:26 +05:00
01zulfi
2955c7c65c editor: refactor moveNode fn && add a test case
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-28 15:16:38 +05:00
01zulfi
d843b9f901 editor: add move parent node up/down keyboard shortcut
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-27 15:45:48 +05:00
Abdullah Atta
3b7621d237 editor: add missing changes 2025-11-27 14:08:14 +05:00
Abdullah Atta
cdabdfa640 docs: update keyboard shortcuts 2025-11-27 12:15:32 +05:00
01zulfi
dbac283928 editor: add move line up/down keybinding
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-27 12:12:56 +05:00
5 changed files with 521 additions and 64 deletions

View File

@@ -9,71 +9,75 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
### General
| Description | Web | Windows/Linux | Mac |
| -------------------------------------------------- | ------ | ------------- | --- |
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
| Settings | Ctrl , | Ctrl , | ⌘ , |
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
| New note | - | Ctrl N | ⌘ N |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
| Settings | Ctrl , | Ctrl , | ⌘ , |
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
| New note | - | Ctrl N | ⌘ N |
### Navigation
| Description | Web | Windows/Linux | Mac |
| ---------------- | ------------------------- | ------------- | ------- |
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
| Quick open | Ctrl P | Ctrl P | ⌘ P |
| New tab | - | Ctrl T | ⌘ T |
| Close active tab | - | Ctrl W | ⌘ W |
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
| Quick open | Ctrl P | Ctrl P | ⌘ P |
| New tab | - | Ctrl T | ⌘ T |
| Close active tab | - | Ctrl W | ⌘ W |
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
### Editor
| Description | Web | Windows/Linux | Mac |
| ---------------------------------- | ----------------- | ----------------- | ----------- |
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
| Split list item | ↵ | ↵ | ↵ |
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
| Insert date | Alt D | Alt D | ⌥ D |
| Insert time | Alt T | Alt T | ⌥ T |
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
| Insert paragraph | Ctrl ⇧ 0 | Ctrl ⇧ 0 | ⌘ 0 |
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
| Insert link | Ctrl K | Ctrl K | ⌘ K |
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
| Open search | Ctrl F | Ctrl F | ⌘ F |
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
| Underline | Ctrl U | Ctrl U | ⌘ U |
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
| Split list item | ↵ | ↵ | ↵ |
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
| Insert date | Alt D | Alt D | ⌥ D |
| Insert time | Alt T | Alt T | ⌥ T |
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
| Insert paragraph | Ctrl Alt 0 | Ctrl Alt 0 | ⌘ 0 |
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
| Insert link | Ctrl K | Ctrl K | ⌘ K |
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
| Open search | Ctrl F | Ctrl F | ⌘ F |
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
| Underline | Ctrl U | Ctrl U | ⌘ U |
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
| Move line up | Alt ↑ | Alt ↑ | ⌥ ↑ |
| Move line down | Alt ↓ | Alt ↓ | ⌥ ↓ |
| Move parent node up | Alt ⇧ ↑ | Alt ⇧ ↑ | ⌥ ⇧ ↑ |
| Move parent node down | Alt ⇧ ↓ | Alt ⇧ ↓ | ⌥ ⇧ ↓ |

View File

@@ -394,6 +394,30 @@ export const tiptapKeys = {
description: "Toggle highlight",
category: "Editor",
type: "tiptap"
},
moveLineUp: {
keys: "Alt-ArrowUp",
description: "Move line up",
category: "Editor",
type: "tiptap"
},
moveLineDown: {
keys: "Alt-ArrowDown",
description: "Move line down",
category: "Editor",
type: "tiptap"
},
moveNodeUp: {
keys: "Alt-Shift-ArrowUp",
description: "Move parent node up",
category: "Editor",
type: "tiptap"
},
moveNodeDown: {
keys: "Alt-Shift-ArrowDown",
description: "Move parent node down",
category: "Editor",
type: "tiptap"
}
} satisfies Record<string, TipTapKey>;
@@ -451,6 +475,8 @@ export function formatKey(key: string, isMac = false, separator = " ") {
.replace(/\bright\b/gi, "→")
.replace(/\bleft\b/gi, "←")
.replace(/\benter\b/gi, "↵")
.replace(/\barrowup\b/gi, "↑")
.replace(/\barrowdown\b/gi, "↓")
.replace(/\b\w\b/gi, (e) => e.toUpperCase())
.trim();
}

View File

@@ -0,0 +1,172 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
createEditor,
h,
p,
ul,
li,
outlineList,
outlineListItem
} from "../../../../test-utils/index.js";
import { test, expect, describe } from "vitest";
import { KeyMap } from "../key-map.js";
import { OutlineList } from "../../outline-list/outline-list.js";
import { OutlineListItem } from "../../outline-list-item/outline-list-item.js";
import { BulletList } from "../../bullet-list/bullet-list.js";
import { ListItem } from "../../list-item/list-item.js";
describe("key-map", () => {
test("move paragraph up", async () => {
const el = h("div", [p(["para 1"]), p(["para 2"]), p(["para 3"])]);
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: el.outerHTML,
extensions: {
KeyMap: KeyMap
}
});
editor.commands.setTextSelection({ from: 10, to: 10 });
const event = new KeyboardEvent("keydown", {
key: "ArrowUp",
altKey: true
});
editor.view.dom.dispatchEvent(event);
expect(editor.getHTML()).toBe(`<p>para 2</p><p>para 1</p><p>para 3</p>`);
});
test("move paragraph down", async () => {
const el = h("div", [p(["para 1"]), p(["para 2"]), p(["para 3"])]);
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: el.outerHTML,
extensions: {
KeyMap: KeyMap
}
});
editor.commands.setTextSelection(0);
const event = new KeyboardEvent("keydown", {
key: "ArrowDown",
altKey: true
});
editor.view.dom.dispatchEvent(event);
expect(editor.getHTML()).toBe(`<p>para 2</p><p>para 1</p><p>para 3</p>`);
});
test("move outline list item up", async () => {
const el = outlineList(
outlineListItem(["item 1"]),
outlineListItem(["item 2"]),
outlineListItem(["item 3"])
);
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: el.outerHTML,
extensions: {
KeyMap: KeyMap,
outlineList: OutlineList,
outlineListItem: OutlineListItem
}
});
editor.commands.setTextSelection({ from: 15, to: 15 });
const event = new KeyboardEvent("keydown", {
key: "ArrowUp",
altKey: true
});
editor.view.dom.dispatchEvent(event);
const expectedHTML = outlineList(
outlineListItem(["item 2"]),
outlineListItem(["item 1"]),
outlineListItem(["item 3"])
).outerHTML;
expect(editor.getHTML()).toBe(expectedHTML);
});
test("move outline list item down", async () => {
const el = outlineList(
outlineListItem(["item 1"]),
outlineListItem(["item 2"]),
outlineListItem(["item 3"])
);
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: el.outerHTML,
extensions: {
KeyMap: KeyMap,
outlineList: OutlineList,
outlineListItem: OutlineListItem
}
});
editor.commands.setTextSelection(0);
const event = new KeyboardEvent("keydown", {
key: "ArrowDown",
altKey: true
});
editor.view.dom.dispatchEvent(event);
const expectedHTML = outlineList(
outlineListItem(["item 2"]),
outlineListItem(["item 1"]),
outlineListItem(["item 3"])
).outerHTML;
expect(editor.getHTML()).toBe(expectedHTML);
});
test("move entire bullet list down", async () => {
const el = h("div", [
p(["para 1"]),
ul([li([p(["list item 1"])]), li([p(["list item 2"])])]),
p(["para 2"])
]);
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: el.innerHTML,
extensions: {
KeyMap: KeyMap,
bulletList: BulletList,
listItem: ListItem
}
});
editor.commands.setTextSelection({ from: 10, to: 10 });
const event = new KeyboardEvent("keydown", {
key: "ArrowDown",
altKey: true,
shiftKey: true
});
editor.view.dom.dispatchEvent(event);
expect(editor.getHTML()).toBe(
`<p>para 1</p><p>para 2</p><ul><li><p>list item 1</p></li><li><p>list item 2</p></li></ul>`
);
});
});

View File

@@ -17,12 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { tiptapKeys } from "@notesnook/common";
import { Extension } from "@tiptap/core";
import { CodeBlock } from "../code-block/index.js";
import { showLinkPopup } from "../../toolbar/popups/link-popup.js";
import { isListActive } from "../../utils/list.js";
import { tiptapKeys } from "@notesnook/common";
import { CodeBlock } from "../code-block/index.js";
import { isInTable } from "../table/prosemirror-tables/util.js";
import {
moveNodeUp,
moveNodeDown,
moveParentUp,
moveParentDown
} from "./move-node.js";
export const KeyMap = Extension.create({
name: "key-map",
@@ -71,6 +77,38 @@ export const KeyMap = Extension.create({
[tiptapKeys.insertLink.keys]: ({ editor }) => {
showLinkPopup(editor);
return true;
},
[tiptapKeys.moveLineUp.keys]: ({ editor }) => {
try {
return moveNodeUp(editor);
} catch (e) {
console.error("Error moving node up:", e);
return false;
}
},
[tiptapKeys.moveLineDown.keys]: ({ editor }) => {
try {
return moveNodeDown(editor);
} catch (e) {
console.error("Error moving node down:", e);
return false;
}
},
[tiptapKeys.moveNodeUp.keys]: ({ editor }) => {
try {
return moveParentUp(editor);
} catch (e) {
console.error("Error moving node up:", e);
return false;
}
},
[tiptapKeys.moveNodeDown.keys]: ({ editor }) => {
try {
return moveParentDown(editor);
} catch (e) {
console.error("Error moving node down:", e);
return false;
}
}
};
}

View File

@@ -0,0 +1,217 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Editor } from "@tiptap/core";
import OrderedList from "@tiptap/extension-ordered-list";
import { Fragment, Node, Slice } from "@tiptap/pm/model";
import { Selection } from "@tiptap/pm/state";
import { ReplaceStep } from "@tiptap/pm/transform";
import {
findParentNodeClosestToPos,
findParentNodeOfType
} from "../../utils/prosemirror.js";
import { Blockquote } from "../blockquote/blockquote.js";
import { BulletList } from "../bullet-list/bullet-list.js";
import { Callout } from "../callout/callout.js";
import { CheckList } from "../check-list/check-list.js";
import { OutlineList } from "../outline-list/outline-list.js";
import { Table } from "../table/table.js";
import { TaskListNode } from "../task-list/task-list.js";
import { ListItem } from "../list-item/list-item.js";
import { CheckListItem } from "../check-list-item/check-list-item.js";
import { TaskItemNode } from "../task-item/task-item.js";
import { OutlineListItem } from "../outline-list-item/outline-list-item.js";
type ResolvedNode = {
start: number;
depth: number;
node: Node;
};
function mapChildren<T>(
node: Node | Fragment,
callback: (child: Node, index: number, frag: Fragment) => T
): T[] {
const array = [];
for (let i = 0; i < node.childCount; i++) {
array.push(
callback(node.child(i), i, node instanceof Fragment ? node : node.content)
);
}
return array;
}
const listItems = [
ListItem.name,
CheckListItem.name,
TaskItemNode.name,
OutlineListItem.name
];
function resolveNode(editor: Editor): ResolvedNode | undefined {
const { state } = editor;
const { $from } = state.selection;
const currentNode = $from.node();
const parentNode = $from.node($from.depth - 1);
let targetType = currentNode.type;
let currentResolved: ResolvedNode | undefined = {
start: $from.start(),
depth: $from.depth,
node: currentNode
};
if (listItems.includes(parentNode.type.name)) {
const isFirstParagraph =
currentNode.type.name === "paragraph" &&
parentNode.firstChild === currentNode;
if (isFirstParagraph) {
targetType = parentNode.type;
currentResolved = findParentNodeOfType(targetType)(state.selection);
}
}
if (parentNode.type.name === Callout.name) {
const isFirstHeading =
currentNode.type.name === "heading" &&
parentNode.firstChild === currentNode;
if (isFirstHeading) {
targetType = parentNode.type;
currentResolved = findParentNodeOfType(targetType)(state.selection);
}
}
return currentResolved;
}
const validParents = [
Callout.name,
Table.name,
BulletList.name,
OrderedList.name,
TaskListNode.name,
CheckList.name,
OutlineList.name,
Blockquote.name
];
function resolveParentNode(editor: Editor): ResolvedNode | undefined {
const { state } = editor;
const { $from } = state.selection;
const parent = findParentNodeClosestToPos($from, (node) =>
validParents.includes(node.type.name)
);
if (!parent) return undefined;
return {
start: parent.start,
depth: parent.depth,
node: parent.node
};
}
function swapNodeWithSibling(
editor: Editor,
resolvedNode: ResolvedNode,
isDown: boolean
): boolean {
const { state } = editor;
const { $from } = state.selection;
const parentDepth = resolvedNode.depth - 1;
const parent = $from.node(parentDepth);
const parentPos = $from.start(parentDepth);
let arr = mapChildren(parent, (node) => node);
let index = arr.indexOf(resolvedNode.node);
let swapWith = isDown ? index + 1 : index - 1;
if (swapWith >= arr.length || swapWith < 0) {
return false;
}
if (swapWith === 0 && parent.type.name === Callout.name) {
return false;
}
const swapWithNodeSize = arr[swapWith].nodeSize;
[arr[index], arr[swapWith]] = [arr[swapWith], arr[index]];
let tr = state.tr;
let replaceStart = parentPos;
let replaceEnd = $from.end(parentDepth);
const slice = new Slice(Fragment.fromArray(arr), 0, 0);
tr = tr.step(new ReplaceStep(replaceStart, replaceEnd, slice, false));
tr = tr.setSelection(
Selection.near(
tr.doc.resolve(
isDown ? $from.pos + swapWithNodeSize : $from.pos - swapWithNodeSize
)
)
);
tr.scrollIntoView();
editor.view.dispatch(tr);
return true;
}
export function moveNodeUp(editor: Editor): boolean {
const { state } = editor;
if (!state.selection.empty) return false;
const resolved = resolveNode(editor);
if (!resolved) return false;
return swapNodeWithSibling(editor, resolved, false);
}
export function moveNodeDown(editor: Editor): boolean {
const { state } = editor;
if (!state.selection.empty) return false;
const resolved = resolveNode(editor);
if (!resolved) return false;
return swapNodeWithSibling(editor, resolved, true);
}
export function moveParentUp(editor: Editor): boolean {
const { state } = editor;
if (!state.selection.empty) return false;
const resolved = resolveParentNode(editor);
if (!resolved) return false;
return swapNodeWithSibling(editor, resolved, false);
}
export function moveParentDown(editor: Editor): boolean {
const { state } = editor;
if (!state.selection.empty) return false;
const resolved = resolveParentNode(editor);
if (!resolved) return false;
return swapNodeWithSibling(editor, resolved, true);
}