常见功能 功能演示 使用本地方式引入(跳过 API Key) 在 社区版 下载对应版本的压缩包
解压后将 tinymce
文件夹放到项目的 public
文件夹下
在 src/components/CustomEditor/index.tsx
中找到 tinymceScriptSrc
, 然后引入本地的 tinymce
文件
1 2 3 4 5 6 7 <Editor ..., init={{ ..., tinymceScriptSrc={'/tinymce/tinymce.min.js' } }} />
注意: 一定要放在 public
文件下, 否则会被打包, 导致找不到文件
下载语言包 在 语言包下载地址 找到需要的语言包并下载
解压后将 langs
文件夹放到项目的 public
文件夹下
在 src/components/CustomEditor/index.tsx
中找到 language
, 填上对应的语言包名, 如 zh-Hans
1 2 3 4 5 6 7 <Editor ..., init={{ ..., language : 'zh-Hans' , }} />
引入自定义字体 在 /public/tinymce
下新建 fonts
文件夹(也可以是其他文件夹, 但一定要在 tinymce
下)
下载字体文件, 如 custom_font.ttf
, 然后放到 /public/tinymce/fonts
文件夹下
新建 font.css
文件, 然后引入字体文件
1 2 3 4 @font-face { font-family : 'custom_font' ; src : url ('./custom_font.ttf' ); }
接着在 src/components/CustomEditor/index.tsx
中找到 content_css
, 引入 font.css
文件, 以及定义字体 自定义字体=custom_font;
1 2 3 4 5 6 7 8 9 10 <Editor ..., init={{ ..., font_css : '/tinymce/fonts/font.css' , font_family_formats : '微软雅黑=微软雅黑,宋体,sans-serif;' + '自定义字体=custom_font;' , }} />
图片上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const Index = ( ) => { const handleUpload = async (blobInfo : BlobInfo ): any => { return new Promise ((resolve, reject ) => { const formData = new FormData (); formData.append ('file' , blobInfo.blob ()); axios.post ('localhost:3000/api/upload' , formData).then ((data : any ) => { if (data.url ) resolve (data.url ); else reject ('上传失败' ); }); }); }; return ( <Editor ... , init ={{ ... , plugins: ['image '], // 图片插件 images_upload_handler: handleUpload , // 图片上传方法 convert_urls: false , // TinyMCE 默认会将图片的 url 转换为相对路径 , 这里禁用 (取得是 img 中 data-mce-src 的值 ) }} /> ) }
自定义保存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const Index = ( ) => { const handleSave = (editor : TinyMCEEditor ) => { alert (`保存成功 ${editor.getContent()} ` ); }; return ( <Editor ... , init ={{ ... , plugins: ['save '], // 保存插件 add_form_submit_trigger: true , // 添加表单提交触发器 ctrl +s save_onsavecallback: handleSave , // 保存回调 }} /> ) }
换行间距过大问题 1 2 3 4 5 6 7 <Editor ..., init={{ ..., forced_root_block : 'div' , }} />
复制粘贴功能 1 2 3 4 5 6 7 <Editor ..., init={{ ..., paste_webkit_styles : true , }} />
快捷工具栏 1 2 3 4 5 6 7 8 9 <Editor ..., init={{ ..., plugins : ["quickbars" ], quickbars_insert_toolbar : false , quickbars_selection_toolbar : "bold italic underline translation translationMenu" , }} />
自定义插件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 const Index : React .FC <Props > = (props ) => {const handleInit = (editor : TinyMCEEditor ) => { let toggleState = "" ; editor.ui .registry .addIcon ( "translationIcon" , `<svg t="1709617096310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4825" width="20" height="20"> <path d="M608 416h288c35.36 0 64 28.48 64 64v416c0 35.36-28.48 64-64 64H480c-35.36 0-64-28.48-64-64v-288H128c-35.36 0-64-28.48-64-64V128c0-35.36 28.48-64 64-64h416c35.36 0 64 28.48 64 64v288z m0 64v64c0 35.36-28.48 64-64 64h-64v256.032c0 17.664 14.304 31.968 31.968 31.968H864a31.968 31.968 0 0 0 31.968-31.968V512a31.968 31.968 0 0 0-31.968-31.968H608zM128 159.968V512c0 17.664 14.304 31.968 31.968 31.968H512a31.968 31.968 0 0 0 31.968-31.968V160A31.968 31.968 0 0 0 512.032 128H160A31.968 31.968 0 0 0 128 159.968z m64 244.288V243.36h112.736V176h46.752c6.4 0.928 9.632 1.824 9.632 2.752a10.56 10.56 0 0 1-1.376 4.128c-2.752 7.328-4.128 16.032-4.128 26.112v34.368h119.648v156.768h-50.88v-20.64h-68.768v118.272H306.112v-118.272H238.752v24.768H192z m46.72-122.368v60.48h67.392V281.92H238.752z m185.664 60.48V281.92h-68.768v60.48h68.768z m203.84 488H576L668.128 576h64.64l89.344 254.4h-54.976l-19.264-53.664h-100.384l-19.232 53.632z m33.024-96.256h72.864l-34.368-108.608h-1.376l-37.12 108.608zM896 320h-64a128 128 0 0 0-128-128V128a192 192 0 0 1 192 192zM128 704h64a128 128 0 0 0 128 128v64a192 192 0 0 1-192-192z" fill="#333333" p-id="4826"></path> </svg>` ); editor.ui .registry .addButton ("translation" , { text : "翻译" , tooltip : "翻译" , onAction : () => { editor.insertContent ("翻译" ); }, }); editor.ui .registry .addMenuButton ("translationMenu" , { icon : "translationIcon" , tooltip : "翻译" , fetch : (callback ) => { const items = [ { type : "togglemenuitem" , text : "zh" , onAction : () => { toggleState = "zh" ; editor.insertContent ("你好!" ); }, onSetup : (api ) => { api.setActive ("zh" == toggleState); return () => {}; }, }, { type : "togglemenuitem" , text : "en" , onAction : () => { toggleState = "en" ; editor.insertContent ("Hello!" ); }, onSetup : (api ) => { api.setActive ("en" == toggleState); return () => {}; }, }, ]; callback (items); }, }); }; return ( <Editor ... , init ={{ ... , plugins: ["translation ", "translationMenu "], // 自定义插件 quickbars_insert_toolbar: false , // 禁用快捷插入 quickbars_selection_toolbar: "bold italic underline translation translationMenu ", // 快捷选择工具栏 setup: handleInit , }} /> ); };
React 使用 Tinymce 富文本的两种方式(官方提供) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function MyComponent ({ initialValue } ) { const editorRef = useRef (null ); const [dirty, setDirty] = useState (false ); useEffect (() => setDirty (false ), [initialValue]); const save = ( ) => { if (editorRef.current ) { const content = editorRef.current .getContent (); setDirty (false ); editorRef.current .setDirty (false ); console .log (content); } }; return ( <> <Editor initialValue ={initialValue} onInit ={(evt, editor ) => (editorRef.current = editor)} onDirty={() => setDirty(true)} /> <button onClick ={save} disabled ={!dirty} > Save </button > {dirty && <p > You have unsaved content!</p > } </> ); }
函数组件
1 2 3 4 5 function MyComponent ({ initialValue } ) { const [value, setValue] = useState (initialValue ?? '' ); useEffect (() => setValue (initialValue ?? '' ), [initialValue]); return <Editor initialValue ={initialValue} value ={value} onEditorChange ={(newValue, editor ) => setValue(newValue)} /> ; }
类组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class MyComponent extends React.Component { constructor (props ) { super (props); this .state = { value : props.initialValue ?? '' }; this .handleEditorChange = this .handleEditorChange .bind (this ); } componentDidUpdate (prevProps ) { if (this .props .initialValue !== prevProps.initialValue ) { this .setState ({ value : this .props .initialValue ?? '' }); } } handleEditorChange (value, editor ) { this .setState ({ value }); } render ( ) { return <Editor initialValue ={this.props.initialValue} value ={this.state.value} onEditorChange ={this.handleEditorChange} /> ; } }
受控模式下大文本处理方案 问题: 当文本过大时, 会导致页面卡顿, 甚至卡死 思路: 将过长的文本放到 iframe
中, 以减轻页面的压力 使用 ref 的方式 功能演示