2024-05-28 22:56:58 +01:00
|
|
|
import {
|
2024-07-16 16:36:08 +01:00
|
|
|
$createNodeSelection,
|
2024-09-07 18:39:58 +01:00
|
|
|
$createParagraphNode, $createRangeSelection,
|
2024-08-03 18:14:01 +01:00
|
|
|
$getRoot,
|
2024-09-16 12:29:46 +01:00
|
|
|
$getSelection, $isBlockElementNode, $isDecoratorNode,
|
2024-08-03 18:14:01 +01:00
|
|
|
$isElementNode,
|
|
|
|
$isTextNode,
|
|
|
|
$setSelection,
|
2024-09-06 14:07:10 +01:00
|
|
|
BaseSelection, DecoratorNode,
|
2024-08-20 13:07:33 +01:00
|
|
|
ElementNode, LexicalEditor,
|
2024-08-03 18:14:01 +01:00
|
|
|
LexicalNode,
|
2024-09-13 15:50:42 +01:00
|
|
|
TextFormatType, TextNode
|
2024-05-28 22:56:58 +01:00
|
|
|
} from "lexical";
|
2024-12-03 16:24:49 +00:00
|
|
|
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
|
2024-08-03 18:14:01 +01:00
|
|
|
import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes";
|
2024-05-28 18:04:48 +01:00
|
|
|
import {$setBlocksType} from "@lexical/selection";
|
2024-05-29 20:38:31 +01:00
|
|
|
|
2024-09-10 12:14:26 +01:00
|
|
|
import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes";
|
2024-09-06 14:07:10 +01:00
|
|
|
import {CommonBlockAlignment} from "../nodes/_common";
|
2024-08-20 13:07:33 +01:00
|
|
|
|
|
|
|
const lastSelectionByEditor = new WeakMap<LexicalEditor, BaseSelection|null>;
|
|
|
|
|
|
|
|
export function getLastSelection(editor: LexicalEditor): BaseSelection|null {
|
|
|
|
return lastSelectionByEditor.get(editor) || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setLastSelection(editor: LexicalEditor, selection: BaseSelection|null): void {
|
|
|
|
lastSelectionByEditor.set(editor, selection);
|
|
|
|
}
|
2024-05-28 18:04:48 +01:00
|
|
|
|
2024-08-03 18:14:01 +01:00
|
|
|
export function $selectionContainsNodeType(selection: BaseSelection | null, matcher: LexicalNodeMatcher): boolean {
|
2024-07-17 16:45:57 +01:00
|
|
|
return $getNodeFromSelection(selection, matcher) !== null;
|
2024-06-01 16:49:47 +01:00
|
|
|
}
|
|
|
|
|
2024-08-03 18:14:01 +01:00
|
|
|
export function $getNodeFromSelection(selection: BaseSelection | null, matcher: LexicalNodeMatcher): LexicalNode | null {
|
2024-05-28 18:04:48 +01:00
|
|
|
if (!selection) {
|
2024-06-01 16:49:47 +01:00
|
|
|
return null;
|
2024-05-28 18:04:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const node of selection.getNodes()) {
|
|
|
|
if (matcher(node)) {
|
2024-06-01 16:49:47 +01:00
|
|
|
return node;
|
2024-05-28 18:04:48 +01:00
|
|
|
}
|
|
|
|
|
2024-07-21 15:11:24 +01:00
|
|
|
const matchedParent = $getParentOfType(node, matcher);
|
|
|
|
if (matchedParent) {
|
|
|
|
return matchedParent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2024-08-03 18:14:01 +01:00
|
|
|
export function $selectionContainsTextFormat(selection: BaseSelection | null, format: TextFormatType): boolean {
|
2024-05-28 22:56:58 +01:00
|
|
|
if (!selection) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const node of selection.getNodes()) {
|
|
|
|
if ($isTextNode(node) && node.hasFormat(format)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-17 16:45:57 +01:00
|
|
|
export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creator: LexicalElementNodeCreator) {
|
|
|
|
const selection = $getSelection();
|
|
|
|
const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
|
|
|
|
if (selection && matcher(blockElement)) {
|
2024-12-03 16:24:49 +00:00
|
|
|
$setBlocksType(selection, $createParagraphNode);
|
2024-07-17 16:45:57 +01:00
|
|
|
} else {
|
|
|
|
$setBlocksType(selection, creator);
|
|
|
|
}
|
2024-06-21 16:18:44 +01:00
|
|
|
}
|
|
|
|
|
2024-07-17 16:45:57 +01:00
|
|
|
export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: boolean = true) {
|
2024-07-29 15:27:41 +01:00
|
|
|
$insertNewBlockNodesAtSelection([node], insertAfter);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) {
|
2024-10-05 12:42:47 +01:00
|
|
|
const selectionNodes = $getSelection()?.getNodes() || [];
|
|
|
|
const blockElement = selectionNodes.length > 0 ? $getNearestNodeBlockParent(selectionNodes[0]) : null;
|
2024-06-21 16:18:44 +01:00
|
|
|
|
|
|
|
if (blockElement) {
|
2024-06-27 15:48:06 +01:00
|
|
|
if (insertAfter) {
|
2024-07-29 15:27:41 +01:00
|
|
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
|
|
blockElement.insertAfter(nodes[i]);
|
|
|
|
}
|
2024-06-27 15:48:06 +01:00
|
|
|
} else {
|
2024-07-29 15:27:41 +01:00
|
|
|
for (const node of nodes) {
|
|
|
|
blockElement.insertBefore(node);
|
|
|
|
}
|
2024-06-27 15:48:06 +01:00
|
|
|
}
|
2024-06-21 16:18:44 +01:00
|
|
|
} else {
|
2024-07-29 15:27:41 +01:00
|
|
|
$getRoot().append(...nodes);
|
2024-06-21 16:18:44 +01:00
|
|
|
}
|
2024-07-16 16:36:08 +01:00
|
|
|
}
|
|
|
|
|
2024-07-17 16:45:57 +01:00
|
|
|
export function $selectSingleNode(node: LexicalNode) {
|
2024-07-16 16:36:08 +01:00
|
|
|
const nodeSelection = $createNodeSelection();
|
|
|
|
nodeSelection.add(node.getKey());
|
|
|
|
$setSelection(nodeSelection);
|
|
|
|
}
|
|
|
|
|
2024-09-13 15:50:42 +01:00
|
|
|
function getFirstTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
|
|
|
|
for (const node of nodes) {
|
|
|
|
if ($isTextNode(node)) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($isElementNode(node)) {
|
|
|
|
const children = node.getChildren();
|
|
|
|
const textNode = getFirstTextNodeInNodes(children);
|
|
|
|
if (textNode !== null) {
|
|
|
|
return textNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLastTextNodeInNodes(nodes: LexicalNode[]): TextNode|null {
|
|
|
|
const revNodes = [...nodes].reverse();
|
|
|
|
for (const node of revNodes) {
|
|
|
|
if ($isTextNode(node)) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($isElementNode(node)) {
|
|
|
|
const children = [...node.getChildren()].reverse();
|
|
|
|
const textNode = getLastTextNodeInNodes(children);
|
|
|
|
if (textNode !== null) {
|
|
|
|
return textNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function $selectNodes(nodes: LexicalNode[]) {
|
|
|
|
if (nodes.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const selection = $createRangeSelection();
|
|
|
|
const firstText = getFirstTextNodeInNodes(nodes);
|
|
|
|
const lastText = getLastTextNodeInNodes(nodes);
|
|
|
|
if (firstText && lastText) {
|
|
|
|
selection.setTextNodeRange(firstText, 0, lastText, lastText.getTextContentSize() || 0)
|
|
|
|
$setSelection(selection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-07 18:39:58 +01:00
|
|
|
export function $toggleSelection(editor: LexicalEditor) {
|
|
|
|
const lastSelection = getLastSelection(editor);
|
|
|
|
|
|
|
|
if (lastSelection) {
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
editor.update(() => {
|
|
|
|
$setSelection(lastSelection.clone());
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-03 18:14:01 +01:00
|
|
|
export function $selectionContainsNode(selection: BaseSelection | null, node: LexicalNode): boolean {
|
2024-07-16 16:36:08 +01:00
|
|
|
if (!selection) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const key = node.getKey();
|
|
|
|
for (const node of selection.getNodes()) {
|
|
|
|
if (node.getKey() === key) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2024-07-17 16:38:20 +01:00
|
|
|
}
|
|
|
|
|
2024-09-06 14:07:10 +01:00
|
|
|
export function $selectionContainsAlignment(selection: BaseSelection | null, alignment: CommonBlockAlignment): boolean {
|
2024-09-07 18:39:58 +01:00
|
|
|
|
|
|
|
const nodes = [
|
|
|
|
...(selection?.getNodes() || []),
|
|
|
|
...$getBlockElementNodesInSelection(selection)
|
|
|
|
];
|
2024-07-17 16:38:20 +01:00
|
|
|
for (const node of nodes) {
|
2024-09-06 14:07:10 +01:00
|
|
|
if (nodeHasAlignment(node) && node.getAlignment() === alignment) {
|
2024-07-17 16:38:20 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-09-16 12:29:46 +01:00
|
|
|
export function $selectionContainsDirection(selection: BaseSelection | null, direction: 'rtl'|'ltr'): boolean {
|
|
|
|
|
|
|
|
const nodes = [
|
|
|
|
...(selection?.getNodes() || []),
|
|
|
|
...$getBlockElementNodesInSelection(selection)
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
if ($isBlockElementNode(node) && node.getDirection() === direction) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-08-03 18:14:01 +01:00
|
|
|
export function $getBlockElementNodesInSelection(selection: BaseSelection | null): ElementNode[] {
|
2024-07-17 16:38:20 +01:00
|
|
|
if (!selection) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const blockNodes: Map<string, ElementNode> = new Map();
|
|
|
|
for (const node of selection.getNodes()) {
|
2024-09-10 12:14:26 +01:00
|
|
|
const blockElement = $getNearestNodeBlockParent(node);
|
|
|
|
if ($isElementNode(blockElement)) {
|
2024-07-17 16:38:20 +01:00
|
|
|
blockNodes.set(blockElement.getKey(), blockElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Array.from(blockNodes.values());
|
2024-09-06 14:07:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export function $getDecoratorNodesInSelection(selection: BaseSelection | null): DecoratorNode<any>[] {
|
|
|
|
if (!selection) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return selection.getNodes().filter(node => $isDecoratorNode(node));
|
2024-05-28 18:04:48 +01:00
|
|
|
}
|