Lexical: Added selection to state for aligned reading

Connected up to work with image form
This commit is contained in:
Dan Brown 2024-06-05 18:43:42 +01:00
parent e959c468f6
commit 0722960260
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
7 changed files with 99 additions and 5 deletions

View File

@ -57,6 +57,16 @@ export class ImageNode extends DecoratorNode<EditorDecoratorAdapter> {
} }
} }
setSrc(src: string): void {
const self = this.getWritable();
self.__src = src;
}
getSrc(): string {
const self = this.getLatest();
return self.__src;
}
setAltText(altText: string): void { setAltText(altText: string): void {
const self = this.getWritable(); const self = this.getWritable();
self.__alt = altText; self.__alt = altText;

View File

@ -88,6 +88,7 @@ export class ImageDecorator extends EditorDecorator {
let startingHeight = element.clientHeight; let startingHeight = element.clientHeight;
let startingRatio = startingWidth / startingHeight; let startingRatio = startingWidth / startingHeight;
let hasHeight = false; let hasHeight = false;
let firstChange = true;
context.editor.getEditorState().read(() => { context.editor.getEditorState().read(() => {
startingWidth = node.getWidth() || startingWidth; startingWidth = node.getWidth() || startingWidth;
startingHeight = node.getHeight() || startingHeight; startingHeight = node.getHeight() || startingHeight;
@ -109,7 +110,7 @@ export class ImageDecorator extends EditorDecorator {
if (flipYChange) { if (flipYChange) {
yChange = 0 - yChange; yChange = 0 - yChange;
} }
const balancedChange = Math.sqrt(Math.pow(xChange, 2) + Math.pow(yChange, 2)); const balancedChange = Math.sqrt(Math.pow(Math.abs(xChange), 2) + Math.pow(Math.abs(yChange), 2));
const increase = xChange + yChange > 0; const increase = xChange + yChange > 0;
const directedChange = increase ? balancedChange : 0-balancedChange; const directedChange = increase ? balancedChange : 0-balancedChange;
const newWidth = Math.max(5, Math.round(startingWidth + directedChange)); const newWidth = Math.max(5, Math.round(startingWidth + directedChange));
@ -118,11 +119,13 @@ export class ImageDecorator extends EditorDecorator {
newHeight = newWidth * startingRatio; newHeight = newWidth * startingRatio;
} }
const updateOptions = firstChange ? {} : {tag: 'history-merge'};
context.editor.update(() => { context.editor.update(() => {
const node = this.getNode() as ImageNode; const node = this.getNode() as ImageNode;
node.setWidth(newWidth); node.setWidth(newWidth);
node.setHeight(newHeight); node.setHeight(newHeight);
}); }, updateOptions);
firstChange = false;
}; };
const mouseUpListener = (event: MouseEvent) => { const mouseUpListener = (event: MouseEvent) => {

View File

@ -25,6 +25,7 @@ import {
} from "@lexical/rich-text"; } from "@lexical/rich-text";
import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link"; import {$isLinkNode, $toggleLink, LinkNode} from "@lexical/link";
import {EditorUiContext} from "../framework/core"; import {EditorUiContext} from "../framework/core";
import {$isImageNode, ImageNode} from "../../nodes/image";
export const undo: EditorButtonDefinition = { export const undo: EditorButtonDefinition = {
label: 'Undo', label: 'Undo',
@ -168,3 +169,35 @@ export const link: EditorButtonDefinition = {
} }
}; };
export const image: EditorButtonDefinition = {
label: 'Insert/Edit Image',
action(context: EditorUiContext) {
const imageModal = context.manager.createModal('image');
const selection = context.lastSelection;
const selectedImage = getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
context.editor.getEditorState().read(() => {
let formDefaults = {};
if (selectedImage) {
formDefaults = {
src: selectedImage.getSrc(),
alt: selectedImage.getAltText(),
height: selectedImage.getHeight(),
width: selectedImage.getWidth(),
}
context.editor.update(() => {
const selection = $createNodeSelection();
selection.add(selectedImage.getKey());
$setSelection(selection);
});
}
imageModal.show(formDefaults);
});
},
isActive(selection: BaseSelection|null): boolean {
return selectionContainsNodeType(selection, $isImageNode);
}
};

View File

@ -2,6 +2,7 @@ import {EditorFormDefinition, EditorSelectFormFieldDefinition} from "../framewor
import {EditorUiContext} from "../framework/core"; import {EditorUiContext} from "../framework/core";
import {$createLinkNode} from "@lexical/link"; import {$createLinkNode} from "@lexical/link";
import {$createTextNode, $getSelection} from "lexical"; import {$createTextNode, $getSelection} from "lexical";
import {$createImageNode} from "../../nodes/image";
export const link: EditorFormDefinition = { export const link: EditorFormDefinition = {
@ -47,4 +48,42 @@ export const link: EditorFormDefinition = {
} }
} as EditorSelectFormFieldDefinition, } as EditorSelectFormFieldDefinition,
], ],
};
export const image: EditorFormDefinition = {
submitText: 'Apply',
action(formData, context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
const imageNode = $createImageNode(formData.get('src')?.toString() || '', {
alt: formData.get('alt')?.toString() || '',
height: Number(formData.get('height')?.toString() || '0'),
width: Number(formData.get('width')?.toString() || '0'),
});
selection?.insertNodes([imageNode]);
});
return true;
},
fields: [
{
label: 'Source',
name: 'src',
type: 'text',
},
{
label: 'Alternative description',
name: 'alt',
type: 'text',
},
{
label: 'Width',
name: 'width',
type: 'text',
},
{
label: 'Height',
name: 'height',
type: 'text',
},
],
}; };

View File

@ -10,6 +10,7 @@ export type EditorUiContext = {
editor: LexicalEditor, editor: LexicalEditor,
translate: (text: string) => string, translate: (text: string) => string,
manager: EditorUIManager, manager: EditorUIManager,
lastSelection: BaseSelection|null,
}; };
export abstract class EditorUiElement { export abstract class EditorUiElement {

View File

@ -6,18 +6,20 @@ import {
} from "lexical"; } from "lexical";
import {getMainEditorFullToolbar} from "./toolbars"; import {getMainEditorFullToolbar} from "./toolbars";
import {EditorUIManager} from "./framework/manager"; import {EditorUIManager} from "./framework/manager";
import {link as linkFormDefinition} from "./defaults/form-definitions"; import {image as imageFormDefinition, link as linkFormDefinition} from "./defaults/form-definitions";
import {DecoratorListener} from "lexical/LexicalEditor"; import {DecoratorListener} from "lexical/LexicalEditor";
import type {NodeKey} from "lexical/LexicalNode"; import type {NodeKey} from "lexical/LexicalNode";
import {EditorDecoratorAdapter} from "./framework/decorator"; import {EditorDecoratorAdapter} from "./framework/decorator";
import {ImageDecorator} from "./decorators/image"; import {ImageDecorator} from "./decorators/image";
import {EditorUiContext} from "./framework/core";
export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) { export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
const manager = new EditorUIManager(); const manager = new EditorUIManager();
const context = { const context: EditorUiContext = {
editor, editor,
manager, manager,
translate: (text: string): string => text, translate: (text: string): string => text,
lastSelection: null,
}; };
manager.setContext(context); manager.setContext(context);
@ -31,6 +33,10 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
title: 'Insert/Edit link', title: 'Insert/Edit link',
form: linkFormDefinition, form: linkFormDefinition,
}); });
manager.registerModal('image', {
title: 'Insert/Edit Image',
form: imageFormDefinition
})
// Register decorator listener // Register decorator listener
// Maybe move to manager? // Maybe move to manager?
@ -54,6 +60,7 @@ export function buildEditorUI(element: HTMLElement, editor: LexicalEditor) {
editor.registerCommand(SELECTION_CHANGE_COMMAND, () => { editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
const selection = $getSelection(); const selection = $getSelection();
toolbar.updateState({editor, selection}); toolbar.updateState({editor, selection});
context.lastSelection = selection;
return false; return false;
}, COMMAND_PRIORITY_LOW); }, COMMAND_PRIORITY_LOW);
} }

View File

@ -2,7 +2,7 @@ import {EditorButton, FormatPreviewButton} from "./framework/buttons";
import { import {
blockquote, bold, code, blockquote, bold, code,
dangerCallout, dangerCallout,
h2, h3, h4, h5, h2, h3, h4, h5, image,
infoCallout, italic, link, paragraph, infoCallout, italic, link, paragraph,
redo, strikethrough, subscript, redo, strikethrough, subscript,
successCallout, superscript, underline, successCallout, superscript, underline,
@ -40,5 +40,6 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
new EditorButton(code), new EditorButton(code),
new EditorButton(link), new EditorButton(link),
new EditorButton(image),
]); ]);
} }