Skip to content

Commit ade8007

Browse files
committed
feat(rte): add image from entity
1 parent 83cff6d commit ade8007

File tree

15 files changed

+388
-63
lines changed

15 files changed

+388
-63
lines changed

packages/pluggableWidgets/rich-text-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- We added support to choose image from entity.
12+
913
## [4.7.0] - 2025-06-02
1014

1115
### Added

packages/pluggableWidgets/rich-text-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@mendix/rich-text-web",
33
"widgetName": "RichText",
4-
"version": "4.7.0",
4+
"version": "4.8.0",
55
"description": "Rich inline or toolbar text editing",
66
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
77
"license": "Apache-2.0",

packages/pluggableWidgets/rich-text-web/src/RichText.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@
179179
</property>
180180
</properties>
181181
</property>
182+
<property key="imageSource" type="datasource" isList="true" required="false">
183+
<caption>Selectable images</caption>
184+
<description />
185+
</property>
182186
</propertyGroup>
183187
</propertyGroup>
184188
<propertyGroup caption="Custom toolbar">

packages/pluggableWidgets/rich-text-web/src/components/CustomToolbars/useEmbedModal.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,22 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
165165
ref.current?.updateContents(imageUpdateDelta, Emitter.sources.USER);
166166
} else {
167167
// upload new image
168-
if (selection && value.files) {
169-
uploadImage(ref, selection, value);
168+
if (selection) {
169+
if (value.files) {
170+
uploadImage(ref, selection, value);
171+
} else if (value.entityGuid) {
172+
const imageConfig = {
173+
alt: value.alt,
174+
width: value.width,
175+
height: value.height,
176+
"data-src": value.entityGuid
177+
};
178+
const delta = new Delta()
179+
.retain(selection.index)
180+
.delete(selection.length)
181+
.insert({ image: value.entityGuid }, imageConfig);
182+
ref.current?.updateContents(delta, Emitter.sources.USER);
183+
}
170184
}
171185
}
172186
closeDialog();
@@ -213,7 +227,16 @@ function uploadImage(ref: MutableRefObject<Quill | null>, range: Range, options:
213227
});
214228
Promise.all(promises).then(images => {
215229
const update = images.reduce((delta: Delta, image) => {
216-
return delta.insert({ image }, { alt: options.alt, width: options.width, height: options.height });
230+
return delta.insert(
231+
{ image },
232+
{
233+
alt: options.alt,
234+
width: options.width,
235+
height: options.height,
236+
"data-src": "10696049115005183",
237+
"data-mx-timestamp": "1749298464712"
238+
}
239+
);
217240
}, new Delta().retain(range.index).delete(range.length)) as Delta;
218241
ref.current?.updateContents(update, Emitter.sources.USER);
219242
ref.current?.setSelection(range.index + images.length, Emitter.sources.SILENT);

packages/pluggableWidgets/rich-text-web/src/components/Editor.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
useLayoutEffect,
1212
useRef
1313
} from "react";
14-
import { CustomFontsType } from "../../typings/RichTextProps";
14+
import { CustomFontsType, RichTextContainerProps } from "../../typings/RichTextProps";
1515
import { EditorDispatchContext } from "../store/EditorProvider";
1616
import { SET_FULLSCREEN_ACTION } from "../store/store";
1717
import "../utils/customPluginRegisters";
@@ -31,7 +31,7 @@ import {
3131
import { useEmbedModal } from "./CustomToolbars/useEmbedModal";
3232
import Dialog from "./ModalDialog/Dialog";
3333

34-
export interface EditorProps {
34+
export interface EditorProps extends Pick<RichTextContainerProps, "imageSource"> {
3535
customFonts: CustomFontsType[];
3636
defaultValue?: string;
3737
onTextChange?: (...args: [delta: Delta, oldContent: Delta, source: EmitterSource]) => void;
@@ -200,6 +200,7 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
200200
isOpen={showDialog}
201201
onOpenChange={open => setShowDialog(open)}
202202
parentNode={modalRef.current?.ownerDocument.body}
203+
imageSource={props.imageSource}
203204
{...dialogConfig}
204205
></Dialog>
205206
</Fragment>

packages/pluggableWidgets/rich-text-web/src/components/EditorWrapper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
4949
readOnlyStyle,
5050
toolbarOptions,
5151
enableStatusBar,
52-
tabIndex
52+
tabIndex,
53+
imageSource
5354
} = props;
5455

5556
const globalState = useContext(EditorContext);
@@ -209,6 +210,7 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
209210
readOnly={stringAttribute.readOnly}
210211
key={`${toolbarId}_${stringAttribute.readOnly}`}
211212
customFonts={props.customFonts}
213+
imageSource={imageSource}
212214
/>
213215
</div>
214216
{enableStatusBar && (

packages/pluggableWidgets/rich-text-web/src/components/ModalDialog/Dialog.scss

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,60 @@
3838
}
3939
}
4040
}
41+
42+
.mx-image-dialog-list {
43+
display: flex;
44+
flex-wrap: wrap;
45+
}
46+
47+
.mx-image-dialog-item {
48+
padding-right: var(--spacing-small, 8px);
49+
padding-bottom: var(--spacing-small, 8px);
50+
cursor: pointer;
51+
52+
&:hover {
53+
transform: scale3d(1.05, 1.05, 1);
54+
transition: transform 0.2s ease-in-out;
55+
}
56+
}
57+
58+
.mx-image-dialog-thumbnail {
59+
max-height: 100px;
60+
&-small {
61+
max-height: 50px;
62+
}
63+
64+
&-container {
65+
position: relative;
66+
67+
.icon-container {
68+
position: absolute;
69+
left: 0;
70+
right: 0;
71+
bottom: 0;
72+
top: 0;
73+
align-items: center;
74+
justify-content: center;
75+
display: none;
76+
77+
.icons {
78+
border-radius: var(--border-radius-default, 4px);
79+
background-color: var(--color-background, #fff);
80+
cursor: pointer;
81+
transition: transform 0.2s ease-in-out;
82+
&:hover {
83+
transform: scale3d(1.05, 1.05, 1);
84+
}
85+
}
86+
}
87+
88+
&:hover {
89+
.icon-container {
90+
display: flex;
91+
}
92+
}
93+
}
94+
}
4195
}
4296

4397
&-header {

packages/pluggableWidgets/rich-text-web/src/components/ModalDialog/Dialog.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import VideoDialog, { VideoDialogProps } from "./VideoDialog";
1515
import ViewCodeDialog, { ViewCodeDialogProps } from "./ViewCodeDialog";
1616
import ImageDialog, { ImageDialogProps } from "./ImageDialog";
1717
import "./Dialog.scss";
18+
import { RichTextContainerProps } from "../../../typings/RichTextProps";
1819

1920
interface BaseDialogProps {
2021
isOpen: boolean;
@@ -48,13 +49,13 @@ export type ChildDialogProps =
4849
| ViewCodeDialogBaseProps
4950
| ImageDialogBaseProps;
5051

51-
export type DialogProps = BaseDialogProps & ChildDialogProps;
52+
export type DialogProps = BaseDialogProps & ChildDialogProps & Pick<RichTextContainerProps, "imageSource">;
5253

5354
/**
5455
* Dialog components that will be shown on toolbar's button
5556
*/
5657
export default function Dialog(props: DialogProps): ReactElement {
57-
const { isOpen, onOpenChange, dialogType, config } = props;
58+
const { isOpen, onOpenChange, dialogType, config, imageSource } = props;
5859
const { refs, context } = useFloating({
5960
open: isOpen,
6061
onOpenChange
@@ -78,7 +79,7 @@ export default function Dialog(props: DialogProps): ReactElement {
7879
></FloatingOverlay>
7980
<FloatingFocusManager context={context}>
8081
<div
81-
className="Dialog mx-layoutgrid"
82+
className="Dialog mx-layoutgrid widget-rich-text"
8283
ref={refs.setFloating}
8384
aria-labelledby={dialogType}
8485
aria-describedby={dialogType}
@@ -94,7 +95,7 @@ export default function Dialog(props: DialogProps): ReactElement {
9495
<ViewCodeDialog {...(config as ViewCodeDialogProps)}></ViewCodeDialog>
9596
</If>
9697
<If condition={dialogType === "image"}>
97-
<ImageDialog {...(config as ImageDialogProps)}></ImageDialog>
98+
<ImageDialog imageSource={imageSource} {...(config as ImageDialogProps)}></ImageDialog>
9899
</If>
99100
</div>
100101
</FloatingFocusManager>

0 commit comments

Comments
 (0)