sjdemartini / mui-tiptap

A Material UI (MUI) styled WYSIWYG rich text editor, using Tiptap
MIT License
299 stars 40 forks source link

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

Open sjdemartini opened 1 year ago

sjdemartini commented 1 year ago

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:

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

devth commented 1 year ago

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

sjdemartini commented 1 year ago

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.

devth commented 1 year ago

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

sjdemartini commented 1 year ago

@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!

devth commented 1 year ago

@sjdemartini this looks awesome! thanks.

rextambua commented 7 months ago

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.

sjdemartini commented 7 months ago

@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.

rextambua commented 7 months ago

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

devth commented 5 months ago

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.

sjdemartini commented 5 months ago

@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.

devth commented 5 months ago

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 commented 5 months ago

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 commented 5 months ago

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".

pymenow commented 4 months ago

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 commented 4 months ago

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 commented 4 months ago

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

sjdemartini commented 4 months ago

@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.