]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/services/keyboard-handling.ts
Tests: Updated comment test to account for new editor usage
[bookstack] / resources / js / wysiwyg / services / keyboard-handling.ts
index 08eed7645967f8a47f4b97326b54d900b3add633..b4f546117bb9951959f7ea21e2f08b5ef5fd5bcf 100644 (file)
@@ -3,7 +3,7 @@ import {
     $createParagraphNode,
     $getSelection,
     $isDecoratorNode,
-    COMMAND_PRIORITY_LOW, KEY_ARROW_DOWN_COMMAND,
+    COMMAND_PRIORITY_LOW, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND,
     KEY_BACKSPACE_COMMAND,
     KEY_DELETE_COMMAND,
     KEY_ENTER_COMMAND, KEY_TAB_COMMAND,
@@ -13,15 +13,16 @@ import {
 import {$isImageNode} from "@lexical/rich-text/LexicalImageNode";
 import {$isMediaNode} from "@lexical/rich-text/LexicalMediaNode";
 import {getLastSelection} from "../utils/selection";
-import {$getNearestNodeBlockParent, $getParentOfType} from "../utils/nodes";
+import {$getNearestNodeBlockParent, $getParentOfType, $selectOrCreateAdjacent} from "../utils/nodes";
 import {$setInsetForSelection} from "../utils/lists";
 import {$isListItemNode} from "@lexical/list";
 import {$isDetailsNode, DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
+import {$isDiagramNode} from "../utils/diagrams";
 
 function isSingleSelectedNode(nodes: LexicalNode[]): boolean {
     if (nodes.length === 1) {
         const node = nodes[0];
-        if ($isDecoratorNode(node) || $isImageNode(node) || $isMediaNode(node)) {
+        if ($isDecoratorNode(node) || $isImageNode(node) || $isMediaNode(node) || $isDiagramNode(node)) {
             return true;
         }
     }
@@ -43,19 +44,24 @@ function deleteSingleSelectedNode(editor: LexicalEditor) {
 }
 
 /**
- * Insert a new empty node after the selection if the selection contains a single
+ * Insert a new empty node before/after the selection if the selection contains a single
  * selected node (like image, media etc...).
  */
-function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEvent|null): boolean {
+function insertAdjacentToSingleSelectedNode(editor: LexicalEditor, event: KeyboardEvent|null): boolean {
     const selectionNodes = getLastSelection(editor)?.getNodes() || [];
     if (isSingleSelectedNode(selectionNodes)) {
         const node = selectionNodes[0];
         const nearestBlock = $getNearestNodeBlockParent(node) || node;
+        const insertBefore = event?.shiftKey === true;
         if (nearestBlock) {
             requestAnimationFrame(() => {
                 editor.update(() => {
                     const newParagraph = $createParagraphNode();
-                    nearestBlock.insertAfter(newParagraph);
+                    if (insertBefore) {
+                        nearestBlock.insertBefore(newParagraph);
+                    } else {
+                        nearestBlock.insertAfter(newParagraph);
+                    }
                     newParagraph.select();
                 });
             });
@@ -67,6 +73,21 @@ function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEve
     return false;
 }
 
+function focusAdjacentOrInsertForSingleSelectNode(editor: LexicalEditor, event: KeyboardEvent|null, after: boolean = true): boolean {
+    const selectionNodes = getLastSelection(editor)?.getNodes() || [];
+    if (!isSingleSelectedNode(selectionNodes)) {
+        return false;
+    }
+
+    event?.preventDefault();
+    const node = selectionNodes[0];
+    editor.update(() => {
+        $selectOrCreateAdjacent(node, after);
+    });
+
+    return true;
+}
+
 /**
  * Insert a new node after a details node, if inside a details node that's
  * the last element, and if the cursor is at the last block within the details node.
@@ -151,6 +172,15 @@ function getDetailsScenario(editor: LexicalEditor): {
     }
 }
 
+function $isSingleListItem(nodes: LexicalNode[]): boolean {
+    if (nodes.length !== 1) {
+        return false;
+    }
+
+    const node = nodes[0];
+    return $isListItemNode(node) || $isListItemNode(node.getParent());
+}
+
 /**
  * Inset the nodes within selection when a range of nodes is selected
  * or if a list node is selected.
@@ -159,7 +189,7 @@ function handleInsetOnTab(editor: LexicalEditor, event: KeyboardEvent|null): boo
     const change = event?.shiftKey ? -40 : 40;
     const selection = $getSelection();
     const nodes = selection?.getNodes() || [];
-    if (nodes.length > 1 || (nodes.length === 1 && $isListItemNode(nodes[0].getParent()))) {
+    if (nodes.length > 1 || $isSingleListItem(nodes)) {
         editor.update(() => {
             $setInsetForSelection(editor, change);
         });
@@ -182,7 +212,7 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
     }, COMMAND_PRIORITY_LOW);
 
     const unregisterEnter = context.editor.registerCommand(KEY_ENTER_COMMAND, (event): boolean => {
-        return insertAfterSingleSelectedNode(context.editor, event)
+        return insertAdjacentToSingleSelectedNode(context.editor, event)
             || moveAfterDetailsOnEmptyLine(context.editor, event);
     }, COMMAND_PRIORITY_LOW);
 
@@ -190,8 +220,13 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
         return handleInsetOnTab(context.editor, event);
     }, COMMAND_PRIORITY_LOW);
 
+    const unregisterUp = context.editor.registerCommand(KEY_ARROW_UP_COMMAND, (event): boolean => {
+        return focusAdjacentOrInsertForSingleSelectNode(context.editor, event, false);
+    }, COMMAND_PRIORITY_LOW);
+
     const unregisterDown = context.editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event): boolean => {
-        return insertAfterDetails(context.editor, event);
+        return insertAfterDetails(context.editor, event)
+            || focusAdjacentOrInsertForSingleSelectNode(context.editor, event, true)
     }, COMMAND_PRIORITY_LOW);
 
     return () => {
@@ -199,6 +234,7 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
         unregisterDelete();
         unregisterEnter();
         unregisterTab();
+        unregisterUp();
         unregisterDown();
     };
 }
\ No newline at end of file