mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Added selection to state for aligned reading
Connected up to work with image form
This commit is contained in:
parent
e959c468f6
commit
0722960260
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user