X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/8486775edf9d7979f9d6f1f555460bf8678d6653..refs/pull/5685/head:/resources/js/wysiwyg/services/keyboard-handling.ts diff --git a/resources/js/wysiwyg/services/keyboard-handling.ts b/resources/js/wysiwyg/services/keyboard-handling.ts index 08eed7645..b4f546117 100644 --- a/resources/js/wysiwyg/services/keyboard-handling.ts +++ b/resources/js/wysiwyg/services/keyboard-handling.ts @@ -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