将 React 组件添加到 TinyMCE 编辑器内容中

问题描述 投票:0回答:2

我们正在构建一个编辑器,我们希望使用 TimyMCE 和 React。我们有一些场景,在事件中我们必须将基于模板的内容添加到编辑器。我计划将模板设计为 React 组件。

那么,有了TinyMCE和React,如何将react组件添加到TinyMCE编辑器中。

    export class AppEditor extends React.Component<iEditorProps, iEditorState> {

    innerEditor: React.RefObject<Editor>;

    constructor(props: iEditorProps) {
        super(props);
        this.state = {
            content: ''            
        };
        this.innerEditor = React.createRef();
    }


    handleChange = (content) => {
        this.setState({ content });
    }

    handleAddContent = (e) => {
        this.setState(prevState => {
            return { content: prevState.content + <TemplateComp>Added Content</TemplateComp> }
        });
    }

    render() {
        return (
            <div>
                <Editor ref={this.innerEditor} inline onEditorChange={this.handleChange} value={this.state.content} />
            </div>);
    }
}

在上面的“

handleAddContent
”代码中,我尝试将
<TemplateComp>
添加到编辑器中,但它被渲染为[object] [Object]。我知道字符串连接就是这样做的。正在使用的 TinyMCE 包 - https://github.com/tinymce/tinymce-react.

但是如何向编辑器添加组件呢?

javascript reactjs tinymce
2个回答
2
投票

将字符串与 JSX 表达式(实际上是虚拟节点的 JavaScript 对象树)连接起来

prevState.content + <TemplateComp>Added Content</TemplateComp>

结果如您所描述。您必须将虚拟 DOM 节点转换为 HTML,例如通过以下方式:

prevState.content + ReactDOMServer.renderToStaticMarkup(<TemplateComp>Added Content</TemplateComp>)

这会起作用,尽管这是如何将 React 组件静态渲染到 Tiny MCE 编辑器的一种方法。如果您确实想将 React 组件及其生命周期放置到富文本编辑器中,请考虑使用

Draft.js基于它的组件 而不是 Tiny MCE。


0
投票
经过长时间的斗争,我做到了。

首先,我们需要了解,为了使插件正常工作,我们需要能够注入项目上下文。就我而言,我使用的是

react-intl

Material-UI
Axios
react-query
,你可能还有其他的,所以我为此创建了一个组件:

import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import AxiosInterceptors from "AxiosInterceptors"; import { GlobalDataProvider } from "hooks/globalData"; import { queryClient } from "index"; import en from "intl/en"; import React from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { IntlProvider } from "react-intl"; import { QueryClientProvider } from "react-query"; import DateFnsUtils from "@date-io/date-fns"; type AppDependenciesProviderProps = { children: React.ReactNode; }; export const AppDependenciesProvider = ({ children, }: AppDependenciesProviderProps) => { const onIntlError = (err: any) => { console.log(err); }; return ( <MuiPickersUtilsProvider utils={DateFnsUtils}> <AxiosInterceptors /> <QueryClientProvider client={queryClient}> <IntlProvider locale="en" messages={en} onError={onIntlError}> <GlobalDataProvider> <DndProvider backend={HTML5Backend}>{children}</DndProvider> </GlobalDataProvider> </IntlProvider> </QueryClientProvider> </MuiPickersUtilsProvider> ); };
这个组件在我的

App.tsx

以及我的插件中被调用。

然后,当您调用编辑器时,假设您需要创建一个名为

medialibrary 的自定义组件(在我的例子中),它用于插入来自自定义公司 blob 的图像以及在编辑器中插入图像。我创建了一个 RichTextEditor

 组件,以便从外部更简单地调用它。

import { withStyles, } from "@material-ui/core"; import { Editor, IAllProps } from "@tinymce/tinymce-react"; import clsx from "clsx"; import RegisterPlugins from "components/TinyMCEPlugins/Plugins/RegisterPlugins"; import { useCallback, useRef, useState } from "react"; import { FieldInputProps, FieldMetaState } from "react-final-form"; import { useIntl } from "react-intl"; import "tinymce/icons/default"; import "tinymce/plugins/code"; import "tinymce/plugins/image"; import "tinymce/plugins/imagetools"; import "tinymce/plugins/link"; import "tinymce/plugins/lists"; import "tinymce/plugins/paste"; import "tinymce/plugins/table"; import "tinymce/plugins/template"; import "tinymce/plugins/textcolor"; import "tinymce/skins/content/default/content.min.css"; import "tinymce/skins/ui/oxide/content.min.css"; import "tinymce/skins/ui/oxide/skin.min.css"; import "tinymce/themes/silver"; import "tinymce/tinymce"; import { Editor as TinyMCEEditor } from "tinymce/tinymce"; type RichTextEditorProps = { setupEditor?: (e: TinyMCEEditor) => void; } & Partial<IAllProps>; const RichTextEditor = ({ setupEditor, init = {}, ...rest }: RichTextEditorProps) => { const [value, setValue] = useState(input.value); const intl = useIntl(); const setup = useCallback((editor: TinyMCEEditor) => { RegisterPlugins({ editor, intl, ...rest }); setupEditor && setupEditor(editor); }, []); return ( <FormControl fullWidth error={meta.error && meta.submitFailed} focused={focused} > <InputLabel shrink={forceLabelShrink || value !== "" ? true : undefined}> {label !== undefined ? label : ""} </InputLabel> <div> <Editor value={value} onEditorChange={onEditorChange} onSelectionChange={() => false} onBlur={onBlur} onFocus={onFocus} init={{ branding: false, skin: "outside", content_css: false, height: 200, menubar: false, plugins: [ "medialibrary", "paste", "lists", "image", "code", "imagetools", "link", "template", "table", ], statusbar: false, toolbar: "styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist", table_toolbar: false, setup, ...init, }} {...rest} /> </div> {meta.error && meta.submitFailed && ( <FormHelperText>{meta.error}</FormHelperText> )} </FormControl> ); }; export default withStyles(styles)(RichTextEditor);
现在,我们需要声明组件 

RegisterPlugins

,它允许我们声明所有 React 自定义插件:

import MediaLibraryPlugin from "components/TinyMCEPlugins/Plugins/MediaLibraryPlugin"; import { IntlShape } from "react-intl"; import { Editor } from "tinymce"; export type PluginRegistrationProps = { editor: Editor; intl?: IntlShape; /*in my case, I send the intl from the caller, but you can avoid this*/ }; const RegisterPlugins = ({ ...props }: PluginRegistrationProps) => { /*Declare all your plugins here */ MediaLibraryPlugin({ ...props }); /*this is my custom component*/ }; export default RegisterPlugins;
此时,我们需要关注我们的自定义插件,我建议将其分为两步:一个用于声明对话框包装器,另一个用于声明组件的功能:

import { Dialog, DialogContent } from "@material-ui/core"; import { AppDependenciesProvider } from "components/App/AppDependenciesProvider"; import { PluginRegistrationProps } from "components/TinyMCEPlugins/Plugins/RegisterPlugins"; import ReactDOM from "react-dom"; import { MediaLibraryPluginView } from "./MediaLibraryPluginView"; const MediaLibraryPlugin = ({ editor, intl, ...rest }: PluginRegistrationProps) => { editor.ui.registry.addButton("medialibrary"/*name of the plugin*/, { text: "Media Library", icon: "gallery", /*You can pick your custom icon*/ onAction: function () { const dialogContainer = document.createElement("div"); document.body.appendChild(dialogContainer); const closeDialog = () => { ReactDOM.unmountComponentAtNode(dialogContainer); dialogContainer.remove(); }; const dialog = ( <Dialog fullWidth maxWidth={"lg"} open onClose={closeDialog}> <DialogContent> <AppDependenciesProvider> /*Here we inject the app context as explained in the first step*/ <MediaLibraryPluginView onClose={closeDialog} editor={editor} /> </AppDependenciesProvider> </DialogContent> </Dialog> ); ReactDOM.render(dialog, dialogContainer); }, }); }; export default MediaLibraryPlugin;
最后,我们可以专注于我们的 React 代码:

import useBoolean from "hooks/useBoolean"; import MediaLibraryItemCreateForm from "pages/MediaLibraryPage/MediaLibraryItemCreateForm"; import { MediaLibraryItemsListView } from "pages/MediaLibraryPage/MediaLibraryItemsListView"; import { IMediaLibraryItem } from "services/KnowfullyAdminClient"; import { TinyMCEPluginBase } from "types/common-types"; import { FileFormat } from "utils/mediaUtils"; export const MediaLibraryPluginView = ({ editor, onClose, }: TinyMCEPluginBase) => { /*This component is absolutely custom. As you can see you can use hooks and whatever dependencies you need to make it work. In my case, this component inserts images coming from a custom source. Demo can be seen below*/ const [onCreate, setOnCreate] = useBoolean(false); const onClickCreateMediaLibraryItem = () => { setOnCreate.setTrue(); }; const onClickMediaLibraryItem = (mediaLibraryItem: IMediaLibraryItem) => { editor.insertContent( `<img src="${mediaLibraryItem.fileUrl}" width="${mediaLibraryItem.width}" height="${mediaLibraryItem.height}"/>` ); onClose(); }; return !onCreate ? ( <MediaLibraryItemsListView onClickCreateMediaLibraryItem={onClickCreateMediaLibraryItem} onClickMediaLibraryItem={onClickMediaLibraryItem} options={[FileFormat.IMAGE]} contentType={FileFormat.IMAGE} /> ) : ( <MediaLibraryItemCreateForm onRedirectBack={() => setOnCreate.setFalse()} /> ); };
结果如下:

© www.soinside.com 2019 - 2024. All rights reserved.