X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/20364382034c4979dc05e207242baf2871bf6283..refs/pull/5676/head:/resources/js/wysiwyg/utils/selection.ts diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 4aa21045f..e4b5bf2dc 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -2,23 +2,21 @@ import { $createNodeSelection, $createParagraphNode, $createRangeSelection, $getRoot, - $getSelection, $isDecoratorNode, - $isElementNode, + $getSelection, $isBlockElementNode, $isDecoratorNode, + $isElementNode, $isParagraphNode, $isTextNode, $setSelection, BaseSelection, DecoratorNode, - ElementFormatType, ElementNode, LexicalEditor, LexicalNode, - TextFormatType + TextFormatType, TextNode } from "lexical"; -import {$findMatchingParent, $getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; +import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; import {LexicalElementNodeCreator, LexicalNodeMatcher} from "../nodes"; import {$setBlocksType} from "@lexical/selection"; import {$getNearestNodeBlockParent, $getParentOfType, nodeHasAlignment} from "./nodes"; -import {$createCustomParagraphNode} from "../nodes/custom-paragraph"; -import {CommonBlockAlignment} from "../nodes/_common"; +import {CommonBlockAlignment} from "lexical/nodes/common"; const lastSelectionByEditor = new WeakMap; @@ -53,17 +51,28 @@ export function $getNodeFromSelection(selection: BaseSelection | null, matcher: return null; } +export function $getTextNodeFromSelection(selection: BaseSelection | null): TextNode|null { + return $getNodeFromSelection(selection, $isTextNode) as TextNode|null; +} + export function $selectionContainsTextFormat(selection: BaseSelection | null, format: TextFormatType): boolean { if (!selection) { return false; } - for (const node of selection.getNodes()) { + // Check text nodes + const nodes = selection.getNodes(); + for (const node of nodes) { if ($isTextNode(node) && node.hasFormat(format)) { return true; } } + // If we're in an empty paragraph, check the paragraph format + if (nodes.length === 1 && $isParagraphNode(nodes[0]) && nodes[0].hasTextFormat(format)) { + return true; + } + return false; } @@ -71,7 +80,7 @@ export function $toggleSelectionBlockNodeType(matcher: LexicalNodeMatcher, creat const selection = $getSelection(); const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null; if (selection && matcher(blockElement)) { - $setBlocksType(selection, $createCustomParagraphNode); + $setBlocksType(selection, $createParagraphNode); } else { $setBlocksType(selection, creator); } @@ -82,8 +91,8 @@ export function $insertNewBlockNodeAtSelection(node: LexicalNode, insertAfter: b } export function $insertNewBlockNodesAtSelection(nodes: LexicalNode[], insertAfter: boolean = true) { - const selection = $getSelection(); - const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null; + const selectionNodes = $getSelection()?.getNodes() || []; + const blockElement = selectionNodes.length > 0 ? $getNearestNodeBlockParent(selectionNodes[0]) : null; if (blockElement) { if (insertAfter) { @@ -106,6 +115,57 @@ export function $selectSingleNode(node: LexicalNode) { $setSelection(nodeSelection); } +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); + } +} + export function $toggleSelection(editor: LexicalEditor) { const lastSelection = getLastSelection(editor); @@ -148,6 +208,22 @@ export function $selectionContainsAlignment(selection: BaseSelection | null, ali return false; } +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; +} + export function $getBlockElementNodesInSelection(selection: BaseSelection | null): ElementNode[] { if (!selection) { return [];