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

Unable to use half of the mui-tiptap controls #174

Closed
sgober opened this issue Oct 27, 2023 · 12 comments
Closed

Unable to use half of the mui-tiptap controls #174

sgober opened this issue Oct 27, 2023 · 12 comments

Comments

@sgober
Copy link

sgober commented Oct 27, 2023

Describe the bug

I am unable to use like half of the mui-tiptap components as they fail with errors like setColor or toggleUnderline are not functions (see screenshot below)

To Reproduce

Steps to reproduce the behavior:

This is what I am trying to use with json forms:

        <RichTextEditorProvider editor={editor}>
          <RichTextField
            controls={
              <MenuControlsContainer>
                {/* <MenuSelectFontFamily /> */}
                <MenuSelectHeading />
                {/* <MenuSelectFontSize /> */}
                <MenuDivider />
                <MenuButtonBold />
                <MenuButtonItalic />
                <MenuButtonUnderline />
                <MenuButtonStrikethrough />
                {/* <MenuButtonSubscript /> */}
                {/* <MenuButtonSuperscript /> */}
                <MenuDivider />
                {/* <MenuButtonTextColor
                  defaultTextColor="fff"
                  swatchColors={[
                    { value: '#000000', label: 'Black' },
                    { value: '#ffffff', label: 'White' },
                    { value: '#888888', label: 'Grey' },
                    { value: '#ff0000', label: 'Red' },
                    { value: '#ff9900', label: 'Orange' },
                    { value: '#ffff00', label: 'Yellow' },
                    { value: '#00d000', label: 'Green' },
                    { value: '#0000ff', label: 'Blue' }
                  ]}
                /> */}
                {/* <MenuButtonHighlightColor /> */}
                {/* <MenuDivider /> */}
                <MenuButtonEditLink />

                <MenuDivider />

                {/* <MenuSelectTextAlign /> */}
                {/* <MenuButtonAlignLeft />
                <MenuButtonAlignCenter />
                <MenuButtonAlignRight />
                <MenuButtonAlignJustify />
                <MenuDivider /> */}
                <MenuButtonOrderedList />
                <MenuButtonBulletedList />
                {/* <MenuButtonTaskList /> */}
                <MenuDivider />
                {/* TODO: why are these disabled */}
                {/* <MenuButtonIndent />
                <MenuButtonUnindent />
                <MenuDivider /> */}

                <MenuButtonBlockquote />
                <MenuDivider />
                <MenuButtonCode />
                <MenuButtonCodeBlock />
                <MenuDivider />
                <MenuButtonUndo />
                <MenuButtonRedo />
              </MenuControlsContainer>
            }
          />
        </RichTextEditorProvider>

With editor defined as follows:

  const editor = useEditor({
    extensions: [StarterKit],
    content: data,
    onUpdate({ editor }) {
      handleChange(path, editor.getHTML());
    }
  });

Expected behavior

Every mui-tiptap component above that is commented out results in an error similar to the one I described above. It is odd that some of them work and some of them don't

Screenshots

What is able to work:
Screenshot 2023-10-27 at 11 22 25 AM

Error when I try to use MenuButtonUnderline:
Screenshot 2023-10-27 at 10 46 49 AM

All other error are similar, not sure if this is a tiptap error or something specific to mui-tiptap

System (please complete the following information):

  • mui-tiptap version: 1.8.5
  • tiptap version: 2.1.12
  • Browser: Chrome
  • Node version: 16.20
  • OS: macOS 13.6.1

Additional context

Add any other context about the problem here.

@sgober sgober changed the title Un Unable to use half of the mui-tiptap controls Oct 27, 2023
sjdemartini added a commit that referenced this issue Oct 27, 2023
Hopefully help avoid confusing errors like the ones seen here
#174
@sjdemartini
Copy link
Owner

@sgober In order to use extensions with Tiptap, you have to (1) install the packages you need (e.g. @tiptap/extension-color) and (2) include the extension in your extensions array when instantiating your editor (e.g., extensions: [StarterKit, Color]).

So rather than just including StarterKit (which bundles many common Tiptap extensions, but not all), you'd need to also pass in explicitly the other extensions you want to utilize as well.

  const editor = useEditor({
    extensions: [StarterKit, Color, Underline], // And all others you want to include!
    content: data,
    onUpdate({ editor }) {
      handleChange(path, editor.getHTML());
    }
  });

For instance, in the demo example in mui-tiptap (essentially the same as what's in the CodeSandbox linked from the README), you can check out the full list of extensions it uses here:

const extensions = useExtensions({
placeholder: "Add your own content here...",
});

extensions={extensions}

/**
* A hook for providing a default set of useful extensions for the MUI-Tiptap
* editor.
*/
export default function useExtensions({
placeholder,
}: UseExtensionsOptions = {}): EditorOptions["extensions"] {
return useMemo(() => {
return [
// We incorporate all of the functionality that's part of
// https://tiptap.dev/api/extensions/starter-kit, plus a few additional
// extensions, including mui-tiptap's
// Note that the Table extension must come before other nodes that also have "tab"
// shortcut keys so that when using the tab key within a table on a node that also
// responds to that shortcut, it respects that inner node with higher precedence
// than the Table. For instance, if you want to indent or dedent a list item
// inside a table, you should be able to do that by pressing tab. Tab should only
// move between table cells if not within such a nested node. See comment here for
// notes on extension ordering
// https://github.com/ueberdosis/tiptap/issues/1547#issuecomment-890848888, and
// note in prosemirror-tables on the need to have these plugins be lower
// precedence
// https://github.com/ueberdosis/prosemirror-tables/blob/1a0428af3ca891d7db648ce3f08a2c74d47dced7/src/index.js#L26-L30
TableImproved.configure({
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
BulletList,
CodeBlock,
Document,
HardBreak,
ListItem,
OrderedList,
Paragraph,
CustomSubscript,
CustomSuperscript,
Text,
// Blockquote must come after Bold, since we want the "Cmd+B" shortcut to
// have lower precedence than the Blockquote "Cmd+Shift+B" shortcut.
// Otherwise using "Cmd+Shift+B" will mistakenly toggle the bold mark.
// (See https://github.com/ueberdosis/tiptap/issues/4005,
// https://github.com/ueberdosis/tiptap/issues/4006)
Bold,
Blockquote,
Code,
Italic,
Underline,
Strike,
CustomLinkExtension.configure({
// autolink is generally useful for changing text into links if they
// appear to be URLs (like someone types in literally "example.com"),
// though it comes with the caveat that if you then *remove* the link
// from the text, and then add a space or newline directly after the
// text, autolink will turn the text back into a link again. Not ideal,
// but probably still overall worth having autolink enabled, and that's
// how a lot of other tools behave as well.
autolink: true,
linkOnPaste: true,
openOnClick: false,
}),
LinkBubbleMenuHandler,
// Extensions
Gapcursor,
HeadingWithAnchor,
TextAlign.configure({
types: ["heading", "paragraph", "image"],
}),
TextStyle,
Color,
FontFamily,
FontSize,
Highlight.configure({ multicolor: true }),
HorizontalRule,
ResizableImage,
// When images are dragged, we want to show the "drop cursor" for where they'll
// land
Dropcursor,
TaskList,
TaskItem.configure({
nested: true,
}),
Mention.configure({
suggestion: mentionSuggestionOptions,
}),
Placeholder.configure({
placeholder,
}),
// We use the regular `History` (undo/redo) extension when not using
// collaborative editing
History,
];
}, [placeholder]);
}

Apologies that this wasn't clearer in the README. I've updated the Choosing your extensions section to hopefully help folks avoid these sorts of confusing errors in the future.

@sgober
Copy link
Author

sgober commented Oct 30, 2023

This was SUPER helpful, THANK YOU!

I pretty much got everything working except for the TextAlign stuff.

This is in my extensions declarations:

TextAlign.configure({
  types: ['heading', 'paragraph', 'image']
})

With this imported: import { TextAlign } from '@tiptap/extension-text-align';

All I get is a disabled dropdown (see screenshot). Is there something I'm missing?

Screenshot 2023-10-30 at 2 58 31 PM

@sgober
Copy link
Author

sgober commented Oct 30, 2023

Also, would it be possible to add a aria-label to the inputs like font family and font size? There currently is one, but it's not on the input so there is a missing label error from an accessibility standpoint

@sjdemartini
Copy link
Owner

sjdemartini commented Oct 30, 2023

@sgober Glad to hear that! The MenuSelectTextAlign component will show as disabled if the editor itself is not editable (likely not the case for you) or if the Tiptap editor can't set text-alignment for the current selected content in the editor. You can see the logic here (note that enabledAlignments will be left, center, right, and justify by default per Tiptap TextAlign default options):

disabled={
!editor?.isEditable ||
!Array.from(enabledAlignments).some((alignment) =>
editor.can().setTextAlign(alignment)
)
}

So for instance, if your cursor/caret is currently inside a code block (not one of the types you configured in TextAlign.configure({ types: [...] }), it will show the text align select as disabled, since you cannot align a code block. But if you put your cursor in a regular text paragraph or click to select an image, the text-align select controls should be enabled. This is the behavior in the CodeSandbox demo linked from the README for instance: https://codesandbox.io/p/sandbox/mui-tiptap-demo-3zl2l6

Try adding some text and highlighting it in your example.

@sgober
Copy link
Author

sgober commented Oct 30, 2023

Unfortunately still no luck...

Screen.Recording.2023-10-30.at.3.29.14.PM.mov

@sjdemartini
Copy link
Owner

sjdemartini commented Oct 30, 2023

Also, would it be possible to add a aria-label to the inputs like font family and font size? There currently is one, but it's not on the input so there is a missing label error from an accessibility standpoint

I've added a new issue to track that here #175. The input itself has aria-hidden="true", so I don't think it needs the aria-label (though you could provide one via inputProps similar to mui/material-ui#25697 (comment) if you like, since inputProps provided to MenuSelect* components get passed onto <Select />). If you know more about how the a11y should be handled, please follow up on that issue!

@sjdemartini
Copy link
Owner

Unfortunately still no luck...

Hm, I'm not sure what's going on. Can you create a CodeSandbox that reproduces the problem? I can definitely take a look that way. Please create a new issue for that if so!

Or can you tell what you're doing differently compared to the CodeSandbox linked from the mui-tiptap README?

@sgober
Copy link
Author

sgober commented Oct 30, 2023

I'm having a ton of issues with code sandbox at the moment where I can't even load yours to be able to for it. This is essentially what I have and matches what you're doing in the code sandbox, but this is essentially what I have. It's used within json forms you, you might have to ignore some

import React from 'react';
import PropTypes from 'prop-types';
import { and, isDescriptionHidden, isStringControl, optionIs, rankWith, showAsRequired } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden, InputLabel, useTheme } from '@mui/material';
import { useFocus } from '@jsonforms/material-renderers';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { Color } from '@tiptap/extension-color';
import { FontFamily } from '@tiptap/extension-font-family';
import { Highlight } from '@tiptap/extension-highlight';
import { Link } from '@tiptap/extension-link';
import { Subscript } from '@tiptap/extension-subscript';
import { Superscript } from '@tiptap/extension-superscript';
import { TableCell } from '@tiptap/extension-table-cell';
import { TableHeader } from '@tiptap/extension-table-header';
import { TableRow } from '@tiptap/extension-table-row';
import { TaskItem } from '@tiptap/extension-task-item';
import { TaskList } from '@tiptap/extension-task-list';
import { TextAlign } from '@tiptap/extension-text-align';
import { TextStyle } from '@tiptap/extension-text-style';
import { Underline } from '@tiptap/extension-underline';
import {
  FontSize,
  MenuButtonAddTable,
  MenuButtonBlockquote,
  MenuButtonBold,
  MenuButtonBulletedList,
  MenuButtonCode,
  MenuButtonCodeBlock,
  MenuButtonEditLink,
  MenuButtonHighlightColor,
  MenuButtonHorizontalRule,
  MenuButtonItalic,
  MenuButtonOrderedList,
  MenuButtonRedo,
  MenuButtonRemoveFormatting,
  MenuButtonSubscript,
  MenuButtonSuperscript,
  MenuButtonStrikethrough,
  MenuButtonTaskList,
  MenuButtonTextColor,
  MenuButtonUnderline,
  MenuButtonUndo,
  MenuControlsContainer,
  MenuDivider,
  MenuSelectFontFamily,
  MenuSelectFontSize,
  MenuSelectHeading,
  MenuSelectTextAlign,
  LinkBubbleMenu,
  LinkBubbleMenuHandler,
  RichTextField,
  RichTextEditorProvider,
  TableImproved
} from 'mui-tiptap';
import _ from 'lodash';

export const RichTextRenderer = props => {
  const theme = useTheme();
  const [focused] = useFocus();
  const { config, data, description, errors, handleChange, label, path, required, uischema, visible } = props;

  const isValid = errors.length === 0;
  const appliedUiSchemaOptions = _.merge({}, config, uischema.options);
  const showDescription = !isDescriptionHidden(
    visible,
    description,
    focused,
    appliedUiSchemaOptions.showUnfocusedDescription
  );
  const firstFormHelperText = showDescription ? description : !isValid ? errors : null;
  const secondFormHelperText = showDescription && !isValid ? errors : null;

  const editor = useEditor({
    extensions: [
      StarterKit,
      Color,
      FontFamily,
      FontSize,
      Highlight,
      Link,
      LinkBubbleMenuHandler,
      Subscript.extend({
        excludes: 'superscript'
      }),
      Superscript.extend({
        excludes: 'subscript'
      }),
      TableCell,
      TableHeader,
      TableImproved.configure({
        resizable: true
      }),
      TableRow,
      TaskItem.configure({
        nested: true
      }),
      TaskList,
      TextAlign.configure({
        types: ['heading', 'paragraph', 'image']
      }),
      TextStyle,
      Underline
    ],
    content: data,
    onUpdate({ editor }) {
      handleChange(path, editor.getHTML());
    }
  });

  const fontOptions = [
    { label: 'Roboto', value: 'Roboto' },
    { label: 'Roboto Condensed', value: 'Roboto Condensed' },
    { label: 'Barlow', value: 'Barlow' },
    { label: 'Barlow Condensed', value: 'Barlow Condensed' }
  ];

  const textColorOptions = [
    { value: '#000000', label: 'Black' },
    { value: '#ffffff', label: 'White' },
    { value: '#888888', label: 'Grey' },
    { value: theme.palette.error.main, label: 'Red' },
    { value: theme.palette.warning.main, label: 'Orange' },
    { value: '#ffff00', label: 'Yellow' },
    { value: theme.palette.success.main, label: 'Green' },
    { value: theme.palette.primary.main, label: 'Blue' }
  ];

  const highlightColorOptions = [
    { value: '#d6d6d6', label: 'Light gray' },
    { value: theme.palette.error.background, label: 'Light red' },
    { value: theme.palette.warning.background, label: 'Light orange' },
    { value: '#ffffb1', label: 'Light yellow' },
    { value: theme.palette.success.background, label: 'Light green' },
    { value: theme.palette.primary.background, label: 'Light blue' }, // need to be updated in theme
    { value: theme.palette.secondary.background, label: 'Light blue' }, // need to be updated in theme
    { value: '#dcd7f3', label: 'Light purple' }
  ];

  return (
    <Hidden xsUp={!visible}>
      <InputLabel
        component="legend"
        error={!isValid}
        required={showAsRequired(required, appliedUiSchemaOptions.hideRequiredAsterisk)}>
        {label}
      </InputLabel>
      <div className="rich-text">
        <RichTextEditorProvider editor={editor}>
          <RichTextField
            controls={
              <MenuControlsContainer>
                <MenuSelectFontFamily options={fontOptions} />
                <MenuDivider />
                <MenuSelectHeading />
                <MenuDivider />
                <MenuSelectFontSize />
                <MenuDivider />
                <MenuButtonBold aria-label="Rich Text Input Bold" />
                <MenuButtonItalic aria-label="Rich Text Input Italic" />
                <MenuButtonUnderline aria-label="Rich Text Input Underline" />
                <MenuButtonStrikethrough aria-label="Rich Text Input Strikethrough" />
                <MenuButtonSubscript aria-label="Rich Text Input Subscript" />
                <MenuButtonSuperscript aria-label="Rich Text Input Superscript" />
                <MenuDivider />
                <MenuButtonTextColor
                  aria-label="Rich Text Input Text Color"
                  defaultTextColor="#000"
                  swatchColors={textColorOptions}
                />
                <MenuButtonHighlightColor
                  aria-label="Rich Text Input Highlight Color"
                  swatchColors={highlightColorOptions}
                />
                <MenuDivider />
                <MenuButtonEditLink aria-label="Rich Text Input Link" />
                <MenuDivider />
                {/* TODO: figure out how this works */}
                <MenuSelectTextAlign />
                <MenuDivider />
                <MenuButtonOrderedList aria-label="Rich Text Input Ordered List" />
                <MenuButtonBulletedList aria-label="Rich Text Input Bullet List" />
                <MenuButtonTaskList aria-label="Rich Text Input Task List" />
                <MenuDivider />
                <MenuButtonBlockquote aria-label="Rich Text Input Block Quote" />
                <MenuDivider />
                <MenuButtonCode aria-label="Rich Text Input Code" />
                <MenuButtonCodeBlock aria-label="Rich Text Input Code Block" />
                <MenuDivider />
                <MenuButtonHorizontalRule aria-label="Rich Text Input Horizontal Rule" />
                <MenuButtonAddTable aria-label="Rich Text Input Add Table" />
                <MenuDivider />
                <MenuButtonRemoveFormatting aria-label="Rich Text Input Remove Formatting" />
                <MenuDivider />
                <MenuButtonUndo aria-label="Rich Text Input Undo" />
                <MenuButtonRedo aria-label="Rich Text Input Redo" />
              </MenuControlsContainer>
            }
          />
          <LinkBubbleMenu />
        </RichTextEditorProvider>
      </div>
      <FormHelperText error={!isValid && !showDescription} sx={{ margin: '3px 0 0' }}>
        {firstFormHelperText}
      </FormHelperText>
      <FormHelperText error={!isValid} sx={{ margin: '3px 0 0' }}>
        {secondFormHelperText}
      </FormHelperText>
    </Hidden>
  );
};

It very much seems like I could be missing something small in the setup. All of the tiptap extensions are version 2.1.12

@sjdemartini
Copy link
Owner

@sgober Thanks, I was able to copy that locally (and remove the jsonforms stuff) and reproduce the above issue with MenuSelectTextAlign showing as disabled.

tl;dr: It seems this can be fixed by changing types: ['heading', 'paragraph', 'image'] to types: ['heading', 'paragraph'], since that array should only contain extension types that are part of the included Tiptap editor extensions, and your example is not including Image/ResizableImage.

I didn't realize this was the behavior until testing your example. But if there are any "unknown" types in that list, the editor.can().setTextAlign() method (used by MenuSelectTextAlign under the hood) will always return false, since it checks to see if "every" type can receive the update of text align under the hood here. Without the Image (or ResizableImage) extension being passed into extensions for useEditor, Tiptap doesn't recognize the "image" type so returns false for commands.updateAttributes("image", {textAlign}).

Let me know if that resolves the problem on your end!

@sgober
Copy link
Author

sgober commented Oct 31, 2023

@sjdemartini this did the trick, THANK YOU! I appreciate you taking the time to figure out what was wrong.

One other quick question for you, I'm seeing the indent/unindent buttons disabled (if you comment the the isTouchDevice conditional out in the code sandbox you can recreate). Is there something missing for this to work? I'm also not able to get shift+tab to work

@sjdemartini
Copy link
Owner

@sgober Great! The indent and unindent actions/buttons are specifically for lists (bullet, number, task). Also note that you can only indent something if there is a list item above it that is part of the same list and is at the same indentation level as it. You could also look at Tiptap's demo here to see the behavior: https://tiptap.dev/api/nodes/bullet-list. Hope that helps.

@sgober
Copy link
Author

sgober commented Nov 2, 2023

Got it - doesn't fully serve my purposes so will probably leave it out for now. Thanks again for all your help!

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants