Skip to content

Commit 8965f9c

Browse files
FredrikWallstromadrianschmidt
authored andcommitted
feat(text-editor): add support for pasting inline images
1 parent ba7f83c commit 8965f9c

16 files changed

+1075
-109
lines changed

etc/lime-elements.api.md

+34
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,32 @@ interface Image_2 {
966966
}
967967
export { Image_2 as Image }
968968

969+
// @alpha (undocumented)
970+
export interface ImageInfo {
971+
fileInfoId: string;
972+
src: string;
973+
state: ImageState;
974+
}
975+
976+
// @alpha (undocumented)
977+
export interface ImageInserter {
978+
// (undocumented)
979+
fileInfo: FileInfo;
980+
insertFailedThumbnail: () => void;
981+
insertImage: (src?: string) => void;
982+
insertThumbnail: () => void;
983+
}
984+
985+
// @alpha (undocumented)
986+
export enum ImageState {
987+
// (undocumented)
988+
FAILED = "failed",
989+
// (undocumented)
990+
LOADING = "loading",
991+
// (undocumented)
992+
SUCCESS = "success"
993+
}
994+
969995
// @public (undocumented)
970996
export interface InfoTileProgress {
971997
displayPercentageColors?: boolean;
@@ -1651,6 +1677,10 @@ export namespace JSX {
16511677
"language"?: Languages;
16521678
"onChange"?: (event: LimelProsemirrorAdapterCustomEvent<string>) => void;
16531679
// @alpha
1680+
"onImagePasted"?: (event: LimelProsemirrorAdapterCustomEvent<ImageInserter>) => void;
1681+
// @alpha
1682+
"onImageRemoved"?: (event: LimelProsemirrorAdapterCustomEvent<ImageInfo>) => void;
1683+
// @alpha
16541684
"triggerCharacters"?: TriggerCharacter[];
16551685
"value"?: string;
16561686
}
@@ -1772,6 +1802,10 @@ export namespace JSX {
17721802
"language"?: Languages;
17731803
"onChange"?: (event: LimelTextEditorCustomEvent<string>) => void;
17741804
// @alpha
1805+
"onImagePasted"?: (event: LimelTextEditorCustomEvent<ImageInserter>) => void;
1806+
// @alpha
1807+
"onImageRemoved"?: (event: LimelTextEditorCustomEvent<ImageInfo>) => void;
1808+
// @alpha
17751809
"onTriggerChange"?: (event: LimelTextEditorCustomEvent<TriggerEventDetail>) => void;
17761810
// @alpha
17771811
"onTriggerStart"?: (event: LimelTextEditorCustomEvent<TriggerEventDetail>) => void;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Component, h, State } from '@stencil/core';
2+
import {
3+
ImageInserter,
4+
LimelTextEditorCustomEvent,
5+
} from '@limetech/lime-elements';
6+
/**
7+
* Handling inline images (with base64 encoded data)
8+
*
9+
* To allow users to paste images directly into the text editor, you can
10+
* listen to the `imagePasted` event, which is triggered when an image file
11+
* is pasted into the editor.
12+
*
13+
* The `imagePasted` event contains an `ImageInserter` object, which you can
14+
* use to insert a thumbnail of the pasted image into the editor.
15+
* After the thumbnail is inserted, you can immediately insert the image
16+
* as base64 encoded data using the `insertImage` method.
17+
*
18+
* NOTE: This example demonstrates the simplest approach using base64 encoding.
19+
* However, for production use, it is recommended to upload images to
20+
* external file storage and insert the src URL of the uploaded image
21+
* instead, as shown in the file-storage example.
22+
*/
23+
@Component({
24+
tag: 'limel-example-text-editor-with-inline-images-base64',
25+
shadow: true,
26+
})
27+
export class TextEditorWithInlineImagesExample {
28+
@State()
29+
private value = 'Copy an image file and paste it here.';
30+
31+
public render() {
32+
return (
33+
<limel-text-editor
34+
value={this.value}
35+
onChange={this.handleChange}
36+
onImagePasted={this.handleImagePasted}
37+
contentType="html"
38+
/>
39+
);
40+
}
41+
42+
private handleChange = (event: LimelTextEditorCustomEvent<string>) => {
43+
this.value = event.detail;
44+
};
45+
46+
private handleImagePasted = async (
47+
event: LimelTextEditorCustomEvent<ImageInserter>,
48+
) => {
49+
const imageInserter = event.detail;
50+
51+
imageInserter.insertThumbnail();
52+
imageInserter.insertImage();
53+
};
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { Component, h, State } from '@stencil/core';
2+
import {
3+
ImageInserter,
4+
FileInfo,
5+
ImageInfo,
6+
LimelTextEditorCustomEvent,
7+
LimelCheckboxCustomEvent,
8+
} from '@limetech/lime-elements';
9+
/**
10+
* Handling inline images (with external file storage)
11+
*
12+
* To allow users to paste images directly into the text editor, you can
13+
* listen to the `imagePasted` event, which is triggered when an image file
14+
* is pasted into the editor.
15+
*
16+
* The `imagePasted` event contains an `ImageInserter` object, which you can
17+
* use to insert a thumbnail of the pasted image into the editor.
18+
* After the thumbnail is inserted, you can upload the image to an external
19+
* file storage and insert the src url of the uploaded image using the
20+
* `insertImage` method.
21+
*
22+
* If the image upload fails, you can insert a failed thumbnail using the
23+
* `insertFailedThumbnail` method.
24+
*/
25+
@Component({
26+
tag: 'limel-example-text-editor-with-inline-images-file-storage',
27+
shadow: true,
28+
})
29+
export class TextEditorWithInlineImagesExample {
30+
@State()
31+
private value = 'Copy an image file and paste it here.';
32+
33+
@State()
34+
private uploadImageFails = false;
35+
36+
public render() {
37+
return [
38+
<limel-text-editor
39+
value={this.value}
40+
onChange={this.handleChange}
41+
onImagePasted={this.handleImagePasted}
42+
onImageRemoved={this.handleImageRemoved}
43+
/>,
44+
<limel-checkbox
45+
label="Upload image fails - insert failed thumbnail"
46+
onChange={this.handleFailedThumbnailChange}
47+
/>,
48+
];
49+
}
50+
51+
private handleFailedThumbnailChange = (
52+
event: LimelCheckboxCustomEvent<boolean>,
53+
) => {
54+
this.uploadImageFails = event.detail;
55+
};
56+
57+
private handleChange = (event: LimelTextEditorCustomEvent<string>) => {
58+
this.value = event.detail;
59+
};
60+
61+
private handleImagePasted = async (
62+
event: LimelTextEditorCustomEvent<ImageInserter>,
63+
) => {
64+
const imageInserter = event.detail;
65+
66+
imageInserter.insertThumbnail();
67+
68+
const imageSrc = await this.uploadImage(imageInserter.fileInfo);
69+
if (imageSrc) {
70+
imageInserter.insertImage(imageSrc);
71+
} else {
72+
imageInserter.insertFailedThumbnail();
73+
}
74+
};
75+
76+
private uploadImage = async (fileInfo: FileInfo): Promise<string> => {
77+
try {
78+
// Upload image to external file storage.
79+
// fileInfo.fileContent contains the image data.
80+
81+
// Simulate upload delay.
82+
const imageSrc: string = await new Promise((resolve, reject) => {
83+
setTimeout(() => {
84+
if (this.uploadImageFails) {
85+
reject('Server error');
86+
} else {
87+
resolve('https://cdn.lime-crm.com/mail-addin-logo.png');
88+
}
89+
}, 2000);
90+
});
91+
92+
// Return the src url of the uploaded image.
93+
return imageSrc;
94+
} catch (error) {
95+
console.error(
96+
`Failed to upload image ${fileInfo.filename}: ${error}`,
97+
);
98+
}
99+
};
100+
101+
private handleImageRemoved = (
102+
event: LimelTextEditorCustomEvent<ImageInfo>,
103+
) => {
104+
const imageInfo = event.detail;
105+
console.log(`Image deleted: ${imageInfo.fileInfoId}`);
106+
107+
try {
108+
// Delete image from external file storage.
109+
} catch (error) {
110+
console.error(
111+
`Failed to delete image ${imageInfo.fileInfoId}`,
112+
error,
113+
);
114+
}
115+
};
116+
}

src/components/text-editor/prosemirror-adapter/plugins/image-remover-plugin.ts

-99
This file was deleted.

0 commit comments

Comments
 (0)