UNPKG

mui-tiptap

Version:

A Material-UI (MUI) styled WYSIWYG rich text editor, using Tiptap

287 lines (286 loc) 19.3 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /// <reference types="@tiptap/extension-paragraph" /> import MenuItem from "@mui/material/MenuItem"; import { styled, useThemeProps } from "@mui/material/styles"; import { clsx } from "clsx"; import { useCallback, useMemo } from "react"; import { useRichTextEditorContext } from "../context"; import { getEditorStyles, getUtilityComponentName } from "../styles"; import { getAttributesForEachSelected } from "../utils/getAttributesForEachSelected"; import { getShortcutKeysDescription } from "../utils/platform"; import MenuButtonTooltip from "./MenuButtonTooltip"; import MenuSelect from "./MenuSelect"; import { menuSelectHeadingClasses, } from "./MenuSelectHeading.classes"; const componentName = getUtilityComponentName("MenuSelectHeading"); const MenuSelectHeadingRoot = styled((MenuSelect), { name: componentName, slot: "root", overridesResolver: (props, styles) => styles.root, })({ [`& .${menuSelectHeadingClasses.selectInput}`]: { // We use a fixed width so that the Select element won't change sizes as // the selected option changes (which would shift other elements in the // menu bar) width: 77, }, }); const MenuSelectMenuOption = styled(MenuButtonTooltip, { name: componentName, slot: "menuOption", overridesResolver: (props, styles) => styles.menuOption, })({ // These styles ensure the item fills its MenuItem container, and the // tooltip appears in the same place when hovering over the item generally // (not just the text of the item) display: "block", width: "100%", }); const MenuSelectHeadingOption = styled(MenuSelectMenuOption, { name: componentName, slot: "headingOption", overridesResolver: (props, styles) => styles.option, })({ marginBlockStart: 0, marginBlockEnd: 0, fontWeight: "bold", }); const MenuSelectHeadingOption1 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption1", overridesResolver: (props, styles) => styles.headingOption1, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h1"].fontSize, })); const MenuSelectHeadingOption2 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption2", overridesResolver: (props, styles) => styles.headingOption2, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h2"].fontSize, })); const MenuSelectHeadingOption3 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption3", overridesResolver: (props, styles) => styles.headingOption3, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h3"].fontSize, })); const MenuSelectHeadingOption4 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption4", overridesResolver: (props, styles) => styles.headingOption4, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h4"].fontSize, })); const MenuSelectHeadingOption5 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption5", overridesResolver: (props, styles) => styles.headingOption5, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h5"].fontSize, })); const MenuSelectHeadingOption6 = styled(MenuSelectHeadingOption, { name: componentName, slot: "headingOption6", overridesResolver: (props, styles) => styles.headingOption6, })(({ theme }) => ({ fontSize: getEditorStyles(theme)["& h6"].fontSize, })); const HEADING_OPTION_VALUES = { Paragraph: "Paragraph", Heading1: "Heading 1", Heading2: "Heading 2", Heading3: "Heading 3", Heading4: "Heading 4", Heading5: "Heading 5", Heading6: "Heading 6", }; const HEADING_OPTION_VALUE_TO_LEVEL = { [HEADING_OPTION_VALUES.Heading1]: 1, [HEADING_OPTION_VALUES.Heading2]: 2, [HEADING_OPTION_VALUES.Heading3]: 3, [HEADING_OPTION_VALUES.Heading4]: 4, [HEADING_OPTION_VALUES.Heading5]: 5, [HEADING_OPTION_VALUES.Heading6]: 6, }; const LEVEL_TO_HEADING_OPTION_VALUE = { 1: HEADING_OPTION_VALUES.Heading1, 2: HEADING_OPTION_VALUES.Heading2, 3: HEADING_OPTION_VALUES.Heading3, 4: HEADING_OPTION_VALUES.Heading4, 5: HEADING_OPTION_VALUES.Heading5, 6: HEADING_OPTION_VALUES.Heading6, }; export default function MenuSelectHeading(inProps) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; const props = useThemeProps({ props: inProps, name: componentName }); const { labels, shortcutKeys: shortcutKeyOverrides, hideShortcuts = false, classes = {}, sx, ...menuSelectProps } = props; const shortcutKeys = hideShortcuts ? undefined : { paragraph: (_a = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.paragraph) !== null && _a !== void 0 ? _a : ["mod", "alt", "0"], heading1: (_b = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading1) !== null && _b !== void 0 ? _b : ["mod", "alt", "1"], heading2: (_c = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading2) !== null && _c !== void 0 ? _c : ["mod", "alt", "2"], heading3: (_d = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading3) !== null && _d !== void 0 ? _d : ["mod", "alt", "3"], heading4: (_e = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading4) !== null && _e !== void 0 ? _e : ["mod", "alt", "4"], heading5: (_f = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading5) !== null && _f !== void 0 ? _f : ["mod", "alt", "5"], heading6: (_g = shortcutKeyOverrides === null || shortcutKeyOverrides === void 0 ? void 0 : shortcutKeyOverrides.heading6) !== null && _g !== void 0 ? _g : ["mod", "alt", "6"], }; const editor = useRichTextEditorContext(); const handleHeadingType = useCallback((event) => { const value = event.target.value; if (value === HEADING_OPTION_VALUES.Paragraph) { editor === null || editor === void 0 ? void 0 : editor.chain().setParagraph().focus().run(); } else if (value in HEADING_OPTION_VALUE_TO_LEVEL) { editor === null || editor === void 0 ? void 0 : editor.chain().setHeading({ level: HEADING_OPTION_VALUE_TO_LEVEL[value], }).focus().run(); } }, [editor]); let selectedValue = ""; let currentLevel; if (editor === null || editor === void 0 ? void 0 : editor.isActive("paragraph")) { selectedValue = HEADING_OPTION_VALUES.Paragraph; } else if (editor === null || editor === void 0 ? void 0 : editor.isActive("heading")) { const currentNodeHeadingAttributes = getAttributesForEachSelected(editor.state, "heading"); const currentNodeLevels = currentNodeHeadingAttributes.map((attrs) => attrs.level); const numCurrentNodeLevels = new Set(currentNodeLevels).size; // We only want to show a selected level value if all of the selected nodes // have the same level. (That way a user can properly change the level when // selecting across two separate headings, and so we don't mistakenly just // show the first of the selected nodes' levels and not allow changing all // selected to that heading level. See // https://github.com/ueberdosis/tiptap/issues/3481.) currentLevel = numCurrentNodeLevels === 1 ? currentNodeLevels[0] : undefined; if (currentLevel && currentLevel in LEVEL_TO_HEADING_OPTION_VALUE) { selectedValue = LEVEL_TO_HEADING_OPTION_VALUE[currentLevel]; } } const isCurrentlyParagraphOrHeading = selectedValue !== ""; const canSetParagraph = !!(editor === null || editor === void 0 ? void 0 : editor.can().setParagraph()); // Figure out which settings the user has enabled with the heading extension const enabledHeadingLevels = useMemo(() => { var _a; const headingExtension = editor === null || editor === void 0 ? void 0 : editor.extensionManager.extensions.find((extension) => extension.name == "heading"); return new Set((_a = headingExtension === null || headingExtension === void 0 ? void 0 : headingExtension.options.levels) !== null && _a !== void 0 ? _a : []); }, [editor]); // In determining whether we can set a heading, at least one heading level // must be enabled in the extension configuration. We have to pass a level // when running `can().setHeading()`, so we just use the first one that is // enabled. And since some Tiptap versions return `false` for // `can().setHeading()` when passing the current level, we also have to check // whether that arbitrary first level is the `currentLevel` (see // https://github.com/sjdemartini/mui-tiptap/issues/197). const firstEnabledHeadingResult = enabledHeadingLevels.values().next(); const firstEnabledHeading = firstEnabledHeadingResult.done ? undefined : firstEnabledHeadingResult.value; const canSetHeading = firstEnabledHeading !== undefined && (currentLevel === firstEnabledHeading || !!(editor === null || editor === void 0 ? void 0 : editor.can().setHeading({ level: firstEnabledHeading }))); return ( // We currently have to specify that the value is of type // `HeadingOptionValue | ""` rather than just `HeadingOptionValue` due to // the bug reported here https://github.com/mui/material-ui/issues/34083. We // need it to support "" as a possible value in the `renderValue` function // below since we have `displayEmpty=true`, and the types don't properly // handle that scenario. _jsxs(MenuSelectHeadingRoot, { onChange: handleHeadingType, disabled: !(editor === null || editor === void 0 ? void 0 : editor.isEditable) || (!isCurrentlyParagraphOrHeading && !canSetParagraph && !canSetHeading), displayEmpty: true, renderValue: (selected) => { var _a, _b; let result; if (selected === "") { // Handle the deprecated `emptyValue` label name, falling back to the // newer `labels.empty`, and finally our default empty label // eslint-disable-next-line @typescript-eslint/no-deprecated result = (_b = (_a = labels === null || labels === void 0 ? void 0 : labels.emptyValue) !== null && _a !== void 0 ? _a : labels === null || labels === void 0 ? void 0 : labels.empty) !== null && _b !== void 0 ? _b : _jsx("em", { children: "Change to\u2026" }); } else if (selected === HEADING_OPTION_VALUES.Paragraph) { result = labels === null || labels === void 0 ? void 0 : labels.paragraph; } else if (selected === HEADING_OPTION_VALUES.Heading1) { result = labels === null || labels === void 0 ? void 0 : labels.heading1; } else if (selected === HEADING_OPTION_VALUES.Heading2) { result = labels === null || labels === void 0 ? void 0 : labels.heading2; } else if (selected === HEADING_OPTION_VALUES.Heading3) { result = labels === null || labels === void 0 ? void 0 : labels.heading3; } else if (selected === HEADING_OPTION_VALUES.Heading4) { result = labels === null || labels === void 0 ? void 0 : labels.heading4; } else if (selected === HEADING_OPTION_VALUES.Heading5) { result = labels === null || labels === void 0 ? void 0 : labels.heading5; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (selected === HEADING_OPTION_VALUES.Heading6) { result = labels === null || labels === void 0 ? void 0 : labels.heading6; } return result !== null && result !== void 0 ? result : selected; }, tooltipTitle: "Styles", ...menuSelectProps, value: selectedValue, inputProps: { ...menuSelectProps.inputProps, className: clsx([ menuSelectHeadingClasses.selectInput, classes.selectInput, (_h = menuSelectProps.inputProps) === null || _h === void 0 ? void 0 : _h.className, ]), }, className: clsx([ menuSelectHeadingClasses.root, classes.root, menuSelectProps.className, ]), sx: sx, children: [_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Paragraph, disabled: !isCurrentlyParagraphOrHeading && !canSetParagraph, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.paragraph), children: _jsx(MenuSelectMenuOption, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.paragraph, placement: "right", classes: { contentWrapper: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.paragraphOption, classes.paragraphOption, ]), }, children: (_j = labels === null || labels === void 0 ? void 0 : labels.paragraph) !== null && _j !== void 0 ? _j : HEADING_OPTION_VALUES.Paragraph }) }), enabledHeadingLevels.has(1) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading1, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading1), children: _jsx(MenuSelectHeadingOption1, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading1, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption1, classes.headingOption1, ]), children: (_k = labels === null || labels === void 0 ? void 0 : labels.heading1) !== null && _k !== void 0 ? _k : HEADING_OPTION_VALUES.Heading1 }) })), enabledHeadingLevels.has(2) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading2, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading2), children: _jsx(MenuSelectHeadingOption2, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading2, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption2, classes.headingOption2, ]), children: (_l = labels === null || labels === void 0 ? void 0 : labels.heading2) !== null && _l !== void 0 ? _l : HEADING_OPTION_VALUES.Heading2 }) })), enabledHeadingLevels.has(3) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading3, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading3), children: _jsx(MenuSelectHeadingOption3, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading3, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption3, classes.headingOption3, ]), children: (_m = labels === null || labels === void 0 ? void 0 : labels.heading3) !== null && _m !== void 0 ? _m : HEADING_OPTION_VALUES.Heading3 }) })), enabledHeadingLevels.has(4) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading4, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading4), children: _jsx(MenuSelectHeadingOption4, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading4, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption4, classes.headingOption4, ]), children: (_o = labels === null || labels === void 0 ? void 0 : labels.heading4) !== null && _o !== void 0 ? _o : HEADING_OPTION_VALUES.Heading4 }) })), enabledHeadingLevels.has(5) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading5, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading5), children: _jsx(MenuSelectHeadingOption5, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading5, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption5, classes.headingOption5, ]), children: (_p = labels === null || labels === void 0 ? void 0 : labels.heading5) !== null && _p !== void 0 ? _p : HEADING_OPTION_VALUES.Heading5 }) })), enabledHeadingLevels.has(6) && (_jsx(MenuItem, { value: HEADING_OPTION_VALUES.Heading6, disabled: !canSetHeading, "aria-description": getShortcutKeysDescription(shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading6), children: _jsx(MenuSelectHeadingOption6, { label: "", shortcutKeys: shortcutKeys === null || shortcutKeys === void 0 ? void 0 : shortcutKeys.heading6, placement: "right", className: clsx([ menuSelectHeadingClasses.menuOption, classes.menuOption, menuSelectHeadingClasses.headingOption, classes.headingOption, menuSelectHeadingClasses.headingOption6, classes.headingOption6, ]), children: (_q = labels === null || labels === void 0 ? void 0 : labels.heading6) !== null && _q !== void 0 ? _q : HEADING_OPTION_VALUES.Heading6 }) }))] })); }