mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Integrated image manager to image button/form
This commit is contained in:
parent
ec965f28c0
commit
accf2565a0
1
resources/icons/editor/image-search.svg
Normal file
1
resources/icons/editor/image-search.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h200v80H200v560h560v-214l80 80v134q0 33-23.5 56.5T760-120H200Zm40-160 120-160 90 120 120-160 150 200H240Zm622-144L738-548q-21 14-45 21t-51 7q-74 0-126-52.5T464-700q0-75 52.5-127.5T644-880q75 0 127.5 52.5T824-700q0 27-8 52t-20 46l122 122-56 56ZM644-600q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Z"/></svg>
|
After Width: | Height: | Size: 466 B |
@ -3,15 +3,12 @@ import {
|
|||||||
DOMConversion,
|
DOMConversion,
|
||||||
DOMConversionMap,
|
DOMConversionMap,
|
||||||
DOMConversionOutput,
|
DOMConversionOutput,
|
||||||
LexicalEditor, LexicalNode,
|
LexicalEditor,
|
||||||
SerializedLexicalNode,
|
SerializedLexicalNode,
|
||||||
Spread
|
Spread
|
||||||
} from "lexical";
|
} from "lexical";
|
||||||
import type {EditorConfig} from "lexical/LexicalEditor";
|
import type {EditorConfig} from "lexical/LexicalEditor";
|
||||||
import {EditorDecoratorAdapter} from "../ui/framework/decorator";
|
import {EditorDecoratorAdapter} from "../ui/framework/decorator";
|
||||||
import * as DrawIO from '../../services/drawio';
|
|
||||||
import {EditorUiContext} from "../ui/framework/core";
|
|
||||||
import {HttpError} from "../../services/http";
|
|
||||||
import {el} from "../utils/dom";
|
import {el} from "../utils/dom";
|
||||||
|
|
||||||
export type SerializedDiagramNode = Spread<{
|
export type SerializedDiagramNode = Spread<{
|
||||||
@ -156,69 +153,3 @@ export class DiagramNode extends DecoratorNode<EditorDecoratorAdapter> {
|
|||||||
export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
|
export function $createDiagramNode(drawingId: string = '', drawingUrl: string = ''): DiagramNode {
|
||||||
return new DiagramNode(drawingId, drawingUrl);
|
return new DiagramNode(drawingId, drawingUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode {
|
|
||||||
return node instanceof DiagramNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function handleUploadError(error: HttpError, context: EditorUiContext): void {
|
|
||||||
if (error.status === 413) {
|
|
||||||
window.$events.emit('error', context.options.translations.serverUploadLimitText || '');
|
|
||||||
} else {
|
|
||||||
window.$events.emit('error', context.options.translations.imageUploadErrorText || '');
|
|
||||||
}
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDiagramIdFromNode(editor: LexicalEditor, node: DiagramNode): Promise<string> {
|
|
||||||
const drawingId = await new Promise<string>((res, rej) => {
|
|
||||||
editor.getEditorState().read(() => {
|
|
||||||
const {id: drawingId} = node.getDrawingIdAndUrl();
|
|
||||||
res(drawingId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return drawingId || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateDrawingNodeFromData(context: EditorUiContext, node: DiagramNode, pngData: string, isNew: boolean): Promise<void> {
|
|
||||||
DrawIO.close();
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
const loadingImage: string = window.baseUrl('/loading.gif');
|
|
||||||
context.editor.update(() => {
|
|
||||||
node.setDrawingIdAndUrl('', loadingImage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const img = await DrawIO.upload(pngData, context.options.pageId);
|
|
||||||
context.editor.update(() => {
|
|
||||||
node.setDrawingIdAndUrl(String(img.id), img.url);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof HttpError) {
|
|
||||||
handleUploadError(err, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
context.editor.update(() => {
|
|
||||||
node.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Failed to save image with error: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function $openDrawingEditorForNode(context: EditorUiContext, node: DiagramNode): void {
|
|
||||||
let isNew = false;
|
|
||||||
DrawIO.show(context.options.drawioUrl, async () => {
|
|
||||||
const drawingId = await loadDiagramIdFromNode(context.editor, node);
|
|
||||||
isNew = !drawingId;
|
|
||||||
return isNew ? '' : DrawIO.load(drawingId);
|
|
||||||
}, async (pngData: string) => {
|
|
||||||
return updateDrawingNodeFromData(context, node, pngData, isNew);
|
|
||||||
});
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## In progress
|
## In progress
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
## Main Todo
|
## Main Todo
|
||||||
|
|
||||||
@ -12,8 +12,6 @@
|
|||||||
- Keyboard shortcuts support
|
- Keyboard shortcuts support
|
||||||
- Link popup menu for cross-content reference
|
- Link popup menu for cross-content reference
|
||||||
- Link heading-based ID reference menu
|
- Link heading-based ID reference menu
|
||||||
- Image gallery integration for insert
|
|
||||||
- Image gallery integration for form
|
|
||||||
- Drawing gallery integration
|
- Drawing gallery integration
|
||||||
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
|
||||||
- Media resize support (like images)
|
- Media resize support (like images)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import {EditorDecorator} from "../framework/decorator";
|
import {EditorDecorator} from "../framework/decorator";
|
||||||
import {EditorUiContext} from "../framework/core";
|
import {EditorUiContext} from "../framework/core";
|
||||||
import {BaseSelection} from "lexical";
|
import {BaseSelection} from "lexical";
|
||||||
import {$openDrawingEditorForNode, DiagramNode} from "../../nodes/diagram";
|
import {DiagramNode} from "../../nodes/diagram";
|
||||||
import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection";
|
import {$selectionContainsNode, $selectSingleNode} from "../../utils/selection";
|
||||||
|
import {$openDrawingEditorForNode} from "../../utils/diagrams";
|
||||||
|
|
||||||
|
|
||||||
export class DiagramDecorator extends EditorDecorator {
|
export class DiagramDecorator extends EditorDecorator {
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
$createNodeSelection,
|
$createNodeSelection,
|
||||||
$createTextNode,
|
$createTextNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
$getSelection,
|
$getSelection, $insertNodes,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
BaseSelection,
|
BaseSelection,
|
||||||
ElementNode
|
ElementNode
|
||||||
@ -20,7 +20,7 @@ import codeBlockIcon from "@icons/editor/code-block.svg";
|
|||||||
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
|
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
|
||||||
import editIcon from "@icons/edit.svg";
|
import editIcon from "@icons/edit.svg";
|
||||||
import diagramIcon from "@icons/editor/diagram.svg";
|
import diagramIcon from "@icons/editor/diagram.svg";
|
||||||
import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../../nodes/diagram";
|
import {$createDiagramNode, DiagramNode} from "../../../nodes/diagram";
|
||||||
import detailsIcon from "@icons/editor/details.svg";
|
import detailsIcon from "@icons/editor/details.svg";
|
||||||
import mediaIcon from "@icons/editor/media.svg";
|
import mediaIcon from "@icons/editor/media.svg";
|
||||||
import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details";
|
import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details";
|
||||||
@ -30,6 +30,9 @@ import {
|
|||||||
$insertNewBlockNodeAtSelection,
|
$insertNewBlockNodeAtSelection,
|
||||||
$selectionContainsNodeType
|
$selectionContainsNodeType
|
||||||
} from "../../../utils/selection";
|
} from "../../../utils/selection";
|
||||||
|
import {$isDiagramNode, $openDrawingEditorForNode} from "../../../utils/diagrams";
|
||||||
|
import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
|
||||||
|
import {$showImageForm} from "../forms/objects";
|
||||||
|
|
||||||
export const link: EditorButtonDefinition = {
|
export const link: EditorButtonDefinition = {
|
||||||
label: 'Insert/edit link',
|
label: 'Insert/edit link',
|
||||||
@ -94,28 +97,19 @@ export const image: EditorButtonDefinition = {
|
|||||||
label: 'Insert/Edit Image',
|
label: 'Insert/Edit Image',
|
||||||
icon: imageIcon,
|
icon: imageIcon,
|
||||||
action(context: EditorUiContext) {
|
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(() => {
|
context.editor.getEditorState().read(() => {
|
||||||
let formDefaults = {};
|
const selectedImage = $getNodeFromSelection(context.lastSelection, $isImageNode) as ImageNode | null;
|
||||||
if (selectedImage) {
|
if (selectedImage) {
|
||||||
formDefaults = {
|
$showImageForm(selectedImage, context);
|
||||||
src: selectedImage.getSrc(),
|
return;
|
||||||
alt: selectedImage.getAltText(),
|
|
||||||
height: selectedImage.getHeight(),
|
|
||||||
width: selectedImage.getWidth(),
|
|
||||||
}
|
|
||||||
|
|
||||||
context.editor.update(() => {
|
|
||||||
const selection = $createNodeSelection();
|
|
||||||
selection.add(selectedImage.getKey());
|
|
||||||
$setSelection(selection);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imageModal.show(formDefaults);
|
showImageManager((image) => {
|
||||||
|
context.editor.update(() => {
|
||||||
|
const link = $createLinkedImageNodeFromImageData(image);
|
||||||
|
$insertNodes([link]);
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isActive(selection: BaseSelection | null): boolean {
|
isActive(selection: BaseSelection | null): boolean {
|
||||||
|
@ -1,31 +1,78 @@
|
|||||||
import {EditorFormDefinition, EditorFormTabs, EditorSelectFormFieldDefinition} from "../../framework/forms";
|
import {
|
||||||
|
EditorFormDefinition,
|
||||||
|
EditorFormField,
|
||||||
|
EditorFormTabs,
|
||||||
|
EditorSelectFormFieldDefinition
|
||||||
|
} from "../../framework/forms";
|
||||||
import {EditorUiContext} from "../../framework/core";
|
import {EditorUiContext} from "../../framework/core";
|
||||||
import {$createTextNode, $getSelection} from "lexical";
|
import {$createTextNode, $getSelection} from "lexical";
|
||||||
import {$createImageNode} from "../../../nodes/image";
|
import {$isImageNode, ImageNode} from "../../../nodes/image";
|
||||||
import {$createLinkNode} from "@lexical/link";
|
import {$createLinkNode} from "@lexical/link";
|
||||||
import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media";
|
import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media";
|
||||||
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
import {$insertNodeToNearestRoot} from "@lexical/utils";
|
||||||
import {$getNodeFromSelection} from "../../../utils/selection";
|
import {$getNodeFromSelection} from "../../../utils/selection";
|
||||||
|
import {EditorFormModal} from "../../framework/modals";
|
||||||
|
import {EditorActionField} from "../../framework/blocks/action-field";
|
||||||
|
import {EditorButton} from "../../framework/buttons";
|
||||||
|
import {showImageManager} from "../../../utils/images";
|
||||||
|
import searchImageIcon from "@icons/editor/image-search.svg";
|
||||||
|
|
||||||
|
export function $showImageForm(image: ImageNode, context: EditorUiContext) {
|
||||||
|
const imageModal: EditorFormModal = context.manager.createModal('image');
|
||||||
|
const height = image.getHeight();
|
||||||
|
const width = image.getWidth();
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
src: image.getSrc(),
|
||||||
|
alt: image.getAltText(),
|
||||||
|
height: height === 0 ? '' : String(height),
|
||||||
|
width: width === 0 ? '' : String(width),
|
||||||
|
};
|
||||||
|
|
||||||
|
imageModal.show(formData);
|
||||||
|
}
|
||||||
|
|
||||||
export const image: EditorFormDefinition = {
|
export const image: EditorFormDefinition = {
|
||||||
submitText: 'Apply',
|
submitText: 'Apply',
|
||||||
async action(formData, context: EditorUiContext) {
|
async action(formData, context: EditorUiContext) {
|
||||||
context.editor.update(() => {
|
context.editor.update(() => {
|
||||||
const selection = $getSelection();
|
const selectedImage = $getNodeFromSelection(context.lastSelection, $isImageNode);
|
||||||
const imageNode = $createImageNode(formData.get('src')?.toString() || '', {
|
if ($isImageNode(selectedImage)) {
|
||||||
alt: formData.get('alt')?.toString() || '',
|
selectedImage.setSrc(formData.get('src')?.toString() || '');
|
||||||
height: Number(formData.get('height')?.toString() || '0'),
|
selectedImage.setAltText(formData.get('alt')?.toString() || '');
|
||||||
width: Number(formData.get('width')?.toString() || '0'),
|
|
||||||
});
|
selectedImage.setWidth(Number(formData.get('width')?.toString() || '0'));
|
||||||
selection?.insertNodes([imageNode]);
|
selectedImage.setHeight(Number(formData.get('height')?.toString() || '0'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: 'Source',
|
build() {
|
||||||
name: 'src',
|
return new EditorActionField(
|
||||||
type: 'text',
|
new EditorFormField({
|
||||||
|
label: 'Source',
|
||||||
|
name: 'src',
|
||||||
|
type: 'text',
|
||||||
|
}),
|
||||||
|
new EditorButton({
|
||||||
|
label: 'Browse files',
|
||||||
|
icon: searchImageIcon,
|
||||||
|
action(context: EditorUiContext) {
|
||||||
|
showImageManager((image) => {
|
||||||
|
const modal = context.manager.getActiveModal('image');
|
||||||
|
if (modal) {
|
||||||
|
modal.getForm().setValues({
|
||||||
|
src: image.thumbs?.display || image.url,
|
||||||
|
alt: image.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Alternative description',
|
label: 'Alternative description',
|
||||||
|
26
resources/js/wysiwyg/ui/framework/blocks/action-field.ts
Normal file
26
resources/js/wysiwyg/ui/framework/blocks/action-field.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {EditorContainerUiElement, EditorUiElement} from "../core";
|
||||||
|
import {el} from "../../../utils/dom";
|
||||||
|
import {EditorFormField} from "../forms";
|
||||||
|
import {EditorButton} from "../buttons";
|
||||||
|
|
||||||
|
|
||||||
|
export class EditorActionField extends EditorContainerUiElement {
|
||||||
|
protected input: EditorFormField;
|
||||||
|
protected action: EditorButton;
|
||||||
|
|
||||||
|
constructor(input: EditorFormField, action: EditorButton) {
|
||||||
|
super([input, action]);
|
||||||
|
|
||||||
|
this.input = input;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDOM(): HTMLElement {
|
||||||
|
return el('div', {
|
||||||
|
class: 'editor-action-input-container',
|
||||||
|
}, [
|
||||||
|
this.input.getDOMElement(),
|
||||||
|
this.action.getDOMElement(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ export type SelectionChangeHandler = (selection: BaseSelection|null) => void;
|
|||||||
export class EditorUIManager {
|
export class EditorUIManager {
|
||||||
|
|
||||||
protected modalDefinitionsByKey: Record<string, EditorFormModalDefinition> = {};
|
protected modalDefinitionsByKey: Record<string, EditorFormModalDefinition> = {};
|
||||||
|
protected activeModalsByKey: Record<string, EditorFormModal> = {};
|
||||||
protected decoratorConstructorsByType: Record<string, typeof EditorDecorator> = {};
|
protected decoratorConstructorsByType: Record<string, typeof EditorDecorator> = {};
|
||||||
protected decoratorInstancesByNodeKey: Record<string, EditorDecorator> = {};
|
protected decoratorInstancesByNodeKey: Record<string, EditorDecorator> = {};
|
||||||
protected context: EditorUiContext|null = null;
|
protected context: EditorUiContext|null = null;
|
||||||
@ -50,12 +51,24 @@ export class EditorUIManager {
|
|||||||
throw new Error(`Attempted to show modal of key [${key}] but no modal registered for that key`);
|
throw new Error(`Attempted to show modal of key [${key}] but no modal registered for that key`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = new EditorFormModal(modalDefinition);
|
const modal = new EditorFormModal(modalDefinition, key);
|
||||||
modal.setContext(this.getContext());
|
modal.setContext(this.getContext());
|
||||||
|
|
||||||
return modal;
|
return modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setModalActive(key: string, modal: EditorFormModal): void {
|
||||||
|
this.activeModalsByKey[key] = modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
setModalInactive(key: string): void {
|
||||||
|
delete this.activeModalsByKey[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveModal(key: string): EditorFormModal|null {
|
||||||
|
return this.activeModalsByKey[key];
|
||||||
|
}
|
||||||
|
|
||||||
registerDecoratorType(type: string, decorator: typeof EditorDecorator) {
|
registerDecoratorType(type: string, decorator: typeof EditorDecorator) {
|
||||||
this.decoratorConstructorsByType[type] = decorator;
|
this.decoratorConstructorsByType[type] = decorator;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,12 @@ export interface EditorFormModalDefinition extends EditorModalDefinition {
|
|||||||
|
|
||||||
export class EditorFormModal extends EditorContainerUiElement {
|
export class EditorFormModal extends EditorContainerUiElement {
|
||||||
protected definition: EditorFormModalDefinition;
|
protected definition: EditorFormModalDefinition;
|
||||||
|
protected key: string;
|
||||||
|
|
||||||
constructor(definition: EditorFormModalDefinition) {
|
constructor(definition: EditorFormModalDefinition, key: string) {
|
||||||
super([new EditorForm(definition.form)]);
|
super([new EditorForm(definition.form)]);
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
show(defaultValues: Record<string, string>) {
|
show(defaultValues: Record<string, string>) {
|
||||||
@ -26,13 +28,16 @@ export class EditorFormModal extends EditorContainerUiElement {
|
|||||||
const form = this.getForm();
|
const form = this.getForm();
|
||||||
form.setValues(defaultValues);
|
form.setValues(defaultValues);
|
||||||
form.setOnCancel(this.hide.bind(this));
|
form.setOnCancel(this.hide.bind(this));
|
||||||
|
|
||||||
|
this.getContext().manager.setModalActive(this.key, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.getDOMElement().remove();
|
this.getDOMElement().remove();
|
||||||
|
this.getContext().manager.setModalInactive(this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getForm(): EditorForm {
|
getForm(): EditorForm {
|
||||||
return this.children[0] as EditorForm;
|
return this.children[0] as EditorForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
70
resources/js/wysiwyg/utils/diagrams.ts
Normal file
70
resources/js/wysiwyg/utils/diagrams.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import {LexicalEditor, LexicalNode} from "lexical";
|
||||||
|
import {HttpError} from "../../services/http";
|
||||||
|
import {EditorUiContext} from "../ui/framework/core";
|
||||||
|
import * as DrawIO from "../../services/drawio";
|
||||||
|
import {DiagramNode} from "../nodes/diagram";
|
||||||
|
|
||||||
|
export function $isDiagramNode(node: LexicalNode | null | undefined): node is DiagramNode {
|
||||||
|
return node instanceof DiagramNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUploadError(error: HttpError, context: EditorUiContext): void {
|
||||||
|
if (error.status === 413) {
|
||||||
|
window.$events.emit('error', context.options.translations.serverUploadLimitText || '');
|
||||||
|
} else {
|
||||||
|
window.$events.emit('error', context.options.translations.imageUploadErrorText || '');
|
||||||
|
}
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDiagramIdFromNode(editor: LexicalEditor, node: DiagramNode): Promise<string> {
|
||||||
|
const drawingId = await new Promise<string>((res, rej) => {
|
||||||
|
editor.getEditorState().read(() => {
|
||||||
|
const {id: drawingId} = node.getDrawingIdAndUrl();
|
||||||
|
res(drawingId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return drawingId || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDrawingNodeFromData(context: EditorUiContext, node: DiagramNode, pngData: string, isNew: boolean): Promise<void> {
|
||||||
|
DrawIO.close();
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
const loadingImage: string = window.baseUrl('/loading.gif');
|
||||||
|
context.editor.update(() => {
|
||||||
|
node.setDrawingIdAndUrl('', loadingImage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const img = await DrawIO.upload(pngData, context.options.pageId);
|
||||||
|
context.editor.update(() => {
|
||||||
|
node.setDrawingIdAndUrl(String(img.id), img.url);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
handleUploadError(err, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
context.editor.update(() => {
|
||||||
|
node.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to save image with error: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function $openDrawingEditorForNode(context: EditorUiContext, node: DiagramNode): void {
|
||||||
|
let isNew = false;
|
||||||
|
DrawIO.show(context.options.drawioUrl, async () => {
|
||||||
|
const drawingId = await loadDiagramIdFromNode(context.editor, node);
|
||||||
|
isNew = !drawingId;
|
||||||
|
return isNew ? '' : DrawIO.load(drawingId);
|
||||||
|
}, async (pngData: string) => {
|
||||||
|
return updateDrawingNodeFromData(context, node, pngData, isNew);
|
||||||
|
});
|
||||||
|
}
|
26
resources/js/wysiwyg/utils/images.ts
Normal file
26
resources/js/wysiwyg/utils/images.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {ImageManager} from "../../components";
|
||||||
|
import {$createImageNode} from "../nodes/image";
|
||||||
|
import {$createLinkNode, LinkNode} from "@lexical/link";
|
||||||
|
|
||||||
|
type EditorImageData = {
|
||||||
|
url: string;
|
||||||
|
thumbs?: {display: string};
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function showImageManager(callback: (image: EditorImageData) => any) {
|
||||||
|
const imageManager: ImageManager = window.$components.first('image-manager') as ImageManager;
|
||||||
|
imageManager.show((image: EditorImageData) => {
|
||||||
|
callback(image);
|
||||||
|
}, 'gallery');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function $createLinkedImageNodeFromImageData(image: EditorImageData): LinkNode {
|
||||||
|
const url = image.thumbs?.display || image.url;
|
||||||
|
const linkNode = $createLinkNode(url, {target: '_blank'});
|
||||||
|
const imageNode = $createImageNode(url, {
|
||||||
|
alt: image.name
|
||||||
|
});
|
||||||
|
linkNode.append(imageNode);
|
||||||
|
return linkNode;
|
||||||
|
}
|
@ -479,6 +479,16 @@ textarea.editor-form-field-input {
|
|||||||
.editor-form-tab-contents {
|
.editor-form-tab-contents {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
.editor-action-input-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .1rem;
|
||||||
|
.editor-button {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Editor theme styles
|
// Editor theme styles
|
||||||
.editor-theme-bold {
|
.editor-theme-bold {
|
||||||
|
Loading…
Reference in New Issue
Block a user