From 3bc4fcf199bab75025dcfe6ede32522b3e75a6a0 Mon Sep 17 00:00:00 2001
From: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Date: Mon, 9 Dec 2024 20:55:30 +0500
Subject: [PATCH] editor: add alt arrow keyboard shortcut * implementation from
https://discuss.prosemirror.net/t/keymap-to-move-a-line/3645/5 * only
supports moving paragraphs * TODO: add tests
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
---
.../editor/src/extensions/key-map/key-map.ts | 89 ++++++++++++++++++-
1 file changed, 88 insertions(+), 1 deletion(-)
diff --git a/packages/editor/src/extensions/key-map/key-map.ts b/packages/editor/src/extensions/key-map/key-map.ts
index 148005836d..d8b071fd6c 100644
--- a/packages/editor/src/extensions/key-map/key-map.ts
+++ b/packages/editor/src/extensions/key-map/key-map.ts
@@ -17,11 +17,92 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import { Extension } from "@tiptap/core";
+import { Editor, Extension } from "@tiptap/core";
import { isInTable } from "@tiptap/pm/tables";
import { CodeBlock } from "../code-block/index.js";
import { showLinkPopup } from "../../toolbar/popups/link-popup.js";
import { isListActive } from "../../utils/list.js";
+import { findParentNodeOfType } from "../../utils/prosemirror.js";
+import { Fragment, Node, Slice } from "@tiptap/pm/model";
+import { ReplaceStep } from "@tiptap/pm/transform";
+import { Selection } from "@tiptap/pm/state";
+import { Callout } from "../callout/callout.js";
+
+function mapChildren(
+ 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;
+}
+
+/**
+ * implementation from https://discuss.prosemirror.net/t/keymap-to-move-a-line/3645/5
+ */
+function moveNode(editor: Editor, dir: "up" | "down") {
+ const isDown = dir === "down";
+ const { state } = editor;
+ if (!state.selection.empty) {
+ return false;
+ }
+
+ const { $from } = state.selection;
+ const type = $from.node().type;
+ if (!type) {
+ return false;
+ }
+
+ const currentResolved = findParentNodeOfType(type)(state.selection);
+ if (!currentResolved) {
+ return false;
+ }
+
+ const { node: currentNode } = currentResolved;
+ const parentDepth = currentResolved.depth - 1;
+ const parent = $from.node(parentDepth);
+ const parentPos = $from.start(parentDepth);
+
+ if (currentNode.type !== type) {
+ return false;
+ }
+
+ let arr = mapChildren(parent, (node) => node);
+ let index = arr.indexOf(currentNode);
+ 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 const KeyMap = Extension.create({
name: "key-map",
@@ -70,6 +151,12 @@ export const KeyMap = Extension.create({
"Mod-k": ({ editor }) => {
showLinkPopup(editor);
return true;
+ },
+ "Alt-ArrowUp": ({ editor }) => {
+ return moveNode(editor, "up");
+ },
+ "Alt-ArrowDown": ({ editor }) => {
+ return moveNode(editor, "down");
}
};
}