Skip to content

Commit bdd6710

Browse files
committed
fix: image edit dialog usage
1 parent e9b3390 commit bdd6710

File tree

7 files changed

+453
-865
lines changed

7 files changed

+453
-865
lines changed

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

Lines changed: 22 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ type ModalReturnType = {
2626
export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnType {
2727
const [showDialog, setShowDialog] = useState<boolean>(false);
2828
const [dialogConfig, setDialogConfig] = useState<ChildDialogProps>({});
29-
29+
const openDialog = (): void => {
30+
setShowDialog(true);
31+
};
3032
const closeDialog = (): void => {
3133
setShowDialog(false);
3234
setTimeout(() => ref.current?.focus(), 50);
@@ -52,7 +54,7 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
5254
defaultValue: { ...value, text }
5355
}
5456
});
55-
setShowDialog(true);
57+
openDialog();
5658
} else {
5759
ref.current?.format("link", false);
5860
closeDialog();
@@ -96,7 +98,7 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
9698
selection: ref.current?.getSelection()
9799
}
98100
});
99-
setShowDialog(true);
101+
openDialog();
100102
} else {
101103
ref.current?.format("link", false);
102104
closeDialog();
@@ -119,60 +121,41 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
119121
onClose: closeDialog
120122
}
121123
});
122-
setShowDialog(true);
124+
openDialog();
123125
} else {
124126
ref.current?.format("link", false);
125127
closeDialog();
126128
}
127129
};
128130

129131
const customImageUploadHandler = (value: any): void => {
130-
// Check if this is an edit of an existing image
131-
const selection = ref.current?.getSelection() || null;
132-
const isEdit = typeof value === "object" && value !== true;
133-
134-
// If we have a direct image reference (from Alt button), use it directly
135-
let activeImageElement: HTMLImageElement | null = null;
136-
137-
if (isEdit && value.src) {
138-
// This is likely coming from the Alt button click with a direct image reference
139-
// Find the image by src attribute as a fallback when selection isn't available
140-
activeImageElement = findImageBySrc(ref, value.src);
141-
} else {
142-
// Try to get the image from selection
143-
activeImageElement = isEdit ? getActiveImageElement(ref, selection) : null;
144-
}
145-
146-
// Get the current alt text if it exists
147-
const currentAlt = activeImageElement?.getAttribute("alt") || "";
148-
132+
const selection = ref.current?.getSelection(true);
149133
setDialogConfig({
150134
dialogType: "image",
151135
config: {
152136
onSubmit: (value: imageConfigType) => {
153-
const range = ref.current?.getSelection(true);
154-
155-
// If editing an existing image, update only its alt text
156-
if (activeImageElement) {
157-
if (value.alt !== undefined) {
158-
activeImageElement.setAttribute("alt", value.alt);
137+
if (value.src) {
138+
const index = selection?.index ?? 0;
139+
const length = 1;
140+
const imageConfig = {
141+
alt: value.alt,
142+
width: value.width,
143+
height: value.height
144+
};
145+
const imageUpdateDelta = new Delta().retain(index).retain(length, imageConfig);
146+
ref.current?.updateContents(imageUpdateDelta);
147+
} else {
148+
if (selection && value.files) {
149+
uploadImage(ref, selection, value);
159150
}
160151
}
161-
// Otherwise, handle new image upload if files are provided
162-
else if (range && value.files) {
163-
uploadImage(ref, range, value);
164-
}
165-
166152
closeDialog();
167153
},
168154
onClose: closeDialog,
169-
// Pass props to disable file upload when editing
170-
disableFileUpload: !!activeImageElement,
171-
// Pass the current alt text for editing
172-
alt: currentAlt
155+
defaultValue: { ...value }
173156
}
174157
});
175-
setShowDialog(true);
158+
openDialog();
176159
};
177160

178161
return {
@@ -186,66 +169,6 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
186169
};
187170
}
188171

189-
/**
190-
* Helper function to find an image element by its src attribute
191-
* This is used as a fallback when selection is not available
192-
*/
193-
function findImageBySrc(ref: MutableRefObject<Quill | null>, src: string): HTMLImageElement | null {
194-
if (!ref.current) {
195-
return null;
196-
}
197-
198-
const quill = ref.current as any;
199-
if (quill.root) {
200-
// Try to find the image by src attribute
201-
const images = quill.root.querySelectorAll("img");
202-
for (const img of images) {
203-
if (img.src === src) {
204-
return img as HTMLImageElement;
205-
}
206-
}
207-
}
208-
209-
return null;
210-
}
211-
212-
/**
213-
* Helper function to get the currently selected image element
214-
*/
215-
function getActiveImageElement(ref: MutableRefObject<Quill | null>, selection: Range | null): HTMLImageElement | null {
216-
if (!selection || !ref.current) {
217-
return null;
218-
}
219-
220-
// First try to get the exact element at the selection
221-
const [leaf] = ref.current.getLeaf(selection.index);
222-
if (leaf && leaf.domNode && leaf.domNode instanceof HTMLElement && leaf.domNode.tagName === "IMG") {
223-
return leaf.domNode as HTMLImageElement;
224-
}
225-
226-
// If not found, try to look at the parents of the current selection
227-
const quill = ref.current as any;
228-
if (quill.root) {
229-
const blot = quill.scroll.find(selection.index);
230-
if (blot && blot.domNode) {
231-
// If the blot itself is an image
232-
if (blot.domNode instanceof HTMLElement && blot.domNode.tagName === "IMG") {
233-
return blot.domNode as HTMLImageElement;
234-
}
235-
236-
// Or if it contains an image
237-
if (blot.domNode instanceof HTMLElement) {
238-
const img = blot.domNode.querySelector("img");
239-
if (img) {
240-
return img as HTMLImageElement;
241-
}
242-
}
243-
}
244-
}
245-
246-
return null;
247-
}
248-
249172
function uploadImage(ref: MutableRefObject<Quill | null>, range: Range, options: imageConfigType): void {
250173
const uploads: File[] = [];
251174
const { files } = options;

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "./CustomToolbars/toolbarHandlers";
2121
import { useEmbedModal } from "./CustomToolbars/useEmbedModal";
2222
import Dialog from "./ModalDialog/Dialog";
23+
import type QuillResize from "quill-resize-module";
2324
export interface EditorProps {
2425
defaultValue?: string;
2526
onTextChange?: (...args: [delta: Delta, oldContent: Delta, source: EmitterSource]) => void;
@@ -122,12 +123,20 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
122123
verify(activeEle: HTMLElement) {
123124
return activeEle && activeEle.tagName === "IMG";
124125
},
125-
handler(_evt: MouseEvent, _button: HTMLElement, activeEle: HTMLImageElement) {
126+
handler(
127+
this: { quill: Quill; resizer: typeof QuillResize },
128+
_evt: MouseEvent,
129+
_button: HTMLElement,
130+
activeEle: HTMLImageElement
131+
) {
126132
const imageInfo = {
127133
alt: activeEle.alt || "",
128-
src: activeEle.src
134+
src: activeEle.src,
135+
width: activeEle.width,
136+
height: activeEle.height
129137
};
130-
customImageUploadHandler(imageInfo);
138+
this.resizer.handleEdit();
139+
this.quill.emitter.emit("EDIT-TOOLTIP", imageInfo);
131140
}
132141
}
133142
]
@@ -144,7 +153,13 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
144153
onSelectionChangeRef.current?.(...arg);
145154
});
146155
quill.on("EDIT-TOOLTIP", (...arg: any[]) => {
147-
customLinkHandler(arg[0]);
156+
if (arg[0]) {
157+
if (arg[0].href) {
158+
customLinkHandler(arg[0]);
159+
} else if (arg[0].src) {
160+
customImageUploadHandler(arg[0]);
161+
}
162+
}
148163
});
149164
}
150165

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

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,87 @@
1-
import { ChangeEvent, createElement, ReactElement, useEffect, useState } from "react";
1+
import { ChangeEvent, createElement, ReactElement, useEffect, useRef, useState } from "react";
22
import { type imageConfigType } from "../../utils/formats";
33
import { DialogBody, DialogContent, DialogFooter, DialogHeader, FormControl } from "./DialogContent";
44
import { IMG_MIME_TYPES } from "../CustomToolbars/constants";
55

66
export interface ImageDialogProps {
77
onSubmit(value: imageConfigType): void;
88
onClose(): void;
9-
disableFileUpload?: boolean;
10-
imageUrl?: string;
11-
alt?: string;
9+
defaultValue?: imageConfigType;
1210
}
1311

1412
export default function ImageDialog(props: ImageDialogProps): ReactElement {
15-
const { onSubmit, onClose, disableFileUpload = false, alt = "" } = props;
13+
const { onSubmit, onClose, defaultValue } = props;
14+
const inputReference = useRef<HTMLInputElement>(null);
15+
16+
useEffect(() => {
17+
setTimeout(() => inputReference?.current?.focus(), 50);
18+
}, []);
1619

1720
const [formState, setFormState] = useState<imageConfigType>({
1821
files: null,
19-
width: 100,
20-
height: 100,
21-
alt
22+
alt: defaultValue?.alt ?? "",
23+
width: defaultValue?.width ?? 100,
24+
height: defaultValue?.height ?? 100,
25+
src: defaultValue?.src ?? undefined
2226
});
2327

24-
// Update form state when alt prop changes
25-
useEffect(() => {
26-
if (disableFileUpload) {
27-
setFormState(prevState => ({
28-
...prevState,
29-
alt
30-
}));
31-
}
32-
}, [alt, disableFileUpload]);
33-
3428
const onFileChange = (e: ChangeEvent<HTMLInputElement>): void => {
3529
setFormState({ ...formState, [e.target.name]: e.target.files });
3630
};
3731

3832
const onInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
33+
e.preventDefault();
34+
e.stopPropagation();
3935
setFormState({ ...formState, [e.target.name]: e.target.value });
4036
};
4137

4238
return (
4339
<DialogContent className="image-dialog">
44-
<DialogHeader onClose={onClose}>{disableFileUpload ? "Edit Image Alt Text" : "Edit Image"}</DialogHeader>
40+
<DialogHeader onClose={onClose}>Insert/Edit Image</DialogHeader>
4541
<DialogBody>
46-
{!disableFileUpload && (
47-
<FormControl label="Source Code">
42+
<FormControl label="Source Code">
43+
{defaultValue?.src ? (
44+
<img src={defaultValue.src} alt={defaultValue.alt} height={50} />
45+
) : (
4846
<input
4947
name="files"
5048
className="form-control mx-textarea-input mx-textarea-noresize code-input"
5149
type="file"
5250
accept={IMG_MIME_TYPES.join(", ")}
5351
onChange={onFileChange}
5452
></input>
55-
</FormControl>
56-
)}
53+
)}
54+
</FormControl>
5755
<FormControl label="Alternative description">
5856
<input
5957
className="form-control"
6058
type="text"
6159
name="alt"
6260
onChange={onInputChange}
6361
value={formState.alt}
62+
ref={inputReference}
6463
/>
6564
</FormControl>
66-
{!disableFileUpload && (
67-
<FormControl label="Width">
68-
<input
69-
className="form-control"
70-
type="number"
71-
name="width"
72-
onChange={onInputChange}
73-
value={formState.width}
74-
/>
75-
px
76-
</FormControl>
77-
)}
78-
{!disableFileUpload && (
79-
<FormControl label="Height">
80-
<input
81-
className="form-control"
82-
type="number"
83-
name="height"
84-
onChange={onInputChange}
85-
value={formState.height}
86-
/>
87-
px
88-
</FormControl>
89-
)}
65+
<FormControl label="Width">
66+
<input
67+
className="form-control"
68+
type="number"
69+
name="width"
70+
onChange={onInputChange}
71+
value={formState.width}
72+
/>
73+
px
74+
</FormControl>
75+
<FormControl label="Height">
76+
<input
77+
className="form-control"
78+
type="number"
79+
name="height"
80+
onChange={onInputChange}
81+
value={formState.height}
82+
/>
83+
px
84+
</FormControl>
9085
<DialogFooter onSubmit={() => onSubmit(formState)} onClose={onClose}></DialogFooter>
9186
</DialogBody>
9287
</DialogContent>

packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const direction = Quill.import("attributors/style/direction") as Attributor;
1111
const alignment = Quill.import("attributors/style/align") as Attributor;
1212
import { IndentLeftStyle, IndentRightStyle } from "./formats/indent";
1313
import Formula from "./formats/formula";
14-
import CustomHeader from "./formats/header";
1514
import QuillResize from "quill-resize-module";
1615
class Empty {
1716
doSomething(): string {

packages/pluggableWidgets/rich-text-web/src/utils/formats.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ export type imageConfigType = {
2424
alt?: string;
2525
width?: number;
2626
height?: number;
27+
src?: string;
2728
};

packages/pluggableWidgets/rich-text-web/typings/modules.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ declare module "quill-resize-module" {
4949
interface ResizeModuleConstructor {
5050
new (quill: Quill, options: ResizeModuleOptions): any;
5151
Modules?: any;
52+
handleEdit(): void;
5253
}
5354

5455
const ResizeModule: ResizeModuleConstructor;

0 commit comments

Comments
 (0)