3

I've written a custom field for KeystoneJS's AdminUI which uses TinyMCE's editor.

KeystoneJS runs an Apollo GraphQL Server underneath and auto-generates mutations and queries based on your CMS schema. TinyMCE has the capability to enter custom hooks to upload images.

I'd like to be able to connect the two -- upload images from TinyMCE to KeystoneJS's server using GraphQL mutations.

For instance, in my setup I have an Image field in the CMS. KeystoneJS has a GraphQL mutation that will allow me to upload an image

createImage(data: ImageCreateInput): Image

where imageCreateInputis

type ImageCreateInput {file: Upload}

This tutorial has an explanation of how to upload images from Apollo Client to an Apollo Server (which KeystoneJS is running).

const UPLOAD_MUTATION = gql`
  mutation submit($file: Upload!) {
    submitAFile(file: $file) {
      filename
      mimetype
      filesize
    }
  }
`;

 return (
    <form>
      <Mutation mutation={UPLOAD_MUTATION} update={mutationComplete}>
        {mutation => (
          <input
            type="file"
            onChange={e => {
              const [file] = e.target.files;
              mutation({
                variables: {
                  file
                }
              });
            }}
          />
        )}
      </Mutation>
    </form>
  );

I'm a bit confused as to how to integrate this into TinyMCE, particularly since the example is based on using a form, and TinyMCE sends me the data encoded in -- as far as I can see -- Base64.

TinyMCE provides me the opportunity to specify a custom upload handler :

tinymce.init({
  selector: 'textarea',  // change this value according to your HTML
  images_upload_handler: function (blobInfo, success, failure) {
    var xhr, formData;

    xhr = new XMLHttpRequest();
    xhr.withCredentials = false;
    xhr.open('POST', 'postAcceptor.php');

    xhr.onload = function() {
      var json;

      if (xhr.status != 200) {
        failure('HTTP Error: ' + xhr.status);
        return;
      }

      json = JSON.parse(xhr.responseText);

      if (!json || typeof json.location != 'string') {
        failure('Invalid JSON: ' + xhr.responseText);
        return;
      }

      success(json.location);
    };

    formData = new FormData();
    formData.append('file', blobInfo.blob(), blobInfo.filename());

    xhr.send(formData);
  }
});

It seems that TinyMCE provides me with a blob, when, as far as I see, Apollo Client expects a file name. Do I just use blobInfo.filename ? Is there a better way to upload TinyMCE images to a GraphQL Apollo Server?

I've never done any image uploading with TinyMCE before.

halfer
  • 19,824
  • 17
  • 99
  • 186
Cerulean
  • 5,543
  • 9
  • 59
  • 111

1 Answers1

2

I've never used this, too ... but I would try to use file_picker_callback.

In this demo you can see files[0] - I'm pretty sure you can insert here a upload mutation call with file as an argument. Result (url) pass to cb() (as in example).

Also adjust configuration as in this answer

xadm
  • 8,219
  • 3
  • 14
  • 25
  • I was under the impression that 'file_picker_callback' was just used to customise the file picker dialog itself. Should one not use `images_upload_handler` (https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler) instead? My thought was if one used`file_picker_callback` to upload the images, if this were only to customise the file picker dialog, it would go on and try to upload the images anyway using the default. – Cerulean Mar 10 '20 at 08:39
  • 1
    @Cerulean It can be used for other file processing (f.e. process/parse text file, insert extracted data as text, table, etc.), not upload only. If callback invoked editor inserts image object - further default processing - not mandatory, of course. IMHO by overwriting `file_picker_callback` you can replace/customize bigger slice (containing default `images_upload_handler` call) of processing chain. You can also use file, not blob. – xadm Mar 10 '20 at 09:28
  • I can figure out how to _upload_ the image using one of the above handlers, but TinyMCE is expecting that the server returns an object, `{location:'path to file'}, where `location` is what fills in the dialog picker's file name. However GraphQL returns everything wrapped in 'data', I think. Is there any way at all to get around the fact that what GraphQL returns is different from what TinyMCE expects? I looked at `file_picker_callback` but I can't see anywhere to customise it there. – Cerulean Mar 10 '20 at 10:25
  • `return { location: data.filename };` ? `filename` is a mutation result property, add host/full path if required – xadm Mar 10 '20 at 10:32