UNPKG

mui-tiptap

Version:

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

196 lines (195 loc) 14.1 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); /// <reference types="@tiptap/extension-paragraph" /> const material_1 = require("@mui/material"); const react_1 = require("react"); const mui_1 = require("tss-react/mui"); const context_1 = require("../context"); const styles_1 = require("../styles"); const getAttributesForEachSelected_1 = require("../utils/getAttributesForEachSelected"); const MenuButtonTooltip_1 = __importDefault(require("./MenuButtonTooltip")); const MenuSelect_1 = __importDefault(require("./MenuSelect")); const useStyles = (0, mui_1.makeStyles)({ name: { MenuSelectHeading } })((theme) => { const editorStyles = (0, styles_1.getEditorStyles)(theme); return { 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, }, 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%", }, headingOption: { marginBlockStart: 0, marginBlockEnd: 0, fontWeight: "bold", }, headingOption1: { fontSize: editorStyles["& h1"].fontSize, }, headingOption2: { fontSize: editorStyles["& h2"].fontSize, }, headingOption3: { fontSize: editorStyles["& h3"].fontSize, }, headingOption4: { fontSize: editorStyles["& h4"].fontSize, }, headingOption5: { fontSize: editorStyles["& h5"].fontSize, }, headingOption6: { fontSize: editorStyles["& 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, }; function MenuSelectHeading(_a) { var _b, _c, _d, _e, _f, _g, _h, _j; var { labels, hideShortcuts = false } = _a, menuSelectProps = __rest(_a, ["labels", "hideShortcuts"]); const { classes, cx } = useStyles(); const editor = (0, context_1.useRichTextEditorContext)(); const handleHeadingType = (0, react_1.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 = (0, getAttributesForEachSelected_1.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 = (0, react_1.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. (0, jsx_runtime_1.jsxs)(MenuSelect_1.default, Object.assign({ 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 : (0, jsx_runtime_1.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; }, "aria-label": "Text headings", tooltipTitle: "Styles" }, menuSelectProps, { value: selectedValue, inputProps: Object.assign(Object.assign({}, menuSelectProps.inputProps), { className: cx(classes.selectInput, (_b = menuSelectProps.inputProps) === null || _b === void 0 ? void 0 : _b.className) }), children: [(0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Paragraph, disabled: !isCurrentlyParagraphOrHeading && !canSetParagraph, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "0"], placement: "right", contentWrapperClassName: classes.menuOption, children: (_c = labels === null || labels === void 0 ? void 0 : labels.paragraph) !== null && _c !== void 0 ? _c : HEADING_OPTION_VALUES.Paragraph }) }), enabledHeadingLevels.has(1) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading1, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "1"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption1), children: (_d = labels === null || labels === void 0 ? void 0 : labels.heading1) !== null && _d !== void 0 ? _d : HEADING_OPTION_VALUES.Heading1 }) })), enabledHeadingLevels.has(2) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading2, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "2"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption2), children: (_e = labels === null || labels === void 0 ? void 0 : labels.heading2) !== null && _e !== void 0 ? _e : HEADING_OPTION_VALUES.Heading2 }) })), enabledHeadingLevels.has(3) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading3, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "3"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption3), children: (_f = labels === null || labels === void 0 ? void 0 : labels.heading3) !== null && _f !== void 0 ? _f : HEADING_OPTION_VALUES.Heading3 }) })), enabledHeadingLevels.has(4) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading4, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "4"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption4), children: (_g = labels === null || labels === void 0 ? void 0 : labels.heading4) !== null && _g !== void 0 ? _g : HEADING_OPTION_VALUES.Heading4 }) })), enabledHeadingLevels.has(5) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading5, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "5"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption5), children: (_h = labels === null || labels === void 0 ? void 0 : labels.heading5) !== null && _h !== void 0 ? _h : HEADING_OPTION_VALUES.Heading5 }) })), enabledHeadingLevels.has(6) && ((0, jsx_runtime_1.jsx)(material_1.MenuItem, { value: HEADING_OPTION_VALUES.Heading6, disabled: !canSetHeading, children: (0, jsx_runtime_1.jsx)(MenuButtonTooltip_1.default, { label: "", shortcutKeys: hideShortcuts ? undefined : ["mod", "alt", "6"], placement: "right", contentWrapperClassName: cx(classes.menuOption, classes.headingOption, classes.headingOption6), children: (_j = labels === null || labels === void 0 ? void 0 : labels.heading6) !== null && _j !== void 0 ? _j : HEADING_OPTION_VALUES.Heading6 }) }))] }))); } exports.default = MenuSelectHeading;