Skip to content

Commit

Permalink
feat(text-editor): add support for pasting inline images
Browse files Browse the repository at this point in the history
  • Loading branch information
FredrikWallstrom authored and adrianschmidt committed Mar 12, 2025
1 parent 30f8df1 commit 1278215
Show file tree
Hide file tree
Showing 22 changed files with 1,222 additions and 112 deletions.
34 changes: 34 additions & 0 deletions etc/lime-elements.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,32 @@ interface Image_2 {
}
export { Image_2 as Image }

// @alpha (undocumented)
export interface ImageInfo {
fileInfoId: string;
src: string;
state: ImageState;
}

// @alpha (undocumented)
export interface ImageInserter {
// (undocumented)
fileInfo: FileInfo;
insertFailedThumbnail: () => void;
insertImage: (src?: string) => void;
insertThumbnail: () => void;
}

// @alpha (undocumented)
export enum ImageState {
// (undocumented)
FAILED = "failed",
// (undocumented)
LOADING = "loading",
// (undocumented)
SUCCESS = "success"
}

// @public (undocumented)
export interface InfoTileProgress {
displayPercentageColors?: boolean;
Expand Down Expand Up @@ -1651,6 +1677,10 @@ export namespace JSX {
"language"?: Languages;
"onChange"?: (event: LimelProsemirrorAdapterCustomEvent<string>) => void;
// @alpha
"onImagePasted"?: (event: LimelProsemirrorAdapterCustomEvent<ImageInserter>) => void;
// @alpha
"onImageRemoved"?: (event: LimelProsemirrorAdapterCustomEvent<ImageInfo>) => void;
// @alpha
"triggerCharacters"?: TriggerCharacter[];
"value"?: string;
}
Expand Down Expand Up @@ -1772,6 +1802,10 @@ export namespace JSX {
"language"?: Languages;
"onChange"?: (event: LimelTextEditorCustomEvent<string>) => void;
// @alpha
"onImagePasted"?: (event: LimelTextEditorCustomEvent<ImageInserter>) => void;
// @alpha
"onImageRemoved"?: (event: LimelTextEditorCustomEvent<ImageInfo>) => void;
// @alpha
"onTriggerChange"?: (event: LimelTextEditorCustomEvent<TriggerEventDetail>) => void;
// @alpha
"onTriggerStart"?: (event: LimelTextEditorCustomEvent<TriggerEventDetail>) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Component, h, State } from '@stencil/core';
import {
ImageInserter,
LimelTextEditorCustomEvent,
} from '@limetech/lime-elements';
/**
* Handling inline images (with base64 encoded data)
*
* To allow users to paste images directly into the text editor, you can
* listen to the `imagePasted` event, which is triggered when an image file
* is pasted into the editor.
*
* The `imagePasted` event contains an `ImageInserter` object, which you can
* use to insert a thumbnail of the pasted image into the editor.
* After the thumbnail is inserted, you can immediately insert the image
* as base64 encoded data using the `insertImage` method.
*
* :::note
* This example demonstrates the simplest approach using base64 encoding.
* However, for production use, it is recommended to upload images to
* external file storage and insert the src URL of the uploaded image
* instead, as shown in the file-storage example.
*/
@Component({
tag: 'limel-example-text-editor-with-inline-images-base64',
shadow: true,
})
export class TextEditorWithInlineImagesExample {
@State()
private value = 'Copy an image file and paste it here.';

public render() {
return (
<limel-text-editor
value={this.value}
onChange={this.handleChange}
onImagePasted={this.handleImagePasted}
contentType="html"
/>
);
}

private handleChange = (event: LimelTextEditorCustomEvent<string>) => {
this.value = event.detail;
};

private handleImagePasted = async (
event: LimelTextEditorCustomEvent<ImageInserter>,
) => {
const imageInserter = event.detail;

imageInserter.insertThumbnail();
imageInserter.insertImage();
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Component, h, State, Host } from '@stencil/core';
import {
ImageInserter,
FileInfo,
ImageInfo,
LimelTextEditorCustomEvent,
LimelCheckboxCustomEvent,
} from '@limetech/lime-elements';
/**
* Handling inline images (with external file storage)
*
* To allow users to paste images directly into the text editor, you can
* listen to the `imagePasted` event, which is triggered when an image file
* is pasted into the editor.
*
* The `imagePasted` event contains an `ImageInserter` object, which you can
* use to insert a thumbnail of the pasted image into the editor.
* After the thumbnail is inserted, you can upload the image to an external
* file storage and insert the src url of the uploaded image using the
* `insertImage` method.
*
* If the image upload fails, you can insert a failed thumbnail using the
* `insertFailedThumbnail` method.
*
* :::note
* In this example, because we don't actually upload the image you paste
* anywhere, once the "upload" is done, we will replace the image you
* pasted with a url to an image of the Lime CRM logo.
*
* In reality, you would of course insert the url to the newly uploaded
* image instead.
*/
@Component({
tag: 'limel-example-text-editor-with-inline-images-file-storage',
shadow: true,
})
export class TextEditorWithInlineImagesExample {
@State()
private value = 'Copy an image file and paste it here.';

@State()
private uploadImageFails = false;

public render() {
return (
<Host>
<limel-text-editor
value={this.value}
onChange={this.handleChange}
onImagePasted={this.handleImagePasted}
onImageRemoved={this.handleImageRemoved}
/>
<limel-checkbox
label="Upload image fails - insert failed thumbnail"
onChange={this.handleFailedThumbnailChange}
/>
<limel-example-value label="Value" value={this.value} />
</Host>
);
}

private handleFailedThumbnailChange = (
event: LimelCheckboxCustomEvent<boolean>,
) => {
this.uploadImageFails = event.detail;
};

private handleChange = (event: LimelTextEditorCustomEvent<string>) => {
this.value = event.detail;
};

private handleImagePasted = async (
event: LimelTextEditorCustomEvent<ImageInserter>,
) => {
const imageInserter = event.detail;

imageInserter.insertThumbnail();

const imageSrc = await this.uploadImage(imageInserter.fileInfo);
if (imageSrc) {
imageInserter.insertImage(imageSrc);
} else {
imageInserter.insertFailedThumbnail();
}
};

private uploadImage = async (fileInfo: FileInfo): Promise<string> => {
try {
// Upload image to external file storage.
// fileInfo.fileContent contains the image data.

// Simulate upload delay.
const imageSrc: string = await new Promise((resolve, reject) => {
setTimeout(() => {
if (this.uploadImageFails) {
reject('Server error');
} else {
resolve('https://cdn.lime-crm.com/mail-addin-logo.png');
}
}, 2000);
});

// Return the src url of the uploaded image.
return imageSrc;
} catch (error) {
console.error(
`Failed to upload image ${fileInfo.filename}: ${error}`,
);
}
};

private handleImageRemoved = (
event: LimelTextEditorCustomEvent<ImageInfo>,
) => {
const imageInfo = event.detail;
console.log(`Image deleted: ${imageInfo.fileInfoId}`);

try {
throw new Error('Not implemented.');
} catch (error) {
console.error(
`Failed to delete image ${imageInfo.fileInfoId}`,
error,
);
}
};
}

This file was deleted.

Loading

0 comments on commit 1278215

Please sign in to comment.