mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Added single node enter handling
Also updated media to be an inline element to align with old editor behaviour.
This commit is contained in:
parent
ced66f1671
commit
2036438203
@ -196,6 +196,10 @@ export class MediaNode extends ElementNode {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isParentRequired(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
createInnerDOM() {
|
createInnerDOM() {
|
||||||
const sources = (this.__tag === 'video' || this.__tag === 'audio') ? this.__sources : [];
|
const sources = (this.__tag === 'video' || this.__tag === 'audio') ? this.__sources : [];
|
||||||
const sourceEls = sources.map(source => el('source', source));
|
const sourceEls = sources.map(source => el('source', source));
|
||||||
@ -325,12 +329,13 @@ export function $createMediaNodeFromHtml(html: string): MediaNode | null {
|
|||||||
|
|
||||||
const videoExtensions = ['mp4', 'mpeg', 'm4v', 'm4p', 'mov'];
|
const videoExtensions = ['mp4', 'mpeg', 'm4v', 'm4p', 'mov'];
|
||||||
const audioExtensions = ['3gp', 'aac', 'flac', 'mp3', 'm4a', 'ogg', 'wav', 'webm'];
|
const audioExtensions = ['3gp', 'aac', 'flac', 'mp3', 'm4a', 'ogg', 'wav', 'webm'];
|
||||||
const iframeExtensions = ['html', 'htm', 'php', 'asp', 'aspx'];
|
const iframeExtensions = ['html', 'htm', 'php', 'asp', 'aspx', ''];
|
||||||
|
|
||||||
export function $createMediaNodeFromSrc(src: string): MediaNode {
|
export function $createMediaNodeFromSrc(src: string): MediaNode {
|
||||||
let nodeTag: MediaNodeTag = 'iframe';
|
let nodeTag: MediaNodeTag = 'iframe';
|
||||||
const srcEnd = src.split('?')[0].split('/').pop() || '';
|
const srcEnd = src.split('?')[0].split('/').pop() || '';
|
||||||
const extension = (srcEnd.split('.').pop() || '').toLowerCase();
|
const srcEndSplit = srcEnd.split('.');
|
||||||
|
const extension = (srcEndSplit.length > 1 ? srcEndSplit[srcEndSplit.length - 1] : '').toLowerCase();
|
||||||
if (videoExtensions.includes(extension)) {
|
if (videoExtensions.includes(extension)) {
|
||||||
nodeTag = 'video';
|
nodeTag = 'video';
|
||||||
} else if (audioExtensions.includes(extension)) {
|
} else if (audioExtensions.includes(extension)) {
|
||||||
|
@ -4,22 +4,55 @@ import {
|
|||||||
COMMAND_PRIORITY_LOW,
|
COMMAND_PRIORITY_LOW,
|
||||||
KEY_BACKSPACE_COMMAND,
|
KEY_BACKSPACE_COMMAND,
|
||||||
KEY_DELETE_COMMAND,
|
KEY_DELETE_COMMAND,
|
||||||
LexicalEditor
|
KEY_ENTER_COMMAND,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import {$isImageNode} from "../nodes/image";
|
import {$isImageNode} from "../nodes/image";
|
||||||
import {$isMediaNode} from "../nodes/media";
|
import {$isMediaNode} from "../nodes/media";
|
||||||
import {getLastSelection} from "../utils/selection";
|
import {getLastSelection} from "../utils/selection";
|
||||||
|
import {$getNearestNodeBlockParent} from "../utils/nodes";
|
||||||
|
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
||||||
|
|
||||||
|
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) {
|
function deleteSingleSelectedNode(editor: LexicalEditor) {
|
||||||
const selectionNodes = getLastSelection(editor)?.getNodes() || [];
|
const selectionNodes = getLastSelection(editor)?.getNodes() || [];
|
||||||
if (selectionNodes.length === 1) {
|
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 node = selectionNodes[0];
|
||||||
if ($isDecoratorNode(node) || $isImageNode(node) || $isMediaNode(node)) {
|
const nearestBlock = $getNearestNodeBlockParent(node) || node;
|
||||||
editor.update(() => {
|
if (nearestBlock) {
|
||||||
node.remove();
|
requestAnimationFrame(() => {
|
||||||
|
editor.update(() => {
|
||||||
|
const newParagraph = $createCustomParagraphNode();
|
||||||
|
nearestBlock.insertAfter(newParagraph);
|
||||||
|
newParagraph.select();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
event?.preventDefault();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerKeyboardHandling(context: EditorUiContext): () => void {
|
export function registerKeyboardHandling(context: EditorUiContext): () => void {
|
||||||
@ -33,8 +66,13 @@ export function registerKeyboardHandling(context: EditorUiContext): () => void {
|
|||||||
return false;
|
return false;
|
||||||
}, COMMAND_PRIORITY_LOW);
|
}, COMMAND_PRIORITY_LOW);
|
||||||
|
|
||||||
|
const unregisterEnter = context.editor.registerCommand(KEY_ENTER_COMMAND, (event): boolean => {
|
||||||
|
return insertAfterSingleSelectedNode(context.editor, event);
|
||||||
|
}, COMMAND_PRIORITY_LOW);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unregisterBackspace();
|
unregisterBackspace();
|
||||||
unregisterDelete();
|
unregisterDelete();
|
||||||
|
unregisterEnter();
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -197,7 +197,7 @@ export const media: EditorFormDefinition = {
|
|||||||
if (selectedNode && node) {
|
if (selectedNode && node) {
|
||||||
selectedNode.replace(node)
|
selectedNode.replace(node)
|
||||||
} else if (node) {
|
} else if (node) {
|
||||||
$insertNodeToNearestRoot(node);
|
$insertNodes([node]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ export const media: EditorFormDefinition = {
|
|||||||
updateNode.setSrc(src);
|
updateNode.setSrc(src);
|
||||||
updateNode.setWidthAndHeight(width, height);
|
updateNode.setWidthAndHeight(width, height);
|
||||||
if (!selectedNode) {
|
if (!selectedNode) {
|
||||||
$insertNodeToNearestRoot(updateNode);
|
$insertNodes([updateNode]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
import {$getRoot, $isElementNode, $isTextNode, ElementNode, LexicalEditor, LexicalNode} from "lexical";
|
import {
|
||||||
|
$getRoot,
|
||||||
|
$isDecoratorNode,
|
||||||
|
$isElementNode,
|
||||||
|
$isTextNode,
|
||||||
|
ElementNode,
|
||||||
|
LexicalEditor,
|
||||||
|
LexicalNode
|
||||||
|
} from "lexical";
|
||||||
import {LexicalNodeMatcher} from "../nodes";
|
import {LexicalNodeMatcher} from "../nodes";
|
||||||
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
||||||
import {$generateNodesFromDOM} from "@lexical/html";
|
import {$generateNodesFromDOM} from "@lexical/html";
|
||||||
import {htmlToDom} from "./dom";
|
import {htmlToDom} from "./dom";
|
||||||
import {NodeHasAlignment} from "../nodes/_common";
|
import {NodeHasAlignment} from "../nodes/_common";
|
||||||
|
import {$findMatchingParent} from "@lexical/utils";
|
||||||
|
|
||||||
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
|
function wrapTextNodes(nodes: LexicalNode[]): LexicalNode[] {
|
||||||
return nodes.map(node => {
|
return nodes.map(node => {
|
||||||
@ -73,6 +82,18 @@ export function $getNearestBlockNodeForCoords(editor: LexicalEditor, x: number,
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function $getNearestNodeBlockParent(node: LexicalNode): LexicalNode|null {
|
||||||
|
const isBlockNode = (node: LexicalNode): boolean => {
|
||||||
|
return ($isElementNode(node) || $isDecoratorNode(node)) && !node.isInline();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isBlockNode(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $findMatchingParent(node, isBlockNode);
|
||||||
|
}
|
||||||
|
|
||||||
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
export function nodeHasAlignment(node: object): node is NodeHasAlignment {
|
||||||
return '__alignment' in node;
|
return '__alignment' in node;
|
||||||
}
|
}
|
@ -16,7 +16,7 @@ import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexi
|
|||||||
import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
|
import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
|
||||||
import {$setBlocksType} from "@lexical/selection";
|
import {$setBlocksType} from "@lexical/selection";
|
||||||
|
|
||||||
import {$getParentOfType, nodeHasAlignment} from "./nodes";
|
import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
|
||||||
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
import {$createCustomParagraphNode} from "../nodes/custom-paragraph";
|
||||||
import {CommonBlockAlignment} from "../nodes/_common";
|
import {CommonBlockAlignment} from "../nodes/_common";
|
||||||
|
|
||||||
@ -155,11 +155,8 @@ export function $getBlockElementNodesInSelection(selection: BaseSelection | null
|
|||||||
|
|
||||||
const blockNodes: Map<string, ElementNode> = new Map();
|
const blockNodes: Map<string, ElementNode> = new Map();
|
||||||
for (const node of selection.getNodes()) {
|
for (const node of selection.getNodes()) {
|
||||||
const blockElement = $findMatchingParent(node, (node) => {
|
const blockElement = $getNearestNodeBlockParent(node);
|
||||||
return $isElementNode(node) && !node.isInline();
|
if ($isElementNode(blockElement)) {
|
||||||
}) as ElementNode | null;
|
|
||||||
|
|
||||||
if (blockElement) {
|
|
||||||
blockNodes.set(blockElement.getKey(), blockElement);
|
blockNodes.set(blockElement.getKey(), blockElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user