mui-tiptap icon indicating copy to clipboard operation
mui-tiptap copied to clipboard

Add more built-in features for "Insert image" menu button, for @tiptap/extension-image

Open sjdemartini opened this issue 1 year ago • 17 comments

Right now, there's a general-purpose "insert image" button via MenuButtonAddImage. You provide your own onClick (where you could use your own interface to type in a URL, or you could trigger a file upload), like in the simple example in the internal demo.

As originally mentioned/requested in https://github.com/sjdemartini/mui-tiptap/issues/52, we might also want to add support for more functionality/UI built into mui-tiptap, with:

  • [ ] An interface for a user to provide a URL
  • [x] A user to upload an image from their filesystem (https://github.com/sjdemartini/mui-tiptap/pull/147, available as of mui-tiptap v1.8.0)

See example screenshots here https://github.com/sjdemartini/mui-tiptap/issues/52#issuecomment-1637220903

sjdemartini avatar Aug 23 '23 17:08 sjdemartini

👋🏻 Do you have a simple example of triggering a file upload when clicking the Insert image menu button?

devth avatar Aug 26 '23 17:08 devth

Hi @devth, I don't have one off hand, though I plan to add one soon. I think an approach like this https://medium.com/web-dev-survey-from-kyoto/how-to-customize-the-file-upload-button-in-react-b3866a5973d8 (https://codesandbox.io/embed/how-to-customize-file-upload-buttons-in-react-wthkrz?codemirror=1) is similar to what I had in mind, except swapping the <button> for the MenuButtonAddImage, and using logic similar to the mui-tiptap URL example to insert the image into the editor after you upload it (using a URL returned from the server for the uploaded image). There's more to do to handle multi-file uploads/insertions with the Tiptap API, which I plan to also demo, but maybe that's enough to get you started in the meantime. I'll keep you posted when I add something.

sjdemartini avatar Aug 26 '23 20:08 sjdemartini

@sjdemartini awesome, thanks so much! I’ll play around and see what I can come up with.

devth avatar Aug 26 '23 23:08 devth

@devth I've released a new version 1.8.0 which includes a new component MenuButtonImageUpload that encapsulates the image upload functionality intended/discussed above. You just need to supply your own onUploadFiles prop in order to take/upload the image files the user provided, and return URLs at which the images can be served/viewed. The component will handle the hidden file input, button behavior, and insertion of the images into the editor content.

I've also updated the demo to show how to use drag-and-drop and pasting of image files (notes on this are in the README here).

Hope that's useful!

sjdemartini avatar Sep 11 '23 03:09 sjdemartini

@sjdemartini this looks awesome! thanks.

devth avatar Sep 11 '23 14:09 devth

hi i am new to programing as a whole and am trying to use the mui titap pludin in my react MUI project, i copied the all-in-one component from the gitgub repo, and am trying to add controls but i get such errors "React Router caught the following error during render TypeError: editor.can(...).toggleSuperscript is not a function", toggleSuperscript is the control i want to add for example, and this goes for all other controls i add, Screenshot 2024-03-08 at 23 11 28 as you can see from the screenshot all the commented controls give the error in the console and my application breaks. please how can i go about this.

rextambua avatar Mar 08 '24 20:03 rextambua

@rextambua This comment doesn't seem to be related to the original issue here—please create new issues rather than changing the topic if you have a problem. In this case though, it appears your issue is that you haven't installed and added the necessary extensions (notably Superscript here). See past issues about this here https://github.com/sjdemartini/mui-tiptap/issues/174#issuecomment-1783299136 (or similarly here https://github.com/sjdemartini/mui-tiptap/issues/187) and this section of the README https://github.com/sjdemartini/mui-tiptap?tab=readme-ov-file#choosing-your-editor-extensions.

sjdemartini avatar Mar 08 '24 20:03 sjdemartini

sorry and thanks, i will read this material and if need arises i will create a new issue

rextambua avatar Mar 08 '24 20:03 rextambua

Hey @sjdemartini, quick question on this - when I use insertImages it correctly creates html like this in the editor:

<div class="react-renderer node-image ProseMirror-selectednode" contenteditable="false" draggable="true">
	<div data-node-view-wrapper="" style="white-space: normal; text-align: right; width: 100%;">
		<div class="css-1fk4pfw-ResizableImageComponent-imageContainer">
			<img src="https://firebasestorage.googleapis.com/v0/b/converge-mt.appspot.com/o/images%2Fuploads%2Facme%2Feditor%2FlxsKFckgYGPnfZImBWmJKQSp3cR2%2F1712845173502.IMG_5320.jpeg?alt=media&amp;token=c174d23e-05ab-4b38-aa29-562f3e180e7f" height="auto" width="138" class="ProseMirror-selectednode css-1cxz5lg-ResizableImageComponent-image-ResizableImageComponent-imageSelected" data-drag-handle="true" style="aspect-ratio: 1 / 1;" />
			<div class="css-e5u3zl-ResizableImageResizer-root-ResizableImageComponent-resizer"></div>
		</div>
	</div>
</div>

But when I call editor.getHTML() later it results in a very stripped-down equivalent:

<img height="auto" src="https://firebasestorage.googleapis.com/v0/b/converge-mt.appspot.com/o/images%2Fuploads%2Facme%2Feditor%2FlxsKFckgYGPnfZImBWmJKQSp3cR2%2F1712845173502.IMG_5320.jpeg?alt=media&amp;token=c174d23e-05ab-4b38-aa29-562f3e180e7f" width="138" style="text-align: right; aspect-ratio: 1 / 1;" />

lacking the wrapper, which causes alignment to not work.

I compared this to the official demo and that does not happen - the resulting HTML still contains a wrapper like:

<div class="react-renderer node-image" contenteditable="false" draggable="true">
	<div data-node-view-wrapper="" style="white-space: normal; text-align: right; width: 100%;">
		<div class="css-1fk4pfw-ResizableImageComponent-imageContainer">
			<img src="blob:https://3zl2l6-5173.csb.app/58bd9814-3f86-439d-bcfe-f215abd3fe60" height="auto" width="394" alt="snoop Samurai.jpeg" class="css-12r3bmv-ResizableImageComponent-image" data-drag-handle="true" style="aspect-ratio: 1 / 1;" />
		</div>
	</div>
</div>

I am configuring ResizableImage in my useExtensions, just like the demo. Not sure what else I'm missing 🤔

Edit: I just noticed it uses RichTextReadOnly to render the resulting HTML - does that do some magic? Up till now I was just using html-react-parser to render the resulting html.

devth avatar Apr 11 '24 14:04 devth

@devth getHTML() does not return the full Tiptap-rendered markup (such as the react-renderer elements, etc.). getHTML is for returning a serialized version of the the editor content—it will always use the renderHTML method representation of each extension/node and not custom Node views (React or otherwise) or interactive elements. When its output is parsed again by Tiptap later, it should ideally produce/render the same visual result as before. But on its own, the HTML may not appear the same.

I'm not sure where you are referring to in saying that the CodeSandbox demo has getHTML containing a wrapper. I have just tested and confirmed (via the "Save" button example there) that it only returns the ordinary img markup and no more:

image

In general, you should use Tiptap to render and display Tiptap content, like with RichTextReadOnly as you note, rather than trying to inject the HTML directly into the page.

sjdemartini avatar Apr 11 '24 14:04 sjdemartini

Thanks for the explanation!

What I meant in the CodeSandbox demo was that when the html is rendered via RichTextReadOnly it contains the wrapper when I inspect it in devtools: image

I'll look into using RichTextReadOnly in my app. 👍🏻
Not sure how I missed that after using mui-tiptap for the last year 😅

devth avatar Apr 11 '24 15:04 devth

One thing I'm trying to figure out is how I can replace anchor tags coming from tiptap getHTML with Next.js Links. Currently I do that with html-react-parser's replace function:

const parseHtmlWithNextLink = (html: string) => {
  return htmlParse(html, {
    replace: (domNode) => {
      if (domNode instanceof Element && domNode.attribs) {
        if (domNode.name === "a") {
          const props = attributesToProps(domNode.attribs);
          return (
            <Link legacyBehavior href={domNode.attribs.href} {...props}>
              <a {...props}>{domToReact(domNode.children as DOMNode[])}</a>
            </Link>
          );
        }
      }
    },
  });
};

🤔

devth avatar Apr 11 '24 15:04 devth

An alternate solution I tried is to keep using html-react-parser, and add my own wrapping logic so that image alignment works:

const parseHtmlWithNextLink = (html: string) => {
  return htmlParse(html, {
    replace: (domNode) => {
      if (domNode instanceof Element && domNode.attribs) {
        // surround images with a div that respects alignment
        const props = attributesToProps(domNode.attribs);
        if (domNode.name === "img") {
          const style = props.style;
          const divStyle = {
            width: "100%",
            textAlign: style?.textAlign as CSSProperties["textAlign"],
          };
          return (
            <div style={divStyle}>
              <img
                alt={props.alt as string}
                {...attributesToProps(domNode.attribs)}
              />
            </div>
          );
        }
        // turn anchor tags into Next.js Link elements for client-side routing
        if (domNode.name === "a") {
          return (
            <Link legacyBehavior href={domNode.attribs.href} {...props}>
              <a {...props}>{domToReact(domNode.children as DOMNode[])}</a>
            </Link>
          );
        }
      }
    },
  });
};

Not sure this is a good idea, but it "works".

devth avatar Apr 11 '24 16:04 devth

Great work firstly . Thanks. An interface for a user to provide a URL -> Assuming this is still open ? Couldnt find a way to do this yet in the latest release. May i ask when you would condier adding this feature where just image URL is provided for render ?

pymenow avatar May 13 '24 16:05 pymenow

Oops Ive now included a MUI diag box for insert Images. However upon inserting a Transparent PNG the background showa white. Any think I may be missing ?

pymenow avatar May 13 '24 17:05 pymenow

@sjdemartini would be great if you could help let me know if there is something missing WRT adding transparent images

pymenow avatar May 17 '24 06:05 pymenow

@pymenow There is not a specific built-in way to add an image from a URL in mui-tiptap, but you can do it yourself with your own mui-tiptap MenuButton, MUI Dialog, etc. as you seem to be suggesting.

In terms of transparent images, mui-tiptap currently has behavior similar to Chrome, which is to add a light grey background to all (transparent) images, as this tends to be useful since most images are created on a light background and will help make them readable even in dark mode. (Though of course some images, likely a minority of them, will have the opposite problem.) See the notes here for more of the behavior details and rationale https://github.com/sjdemartini/mui-tiptap/blob/9e4bbb5ab66294ce654ba2e7c141d21fe1e72325/src/styles.ts#L489-L516

You can override the styles by targeting img:not(.ProseMirror-separator) inside of the mui-tiptap RichTextEditor/ProseMirror context and adding CSS to change to backgroundColor: "transparent" or similar.

sjdemartini avatar May 27 '24 18:05 sjdemartini