mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-12-12 05:23:40 +08:00
662110c269
Added list nesting support to allow li > ul style nesting which lexical didn't do by default. Adds tab handling for inset/outset controls. Will be a range of edge-case bugs to squash during testing.
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import {EditorUiContext} from "../ui/framework/core";
|
|
import {
|
|
$getSelection,
|
|
$isDecoratorNode,
|
|
COMMAND_PRIORITY_LOW,
|
|
KEY_BACKSPACE_COMMAND,
|
|
KEY_DELETE_COMMAND,
|
|
KEY_ENTER_COMMAND, KEY_TAB_COMMAND,
|
|
LexicalEditor,
|
|
LexicalNode
|
|
} from "lexical";
|
|
import {$isImageNode} from "../nodes/image";
|
|
import {$isMediaNode} from "../nodes/media";
|
|
import {getLastSelection} from "../utils/selection";
|
|
import {$getNearestNodeBlockParent} from "../utils/nodes";
|
|
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
|
import {$isCustomListItemNode} from "../nodes/custom-list-item";
|
|
import {$setInsetForSelection} from "../utils/lists";
|
|
|
|
function isSingleSelectedNode(nodes: LexicalNode[]): boolean {
|
|
if (nodes.length === 1) {
|
|
const node = nodes[0];
|
|
if ($isDecoratorNode(node) || $isImageNode(node) || $isMediaNode(node)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function deleteSingleSelectedNode(editor: LexicalEditor) {
|
|
const selectionNodes = getLastSelection(editor)?.getNodes() || [];
|
|
if (isSingleSelectedNode(selectionNodes)) {
|
|
editor.update(() => {
|
|
selectionNodes[0].remove();
|
|
});
|
|
}
|
|
}
|
|
|
|
function insertAfterSingleSelectedNode(editor: LexicalEditor, event: KeyboardEvent|null): boolean {
|
|
const selectionNodes = getLastSelection(editor)?.getNodes() || [];
|
|
if (isSingleSelectedNode(selectionNodes)) {
|
|
const node = selectionNodes[0];
|
|
const nearestBlock = $getNearestNodeBlockParent(node) || node;
|
|
if (nearestBlock) {
|
|
requestAnimationFrame(() => {
|
|
editor.update(() => {
|
|
const newParagraph = $createCustomParagraphNode();
|
|
nearestBlock.insertAfter(newParagraph);
|
|
newParagraph.select();
|
|
});
|
|
});
|
|
event?.preventDefault();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function handleInsetOnTab(editor: LexicalEditor, event: KeyboardEvent|null) {
|
|
const change = event?.shiftKey ? -40 : 40;
|
|
editor.update(() => {
|
|
const selection = $getSelection();
|
|
const nodes = selection?.getNodes() || [];
|
|
if (nodes.length > 1 || (nodes.length === 1 && $isCustomListItemNode(nodes[0].getParent()))) {
|
|
$setInsetForSelection(editor, change);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function registerKeyboardHandling(context: EditorUiContext): () => void {
|
|
const unregisterBackspace = context.editor.registerCommand(KEY_BACKSPACE_COMMAND, (): boolean => {
|
|
deleteSingleSelectedNode(context.editor);
|
|
return false;
|
|
}, COMMAND_PRIORITY_LOW);
|
|
|
|
const unregisterDelete = context.editor.registerCommand(KEY_DELETE_COMMAND, (): boolean => {
|
|
deleteSingleSelectedNode(context.editor);
|
|
return false;
|
|
}, COMMAND_PRIORITY_LOW);
|
|
|
|
const unregisterEnter = context.editor.registerCommand(KEY_ENTER_COMMAND, (event): boolean => {
|
|
return insertAfterSingleSelectedNode(context.editor, event);
|
|
}, COMMAND_PRIORITY_LOW);
|
|
|
|
const unregisterTab = context.editor.registerCommand(KEY_TAB_COMMAND, (event): boolean => {
|
|
return handleInsetOnTab(context.editor, event);
|
|
}, COMMAND_PRIORITY_LOW);
|
|
|
|
return () => {
|
|
unregisterBackspace();
|
|
unregisterDelete();
|
|
unregisterEnter();
|
|
unregisterTab();
|
|
};
|
|
} |