UNPKG

ra-input-rich-text

Version:

<RichTextInput> component for react-admin, useful for editing HTML code in admin GUIs.

195 lines 9.2 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; import * as React from 'react'; import { useEffect } from 'react'; import { FormHelperText } from '@mui/material'; import { styled } from '@mui/material/styles'; import { Color } from '@tiptap/extension-color'; import Highlight from '@tiptap/extension-highlight'; import Image from '@tiptap/extension-image'; import Link from '@tiptap/extension-link'; import TextAlign from '@tiptap/extension-text-align'; import TextStyle from '@tiptap/extension-text-style'; import Underline from '@tiptap/extension-underline'; import { EditorContent, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import clsx from 'clsx'; import { useInput, useResourceContext } from 'ra-core'; import { InputHelperText, Labeled, } from 'ra-ui-materialui'; import { RichTextInputToolbar } from "./RichTextInputToolbar.js"; import { TiptapEditorProvider } from "./TiptapEditorProvider.js"; /** * A rich text editor for the react-admin that is accessible and supports translations. Based on [Tiptap](https://www.tiptap.dev/). * @param props The input props. Accept all common react-admin input props. * @param {EditorOptions} props.editorOptions The options to pass to the Tiptap editor. See Tiptap settings [here](https://tiptap.dev/api/editor#settings). * @param {ReactNode} props.toolbar The toolbar containing the editors commands. * * @example <caption>Customizing the editors options</caption> * import { RichTextInput, RichTextInputToolbar } from 'ra-input-rich-text'; * const MyRichTextInput = (props) => ( * <RichTextInput * toolbar={<RichTextInputToolbar size="large" />} * label="Body" * source="body" * {...props} * /> * ); * * @example <caption>Customizing the toolbar size</caption> * import { RichTextInput, RichTextInputToolbar } from 'ra-input-rich-text'; * const MyRichTextInput = (props) => ( * <RichTextInput * toolbar={<RichTextInputToolbar size="large" />} * label="Body" * source="body" * {...props} * /> * ); * * @example <caption>Customizing the toolbar commands</caption> * import { RichTextInput, RichTextInputToolbar } from 'ra-input-rich-text'; * const MyRichTextInput = ({ size, ...props }) => ( * <RichTextInput * toolbar={( * <RichTextInputToolbar> * <LevelSelect size={size} /> * <FormatButtons size={size} /> * <ColorButtons size={size} /> * <ListButtons size={size} /> * <LinkButtons size={size} /> * <ImageButtons size={size} /> * <QuoteButtons size={size} /> * <ClearButtons size={size} /> * </RichTextInputToolbar> * )} * label="Body" * source="body" * {...props} * /> * ); */ export var RichTextInput = function (props) { var _a; var className = props.className, _b = props.defaultValue, defaultValue = _b === void 0 ? '' : _b, _c = props.disabled, disabled = _c === void 0 ? false : _c, _d = props.editorOptions, editorOptions = _d === void 0 ? DefaultEditorOptions : _d, _e = props.fullWidth, fullWidth = _e === void 0 ? true : _e, helperText = props.helperText, label = props.label, _f = props.readOnly, readOnly = _f === void 0 ? false : _f, source = props.source, sx = props.sx, toolbar = props.toolbar; var resource = useResourceContext(props); var _g = useInput(__assign(__assign({}, props), { source: source, defaultValue: defaultValue })), id = _g.id, field = _g.field, isRequired = _g.isRequired, fieldState = _g.fieldState, isSubmitted = _g.formState.isSubmitted; var editor = useEditor(__assign(__assign({}, editorOptions), { editable: !disabled && !readOnly, content: field.value, editorProps: __assign(__assign({}, editorOptions === null || editorOptions === void 0 ? void 0 : editorOptions.editorProps), { attributes: __assign(__assign({}, (_a = editorOptions === null || editorOptions === void 0 ? void 0 : editorOptions.editorProps) === null || _a === void 0 ? void 0 : _a.attributes), { id: id }) }) }), [disabled, editorOptions, readOnly, id]); var error = fieldState.error, invalid = fieldState.invalid, isTouched = fieldState.isTouched; useEffect(function () { if (!editor) return; var _a = editor.state.selection, from = _a.from, to = _a.to; editor.commands.setContent(field.value, false, { preserveWhitespace: true, }); editor.commands.setTextSelection({ from: from, to: to }); }, [editor, field.value]); useEffect(function () { if (!editor) { return; } var handleEditorUpdate = function () { if (editor.isEmpty) { field.onChange(''); field.onBlur(); return; } var html = editor.getHTML(); field.onChange(html); field.onBlur(); }; editor.on('update', handleEditorUpdate); editor.on('blur', field.onBlur); return function () { editor.off('update', handleEditorUpdate); editor.off('blur', field.onBlur); }; }, [editor, field]); return (React.createElement(Root, { className: clsx('ra-input', "ra-input-".concat(source), className, fullWidth ? 'fullWidth' : ''), sx: sx }, React.createElement(Labeled, { isRequired: isRequired, label: label, id: "".concat(id, "-label"), color: (fieldState === null || fieldState === void 0 ? void 0 : fieldState.invalid) ? 'error' : undefined, source: source, resource: resource, fullWidth: fullWidth }, React.createElement(RichTextInputContent, { editor: editor, error: error, helperText: helperText, id: id, isTouched: isTouched, isSubmitted: isSubmitted, invalid: invalid, toolbar: toolbar || React.createElement(RichTextInputToolbar, null) })))); }; export var DefaultEditorOptions = { extensions: [ StarterKit, Underline, Link, TextAlign.configure({ types: ['heading', 'paragraph'], }), Image.configure({ inline: true, }), TextStyle, // Required by Color Color, Highlight.configure({ multicolor: true }), ], }; var PREFIX = 'RaRichTextInput'; var classes = { editorContent: "".concat(PREFIX, "-editorContent"), }; var Root = styled('div', { name: PREFIX, overridesResolver: function (props, styles) { return styles.root; }, })(function (_a) { var _b; var theme = _a.theme; return (_b = { '&.fullWidth': { width: '100%', } }, _b["& .".concat(classes.editorContent)] = { width: '100%', '& .ProseMirror': { backgroundColor: (theme.vars || theme).palette.background.default, borderColor: (theme.vars || theme).palette.divider, borderRadius: theme.shape.borderRadius, borderStyle: 'solid', borderWidth: '1px', padding: theme.spacing(1), '&[contenteditable="false"], &[contenteditable="false"]:hover, &[contenteditable="false"]:focus': { backgroundColor: (theme.vars || theme).palette.action .disabledBackground, }, '&:hover': { backgroundColor: (theme.vars || theme).palette.action.hover, }, '&:focus': { backgroundColor: (theme.vars || theme).palette.background .default, }, '& p': { margin: '0 0 1em 0', '&:last-child': { marginBottom: 0, }, }, }, }, _b); }); /** * Extracted in a separate component so that we can remove fullWidth from the props injected by Labeled * and avoid warnings about unknown props on Root. */ var RichTextInputContent = function (_a) { var editor = _a.editor, error = _a.error, helperText = _a.helperText, id = _a.id, invalid = _a.invalid, toolbar = _a.toolbar; return (React.createElement(React.Fragment, null, React.createElement(TiptapEditorProvider, { value: editor }, toolbar, React.createElement(EditorContent, { "aria-labelledby": "".concat(id, "-label"), className: classes.editorContent, editor: editor })), React.createElement(FormHelperText, { className: invalid ? 'ra-rich-text-input-error' : '', error: invalid }, React.createElement(InputHelperText, { error: error === null || error === void 0 ? void 0 : error.message, helperText: helperText })))); }; //# sourceMappingURL=RichTextInput.js.map