Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 of 2 tasks
sjdemartini opened this issue Aug 23, 2023 · 17 comments
Open
1 of 2 tasks
Labels
enhancement New feature or request

Comments

@sjdemartini
Copy link
Owner

sjdemartini commented Aug 23, 2023

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 #52, we might also want to add support for more functionality/UI built into mui-tiptap, with:

See example screenshots here #52 (comment)

@sjdemartini sjdemartini mentioned this issue Aug 23, 2023
14 tasks
@sjdemartini sjdemartini changed the title Using url Add more built-in features for "Insert image" menu button, for @tiptap/extension-image Aug 23, 2023
@devth
Copy link

devth commented Aug 26, 2023

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

@sjdemartini
Copy link
Owner Author

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
Copy link

devth commented Aug 26, 2023

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

@sjdemartini
Copy link
Owner Author

@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
Copy link

devth commented Sep 11, 2023

@sjdemartini this looks awesome! thanks.

@rextambua
Copy link

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
Copy link
Owner Author

@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 #174 (comment) (or similarly here #187) and this section of the README https://github.com/sjdemartini/mui-tiptap?tab=readme-ov-file#choosing-your-editor-extensions.

@rextambua
Copy link

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

sjdemartini added a commit that referenced this issue Mar 21, 2024
Since it's common to get questions/"issues" pertaining to this and is
important to help diagnose as well.

e.g. #187,
#174,
#136 (comment)
sjdemartini added a commit that referenced this issue Mar 21, 2024
Since it's common to get questions/"issues" pertaining to this and is
important to help diagnose as well.

e.g. #187,
#174,
#136 (comment)
@devth
Copy link

devth commented Apr 11, 2024

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
Copy link
Owner Author

sjdemartini commented Apr 11, 2024

@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
Copy link

devth commented Apr 11, 2024

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
Copy link

devth commented Apr 11, 2024

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
Copy link

devth commented Apr 11, 2024

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

@sjdemartini sjdemartini added the enhancement New feature or request label Apr 18, 2024
@pymenow
Copy link

pymenow commented May 13, 2024

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
Copy link

pymenow commented May 13, 2024

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
Copy link

pymenow commented May 17, 2024

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

@sjdemartini
Copy link
Owner Author

@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

mui-tiptap/src/styles.ts

Lines 489 to 516 in 9e4bbb5

/**
* Get the background color styles to use for user-provided images being previewed.
*
* Useful for handling transparent images better in dark mode, since they typically
* would have been created on a light-colored background context (e.g. may have black
* text labels that wouldn't be readable in dark mode otherwise).
*/
export function getImageBackgroundColorStyles(theme: Theme): {
backgroundColor?: string;
color?: string;
} {
if (theme.palette.mode !== "dark") {
// We only need to alter the colors in dark mode
return {};
}
const backgroundColor = theme.palette.grey[200];
return {
// We add a light grey background to the image when in dark mode, similar to what
// Chrome does in dark mode when viewing an image in its own tab. (Chrome uses a
// background color of "hsl(0, 0%, 90%)", or equivalently "#e6e6e6".)
backgroundColor,
// The "alt text" of an image will be shown if it fails to render (e.g. for
// tif or other file formats that can't be rendered in-browser, like with a
// pending image upload), so make sure the font color is readable
color: theme.palette.getContrastText(backgroundColor),
};
}

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants