FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Lexical: August 2025 fixes by ssddanbrown · Pull Request #5775 · BookStackApp/BookStack · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .scss  (1) .ts  (11) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
4 changes: 3 additions & 1 deletion resources/js/wysiwyg/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {createEditor, LexicalEditor} from 'lexical';
import {createEditor} from 'lexical';
import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {registerRichText} from '@lexical/rich-text';
import {mergeRegister} from '@lexical/utils';
Expand All @@ -20,6 +20,7 @@ import {modals} from "./ui/defaults/modals";
import {CodeBlockDecorator} from "./ui/decorators/code-block";
import {DiagramDecorator} from "./ui/decorators/diagram";
import {registerMouseHandling} from "./services/mouse-handling";
import {registerSelectionHandling} from "./services/selection-handling";

const theme = {
text: {
Expand Down Expand Up @@ -53,6 +54,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
registerShortcuts(context),
registerKeyboardHandling(context),
registerMouseHandling(context),
registerSelectionHandling(context),
registerTableResizer(editor, context.scrollDOM),
registerTableSelectionHandler(editor),
registerTaskListHandler(editor, context.editorDOM),
Expand Down
8 changes: 8 additions & 0 deletions resources/js/wysiwyg/lexical/core/LexicalNode.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,14 @@ export class LexicalNode {
return isSelected;
}

/**
* Indicate if this node should be selected directly instead of the default
* where the selection would descend to the nearest initial child element.
*/
shouldSelectDirectly(): boolean {
return false;
}

/**
* Returns this nodes key.
*/
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -476,12 +476,12 @@ export class RangeSelection implements BaseSelection {
const startOffset = firstPoint.offset;
const endOffset = lastPoint.offset;

if ($isElementNode(firstNode)) {
if ($isElementNode(firstNode) && !firstNode.shouldSelectDirectly()) {
const firstNodeDescendant =
firstNode.getDescendantByIndex<ElementNode>(startOffset);
firstNode = firstNodeDescendant != null ? firstNodeDescendant : firstNode;
}
if ($isElementNode(lastNode)) {
if ($isElementNode(lastNode) && !lastNode.shouldSelectDirectly()) {
let lastNodeDescendant =
lastNode.getDescendantByIndex<ElementNode>(endOffset);
// We don't want to over-select, as node selection infers the child before
Expand All @@ -499,7 +499,7 @@ export class RangeSelection implements BaseSelection {
let nodes: Array<LexicalNode>;

if (firstNode.is(lastNode)) {
if ($isElementNode(firstNode) && firstNode.getChildrenSize() > 0) {
if ($isElementNode(firstNode) && firstNode.getChildrenSize() > 0 && !firstNode.shouldSelectDirectly()) {
nodes = [];
} else {
nodes = [firstNode];
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@ export class ElementNode extends LexicalNode {
}
return node;
}
getFirstSelectableDescendant<T extends LexicalNode>(): null | T {
if (this.shouldSelectDirectly()) {
return null;
}
let node = this.getFirstChild<T>();
while ($isElementNode(node) && !node.shouldSelectDirectly()) {
const child = node.getFirstChild<T>();
if (child === null) {
break;
}
node = child;
}
return node;
}
getLastDescendant<T extends LexicalNode>(): null | T {
let node = this.getLastChild<T>();
while ($isElementNode(node)) {
Expand All @@ -161,6 +175,20 @@ export class ElementNode extends LexicalNode {
}
return node;
}
getLastSelectableDescendant<T extends LexicalNode>(): null | T {
if (this.shouldSelectDirectly()) {
return null;
}
let node = this.getLastChild<T>();
while ($isElementNode(node) && !node.shouldSelectDirectly()) {
const child = node.getLastChild<T>();
if (child === null) {
break;
}
node = child;
}
return node;
}
getDescendantByIndex<T extends LexicalNode>(index: number): null | T {
const children = this.getChildren<T>();
const childrenLength = children.length;
Expand Down Expand Up @@ -279,7 +307,7 @@ export class ElementNode extends LexicalNode {
let anchorOffset = _anchorOffset;
let focusOffset = _focusOffset;
const childrenCount = this.getChildrenSize();
if (!this.canBeEmpty()) {
if (!this.canBeEmpty() && !this.shouldSelectDirectly()) {
if (_anchorOffset === 0 && _focusOffset === 0) {
const firstChild = this.getFirstChild();
if ($isTextNode(firstChild) || $isElementNode(firstChild)) {
Expand Down Expand Up @@ -319,11 +347,11 @@ export class ElementNode extends LexicalNode {
return selection;
}
selectStart(): RangeSelection {
const firstNode = this.getFirstDescendant();
const firstNode = this.getFirstSelectableDescendant();
return firstNode ? firstNode.selectStart() : this.select();
}
selectEnd(): RangeSelection {
const lastNode = this.getLastDescendant();
const lastNode = this.getLastSelectableDescendant();
return lastNode ? lastNode.selectEnd() : this.select();
}
clear(): this {
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export class DetailsNode extends ElementNode {

if (this.__open) {
el.setAttribute('open', 'true');
el.removeAttribute('contenteditable');
} else {
el.setAttribute('contenteditable', 'false');
}

const summary = document.createElement('summary');
Expand All @@ -84,7 +87,7 @@ export class DetailsNode extends ElementNode {
event.preventDefault();
_editor.update(() => {
this.select();
})
});
});

el.append(summary);
Expand All @@ -96,6 +99,11 @@ export class DetailsNode extends ElementNode {

if (prevNode.__open !== this.__open) {
dom.toggleAttribute('open', this.__open);
if (this.__open) {
dom.removeAttribute('contenteditable');
} else {
dom.setAttribute('contenteditable', 'false');
}
}

return prevNode.__id !== this.__id
Expand Down Expand Up @@ -144,6 +152,7 @@ export class DetailsNode extends ElementNode {
}

element.removeAttribute('open');
element.removeAttribute('contenteditable');

return {element};
}
Expand All @@ -165,6 +174,14 @@ export class DetailsNode extends ElementNode {
return node;
}

shouldSelectDirectly(): boolean {
return true;
}

canBeEmpty(): boolean {
return false;
}

}

export function $createDetailsNode() {
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {dispatchKeydownEventForNode, initializeUnitTest} from "lexical/__tests__/utils";
import {$createDetailsNode, DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
import {$createParagraphNode, $getRoot, LexicalNode, ParagraphNode} from "lexical";
import {createTestContext} from "lexical/__tests__/utils";
import {$createDetailsNode} from "@lexical/rich-text/LexicalDetailsNode";

const editorConfig = Object.freeze({
namespace: '',
Expand All @@ -9,32 +8,28 @@ const editorConfig = Object.freeze({
});

describe('LexicalDetailsNode tests', () => {
initializeUnitTest((testEnv) => {
test('createDOM()', () => {
const {editor} = createTestContext();
let html!: string;

test('createDOM()', () => {
const {editor} = testEnv;
let html!: string;

editor.updateAndCommit(() => {
const details = $createDetailsNode();
html = details.createDOM(editorConfig, editor).outerHTML;
});

expect(html).toBe(`<details><summary contenteditable="false"></summary></details>`);
editor.updateAndCommit(() => {
const details = $createDetailsNode();
html = details.createDOM(editorConfig, editor).outerHTML;
});

test('exportDOM()', () => {
const {editor} = testEnv;
let html!: string;
expect(html).toBe(`<details contenteditable="false"><summary contenteditable="false"></summary></details>`);
});

editor.updateAndCommit(() => {
const details = $createDetailsNode();
html = (details.exportDOM(editor).element as HTMLElement).outerHTML;
});
test('exportDOM()', () => {
const {editor} = createTestContext();
let html!: string;

expect(html).toBe(`<details><summary></summary></details>`);
editor.updateAndCommit(() => {
const details = $createDetailsNode();
details.setSummary('Hello there<>!')
html = (details.exportDOM(editor).element as HTMLElement).outerHTML;
});


expect(html).toBe(`<details><summary>Hello there&lt;&gt;!</summary></details>`);
});
})
34 changes: 32 additions & 2 deletions resources/js/wysiwyg/services/keyboard-handling.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {$setInsetForSelection} from "../utils/lists";
import {$isListItemNode} from "@lexical/list";
import {$isDetailsNode, DetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
import {$isDiagramNode} from "../utils/diagrams";
import {$unwrapDetailsNode} from "../utils/details";

function isSingleSelectedNode(nodes: LexicalNode[]): boolean {
if (nodes.length === 1) {
Expand Down Expand Up @@ -172,6 +173,35 @@ function getDetailsScenario(editor: LexicalEditor): {
}
}

function unwrapDetailsNode(context: EditorUiContext, event: KeyboardEvent): boolean {
const selection = $getSelection();
const nodes = selection?.getNodes() || [];

if (nodes.length !== 1) {
return false;
}

const selectedNearestBlock = $getNearestNodeBlockParent(nodes[0]);
if (!selectedNearestBlock) {
return false;
}

const selectedParentBlock = selectedNearestBlock.getParent();
const selectRange = selection?.getStartEndPoints();

if (selectRange && $isDetailsNode(selectedParentBlock) && selectRange[0].offset === 0 && selectedNearestBlock.getIndexWithinParent() === 0) {
event.preventDefault();
context.editor.update(() => {
$unwrapDetailsNode(selectedParentBlock);
selectedNearestBlock.selectStart();
context.manager.triggerLayoutUpdate();
});
return true;
}

return false;
}

function $isSingleListItem(nodes: LexicalNode[]): boolean {
if (nodes.length !== 1) {
return false;
Expand Down Expand Up @@ -201,9 +231,9 @@ function handleInsetOnTab(editor: LexicalEditor, event: KeyboardEvent|null): boo
}

export function registerKeyboardHandling(context: EditorUiContext): () => void {
const unregisterBackspace = context.editor.registerCommand(KEY_BACKSPACE_COMMAND, (): boolean => {
const unregisterBackspace = context.editor.registerCommand(KEY_BACKSPACE_COMMAND, (event): boolean => {
deleteSingleSelectedNode(context.editor);
return false;
return unwrapDetailsNode(context, event);
}, COMMAND_PRIORITY_LOW);

const unregisterDelete = context.editor.registerCommand(KEY_DELETE_COMMAND, (): boolean => {
Expand Down
41 changes: 25 additions & 16 deletions resources/js/wysiwyg/services/mouse-handling.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import {EditorUiContext} from "../ui/framework/core";
import {
$createParagraphNode, $getRoot,
$getSelection,
$createParagraphNode, $getNearestNodeFromDOMNode, $getRoot,
$isDecoratorNode, CLICK_COMMAND,
COMMAND_PRIORITY_LOW, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND,
KEY_BACKSPACE_COMMAND,
KEY_DELETE_COMMAND,
KEY_ENTER_COMMAND, KEY_TAB_COMMAND,
LexicalEditor,
COMMAND_PRIORITY_LOW, ElementNode,
LexicalNode
} from "lexical";
import {$isImageNode} from "@lexical/rich-text/LexicalImageNode";
import {$isMediaNode} from "@lexical/rich-text/LexicalMediaNode";
import {getLastSelection} from "../utils/selection";
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";
import {$isTableNode} from "@lexical/table";
import {$isDetailsNode} from "@lexical/rich-text/LexicalDetailsNode";

function isHardToEscapeNode(node: LexicalNode): boolean {
return $isDecoratorNode(node) || $isImageNode(node) || $isMediaNode(node) || $isDiagramNode(node) || $isTableNode(node);
return $isDecoratorNode(node)
|| $isImageNode(node)
|| $isMediaNode(node)
|| $isDiagramNode(node)
|| $isTableNode(node)
|| $isDetailsNode(node);
}

function $getContextNode(event: MouseEvent): ElementNode {
if (event.target instanceof HTMLElement) {
const nearestDetails = event.target.closest('details');
if (nearestDetails) {
const detailsNode = $getNearestNodeFromDOMNode(nearestDetails);
if ($isDetailsNode(detailsNode)) {
return detailsNode;
}
}
}
return $getRoot();
}

function insertBelowLastNode(context: EditorUiContext, event: MouseEvent): boolean {
const lastNode = $getRoot().getLastChild();
const contextNode = $getContextNode(event);
const lastNode = contextNode.getLastChild();
if (!lastNode || !isHardToEscapeNode(lastNode)) {
return false;
}
Expand All @@ -40,7 +50,7 @@ function insertBelowLastNode(context: EditorUiContext, event: MouseEvent): boole
if (isClickBelow) {
context.editor.update(() => {
const newNode = $createParagraphNode();
$getRoot().append(newNode);
contextNode.append(newNode);
newNode.select();
});
return true;
Expand All @@ -49,7 +59,6 @@ function insertBelowLastNode(context: EditorUiContext, event: MouseEvent): boole
return false;
}


export function registerMouseHandling(context: EditorUiContext): () => void {
const unregisterClick = context.editor.registerCommand(CLICK_COMMAND, (event): boolean => {
insertBelowLastNode(context, event);
Expand Down
Loading

Back | FazBrowse Home | New Git URL