Skip to content

Commit 10e01e0

Browse files
committed
Fixing upload issues
1 parent 56eda71 commit 10e01e0

5 files changed

Lines changed: 104 additions & 2 deletions

File tree

public/r/editor.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

registry/default/editor/editor.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type ImageUploadHandler = (
6060
) => ImageUploadResult | null | Promise<ImageUploadResult | null>;
6161

6262
const DEFAULT_MAX_IMAGE_BYTES = 1_000_000;
63+
const UPLOADED_IMAGE_PRELOAD_TIMEOUT_MS = 8_000;
6364
const UploadableImage = Image.extend({
6465
addAttributes() {
6566
return {
@@ -499,6 +500,44 @@ export function Editor({
499500
reader.readAsDataURL(file);
500501
});
501502

503+
const preloadImageSource = async (src: string, timeoutMs = UPLOADED_IMAGE_PRELOAD_TIMEOUT_MS): Promise<boolean> =>
504+
new Promise<boolean>((resolve) => {
505+
const image = new window.Image();
506+
let settled = false;
507+
const timeoutId = window.setTimeout(() => {
508+
if (settled) return;
509+
settled = true;
510+
image.onload = null;
511+
image.onerror = null;
512+
resolve(false);
513+
}, timeoutMs);
514+
515+
const finish = (ok: boolean): void => {
516+
if (settled) return;
517+
settled = true;
518+
window.clearTimeout(timeoutId);
519+
image.onload = null;
520+
image.onerror = null;
521+
resolve(ok);
522+
};
523+
524+
image.onerror = () => finish(false);
525+
image.onload = () => {
526+
if (typeof image.decode === "function") {
527+
void image.decode().then(
528+
() => finish(true),
529+
// decode errors can still have a usable image after load; keep it non-blocking.
530+
() => finish(true),
531+
);
532+
return;
533+
}
534+
finish(true);
535+
};
536+
537+
image.src = src;
538+
if (image.complete && image.naturalWidth > 0) finish(true);
539+
});
540+
502541
const findImageNodeByUploadId = (
503542
uploadId: string,
504543
): { pos: number; attrs: UploadableImageAttrs } | null => {
@@ -590,6 +629,17 @@ export function Editor({
590629
return;
591630
}
592631

632+
const preloaded = await preloadImageSource(resolved.src);
633+
if (!preloaded) {
634+
finalizeImageUpload(uploadId, (attrs) => ({
635+
...attrs,
636+
uploading: false,
637+
uploadError: "Image uploaded, but preview failed to load",
638+
}));
639+
cleanupUpload(uploadId, { revokeBlob: false });
640+
return;
641+
}
642+
593643
finalizeImageUpload(uploadId, (attrs): UploadableImageAttrs | null => {
594644
const expectedBlob = expectedBlobByUploadIdRef.current.get(uploadId);
595645
const currentSrc = typeof attrs.src === "string" ? attrs.src : "";

registry/default/editor/slash-command/suggestion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const requestImageAndInsert = async ({
6464

6565
if (result.kind === "file") {
6666
if (!onInsertLocalImageFile) return;
67+
editor.chain().focus().deleteRange(range).run();
6768
const fileInsertContext: ImagePickerContext & Omit<ImagePickerFileResult, "kind"> = {
6869
editor,
6970
range,

src/components/ui/editor.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type ImageUploadHandler = (
6060
) => ImageUploadResult | null | Promise<ImageUploadResult | null>;
6161

6262
const DEFAULT_MAX_IMAGE_BYTES = 1_000_000;
63+
const UPLOADED_IMAGE_PRELOAD_TIMEOUT_MS = 8_000;
6364
const UploadableImage = Image.extend({
6465
addAttributes() {
6566
return {
@@ -499,6 +500,44 @@ export function Editor({
499500
reader.readAsDataURL(file);
500501
});
501502

503+
const preloadImageSource = async (src: string, timeoutMs = UPLOADED_IMAGE_PRELOAD_TIMEOUT_MS): Promise<boolean> =>
504+
new Promise<boolean>((resolve) => {
505+
const image = new window.Image();
506+
let settled = false;
507+
const timeoutId = window.setTimeout(() => {
508+
if (settled) return;
509+
settled = true;
510+
image.onload = null;
511+
image.onerror = null;
512+
resolve(false);
513+
}, timeoutMs);
514+
515+
const finish = (ok: boolean): void => {
516+
if (settled) return;
517+
settled = true;
518+
window.clearTimeout(timeoutId);
519+
image.onload = null;
520+
image.onerror = null;
521+
resolve(ok);
522+
};
523+
524+
image.onerror = () => finish(false);
525+
image.onload = () => {
526+
if (typeof image.decode === "function") {
527+
void image.decode().then(
528+
() => finish(true),
529+
// decode errors can still have a usable image after load; keep it non-blocking.
530+
() => finish(true),
531+
);
532+
return;
533+
}
534+
finish(true);
535+
};
536+
537+
image.src = src;
538+
if (image.complete && image.naturalWidth > 0) finish(true);
539+
});
540+
502541
const findImageNodeByUploadId = (
503542
uploadId: string,
504543
): { pos: number; attrs: UploadableImageAttrs } | null => {
@@ -590,6 +629,17 @@ export function Editor({
590629
return;
591630
}
592631

632+
const preloaded = await preloadImageSource(resolved.src);
633+
if (!preloaded) {
634+
finalizeImageUpload(uploadId, (attrs) => ({
635+
...attrs,
636+
uploading: false,
637+
uploadError: "Image uploaded, but preview failed to load",
638+
}));
639+
cleanupUpload(uploadId, { revokeBlob: false });
640+
return;
641+
}
642+
593643
finalizeImageUpload(uploadId, (attrs): UploadableImageAttrs | null => {
594644
const expectedBlob = expectedBlobByUploadIdRef.current.get(uploadId);
595645
const currentSrc = typeof attrs.src === "string" ? attrs.src : "";

src/components/ui/slash-command/suggestion.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const requestImageAndInsert = async ({
6464

6565
if (result.kind === "file") {
6666
if (!onInsertLocalImageFile) return;
67+
editor.chain().focus().deleteRange(range).run();
6768
const fileInsertContext: ImagePickerContext & Omit<ImagePickerFileResult, "kind"> = {
6869
editor,
6970
range,

0 commit comments

Comments
 (0)