mdx-editor/editor

[BUG] Saving/inserting images from custom ImageDialog

Closed this issue · 8 comments

If you want to ask for support or request features, sponsor the project and contact me over email.

  • I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • I have read the documentation and cannot find an answer.

Describe the bug
I have implemented a custom ImageDialog component that uses a Dropbox Chooser. The chooser returns some JSON with url, name, etc. When using saveImage$, insertImage$, etc nothing happens.

Reproduction
Unfortunately, I can't really make a reproducible example because of Dropbox keys, origins, etc. And I don't think that screenshots will be particularly useful. I can add a video and some code that outlines what is happening. Please see code snippet at bottom.

Video: https://www.loom.com/share/1a92dc6b547546668fa188c373459c28

Expected behavior
I expect the image to be loaded into the editor.

Desktop (please complete the following information):

  • OS: Mac
  • Browser Chrome
function ImageDialog() {
 const saveImage = usePublisher(saveImage$);
 const createImage = usePublisher($createImageNode);
 const insertImage = usePublisher(insertImage$);
 const closeImageDialog = usePublisher(closeImageDialog$);

 const [imageDialogState] = useCellValues(imageDialogState$);

 const handleDropboxSuccess = async (files) => {
   const dropboxItem = files[0];
   const file = await urlToObject(dropboxItem.link, dropboxItem.name);
   const list = createFileList(file);
   // const image = createImage({
   //   src: file.link,
   //   altText: file.name,
   //   title: file.name,
   //   files: list
   // });
   // saveImage({
   //   src: dropboxItem.link,
   //   altText: dropboxItem.name,
   //   title: dropboxItem.name,
   //   file: list
   // });
   insertImage({
     src: dropboxItem.link,
     altText: dropboxItem.name,
     title: dropboxItem.name,
     file: list
   });
   closeImageDialog();
 };

 return (
   <Modal
     isOpen={imageDialogState.type !== 'inactive'}
     size="lg"
     fullscreen="md"
     centered={true}
     scrollable={true}
     fade={false}
     contentClassName="h-100"
     backdrop={false}
   >
     <ModalBody className="p-3">
       <div className="mb-4">
         <div className="mb-4">
           Use Dropbox to securely link your files. Click the button below to
           choose files from your Dropbox account.
         </div>
         <DropboxChooser
           appKey={DROPBOX_KEY}
           success={handleDropboxSuccess}
           linkType="direct"
           // cancel={}
           // multiselect={true}
         >
           <Button>
             <FaDropbox className="me-2" />
             Choose From Dropbox
           </Button>
         </DropboxChooser>
       </div>
       {/* <div>
         <div className="mb-4">
           Use Google Drive to securely link your files. Click the button below
           to choose files from your Drive account.
         </div>
         <Button onClick={openGooglePicker}>
           <FaGoogle className="me-2" />
           Choose From Google
         </Button>
       </div> */}
     </ModalBody>
     <ModalFooter className="p-3">
       <Button
         type="button"
         color="secondary"
         className="rounded-pill"
         onClick={closeImageDialog}
       >
         Cancel
       </Button>
     </ModalFooter>
   </Modal>
 );
}

const urlToObject = async (url, name) => {
 const response = await fetch(url);
 const blob = await response.blob();
 const file = new File([blob], name, { type: blob.type });
 return file;
};

let getDataTransfer = () => new DataTransfer();
const { concat } = Array.prototype;

try {
 getDataTransfer();
} catch {
 getDataTransfer = () => new ClipboardEvent('').clipboardData;
}

function createFileList() {
 // eslint-disable-next-line prefer-rest-params
 const files = concat.apply([], arguments);
 let index = 0;
 const { length } = files;

 const dataTransfer = getDataTransfer();

 for (; index < length; index++) {
   dataTransfer.items.add(files[index]);
 }

 return dataTransfer.files;
}

export default createFileList;

Confirming that this is actually a bug of insertImage$. Your code seems correct as far as I can tell, will fix this.

It would also be helpful if the functions took a File OR FileList for the file property. You cannot construct a file list like new FileList().
My code is using: https://www.npmjs.com/package/create-file-list

Thanks!

A new version should be built and published soon. Check the examples I did in the commit above.

There is still an error on this line:

if (values.file.length > 0) {

I believe that should be updated to use if 'file' in values, similar to how insertImage was updated.

How can I reproduce the error?

Call saveImage({ src: '...' }) without a file property.

Given that you're using a custom dialog component, you don't have to use the saveImage signal. It's bound to the data shape of the default dialog form. Instead, use the insertImage, it should work as expected.

OK. I was thinking saveImage might "update" the current image data. How would I go about editing the image data? For example, my custom ImageDialog now has inputs for file name and alt text. When I click the gear icon on an image, it opens the modal with the field values. How do I save those if the user edits them?