1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-07-30 04:23:11 +03:00

Lexical: Range of bug fixes, Updated lexical version

- Updated selection change detection to be more accurate
- Added UI refresh for extra actions
- Fixed remove link deleting contents
This commit is contained in:
Dan Brown
2024-09-08 15:54:59 +01:00
parent bed2c29a33
commit 16518a4f89
8 changed files with 195 additions and 173 deletions

View File

@ -1,4 +1,4 @@
import {createEditor, CreateEditorArgs, LexicalEditor} from 'lexical';
import {$getSelection, createEditor, CreateEditorArgs, isCurrentlyReadOnlyMode, LexicalEditor} from 'lexical';
import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {registerRichText} from '@lexical/rich-text';
import {mergeRegister} from '@lexical/utils';
@ -69,7 +69,19 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
}
let changeFromLoading = true;
editor.registerUpdateListener(({editorState, dirtyElements, dirtyLeaves}) => {
editor.registerUpdateListener(({dirtyElements, dirtyLeaves, editorState, prevEditorState}) => {
// Watch for selection changes to update the UI on change
// Used to be done via SELECTION_CHANGE_COMMAND but this would not always emit
// for all selection changes, so this proved more reliable.
const selectionChange = !(prevEditorState._selection?.is(editorState._selection) || false);
if (selectionChange) {
editor.update(() => {
const selection = $getSelection();
context.manager.triggerStateUpdate({
editor, selection,
});
});
}
// Emit change event to component system (for draft detection) on actual user content change
if (dirtyElements.size > 0 || dirtyLeaves.size > 0) {

View File

@ -40,7 +40,7 @@ export class ImageNode extends ElementNode {
alt: node.__alt,
width: node.__width,
height: node.__height,
});
}, node.__key);
newNode.__alignment = node.__alignment;
return newNode;
}

View File

@ -16,6 +16,4 @@
## Bugs
- Removing link around image via button deletes image, not just link
- `SELECTION_CHANGE_COMMAND` not fired when clicking out of a table cell. Prevents toolbar hiding on table unselect.
- Template drag/drop not handled when outside core editor area (ignored in margin area).

View File

@ -19,6 +19,7 @@ export const undo: EditorButtonDefinition = {
icon: undoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(UNDO_COMMAND, undefined);
context.manager.triggerFutureStateRefresh();
},
isActive(selection: BaseSelection|null): boolean {
return false;
@ -38,6 +39,7 @@ export const redo: EditorButtonDefinition = {
icon: redoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(REDO_COMMAND, undefined);
context.manager.triggerFutureStateRefresh();
},
isActive(selection: BaseSelection|null): boolean {
return false;

View File

@ -6,7 +6,7 @@ import {
$getRoot,
$getSelection, $insertNodes,
BaseSelection,
ElementNode
ElementNode, isCurrentlyReadOnlyMode
} from "lexical";
import {$isLinkNode, LinkNode} from "@lexical/link";
import unlinkIcon from "@icons/editor/unlink.svg";
@ -54,16 +54,17 @@ export const unlink: EditorButtonDefinition = {
context.editor.update(() => {
const selection = getLastSelection(context.editor);
const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode | null;
const selectionPoints = selection?.getStartEndPoints();
if (selectedLink) {
const newNode = $createTextNode(selectedLink.getTextContent());
selectedLink.replace(newNode);
if (selectionPoints?.length === 2) {
newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
} else {
newNode.select();
const contents = selectedLink.getChildren().reverse();
for (const child of contents) {
selectedLink.insertAfter(child);
}
selectedLink.remove();
contents[contents.length - 1].selectStart();
context.manager.triggerFutureStateRefresh();
}
});
},

View File

@ -108,7 +108,7 @@ export class EditorUIManager {
this.contextToolbarDefinitionsByKey[key] = definition;
}
protected triggerStateUpdate(update: EditorUiStateUpdate): void {
triggerStateUpdate(update: EditorUiStateUpdate): void {
setLastSelection(update.editor, update.selection);
this.toolbar?.updateState(update);
this.updateContextToolbars(update);
@ -120,9 +120,20 @@ export class EditorUIManager {
triggerStateRefresh(): void {
const editor = this.getContext().editor;
this.triggerStateUpdate({
const update = {
editor,
selection: getLastSelection(editor),
};
this.triggerStateUpdate(update);
this.updateContextToolbars(update);
}
triggerFutureStateRefresh(): void {
requestAnimationFrame(() => {
this.getContext().editor.getEditorState().read(() => {
this.triggerStateRefresh();
});
});
}
@ -195,15 +206,6 @@ export class EditorUIManager {
}
protected setupEditor(editor: LexicalEditor) {
// Update button states on editor selection change
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
this.triggerStateUpdate({
editor: editor,
selection: $getSelection(),
});
return false;
}, COMMAND_PRIORITY_LOW);
// Register our DOM decorate listener with the editor
const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
editor.getEditorState().read(() => {