From 44118b22f057c4e7c64e15e393c01729130c0388 Mon Sep 17 00:00:00 2001 From: 2betop <2betop.cn@gmail.com> Date: Wed, 15 Apr 2020 16:04:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=20tinymce=20=E4=B8=BA?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=20rich-text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/renderers/Form/Rich-Text.md | 14 +- fis-conf.js | 6 +- package.json | 4 +- scss/components/form/_rich-text.scss | 4 + src/components/Tinymce.tsx | 600 +++++++++++++++++++++++++++ src/renderers/Form/RichText.tsx | 139 ++++--- 6 files changed, 710 insertions(+), 57 deletions(-) create mode 100644 src/components/Tinymce.tsx diff --git a/docs/renderers/Form/Rich-Text.md b/docs/renderers/Form/Rich-Text.md index 8d02ac34a..c35220c77 100644 --- a/docs/renderers/Form/Rich-Text.md +++ b/docs/renderers/Form/Rich-Text.md @@ -3,10 +3,10 @@ 富文本编辑器 - `type` 请设置成 `rich-text` -- `saveAsUbb` 是否保存为 ubb 格式 +- `vendor` 默认为 `tinymce`,amis 平台中默认为 `froala`。 - `reciever` 默认的图片保存 API `/api/upload/image` -- `videoReciever` 默认的视频保存 API `/api/upload/video` -- `size` 框的大小,可以设置成 `md` 或者 `lg` 来增大输入框。 +- `videoReciever` 默认的视频保存 API `/api/upload/video`。 当为 tinymce 时无效 +- `size` 框的大小,可以设置成 `md` 或者 `lg` 来增大输入框。 当为 tinymce 时无效 - `buttons` 默认为 ```js @@ -34,7 +34,12 @@ ]; ``` -- `options` Object 类型,给富文本的配置信息。请参考 https://www.froala.com/wysiwyg-editor/docs/options + 当为 tinymce 时无效 + +- `options` Object 类型,给富文本的配置信息。请参考 https://www.froala.com/wysiwyg-editor/docs/options 或者 https://www.tiny.cloud/docs/configure/integration-and-setup/ + + tinymce 你可能需要指定样式表才能达到更好的展示效果,这个默认配置是关闭的,具体请参考 tinymce 文档。 + - **还有更多通用配置请参考** [FormItem](./FormItem.md) ```schema:height="350" scope="form-item" @@ -43,4 +48,5 @@ "name": "html", "label": "Rich Text" } + ``` diff --git a/fis-conf.js b/fis-conf.js index e3908c771..ab0368666 100644 --- a/fis-conf.js +++ b/fis-conf.js @@ -133,7 +133,8 @@ fis.match('*.html:jsx', { fis.hook('node_modules', { shimProcess: false, shimGlobal: false, - shimBuffer: false + shimBuffer: false, + shutup: true }); fis.hook('commonjs', { extList: ['.js', '.jsx', '.tsx', '.ts'] @@ -344,6 +345,8 @@ if (fis.project.currentMedia() === 'publish') { 'jquery/**' ], + 'tinymce.js': ['src/components/Tinymce.tsx', 'tinymce/**'], + 'charts.js': ['zrender/**', 'echarts/**'], 'editor.js': [ @@ -566,6 +569,7 @@ if (fis.project.currentMedia() === 'publish') { 'froala-editor/**', 'jquery/**' ], + 'pkg/tinymce.js': ['src/components/Tinymce.tsx', 'tinymce/**'], 'pkg/charts.js': ['zrender/**', 'echarts/**'], 'pkg/api-mock.js': ['mock/*.ts'], 'pkg/app.js': [ diff --git a/package.json b/package.json index 75f6313b5..bdecb7d22 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "redux": "^3.7.2", "setimmediate": "1.0.5", "sortablejs": "1.10.0", + "tinymce": "5.2.1", "tslib": "^1.10.0", "uncontrollable": "4.1.0", "video-react": "0.9.4" @@ -102,6 +103,7 @@ "@types/react-test-renderer": "^16.8.1", "@types/react-transition-group": "^2.0.6", "@types/sortablejs": "^1.3.32", + "@types/tinymce": "^4.5.24", "@types/whatwg-fetch": "0.0.33", "animate.css": "3.5.2", "axios": "0.18.1", @@ -118,8 +120,8 @@ "fis3-hook-node_modules": "^2.3.1", "fis3-parser-typescript": "^1.3.0", "fis3-postpackager-loader": "^2.1.11", - "fis3-preprocessor-js-require-css": "^0.1.3", "fis3-prepackager-stand-alone-pack": "^1.0.0", + "fis3-preprocessor-js-require-css": "^0.1.3", "font-awesome": "4.7.0", "fs-walk": "0.0.2", "highlight.js": "^9.12.0", diff --git a/scss/components/form/_rich-text.scss b/scss/components/form/_rich-text.scss index b97a605d1..05e65f8f4 100644 --- a/scss/components/form/_rich-text.scss +++ b/scss/components/form/_rich-text.scss @@ -5,6 +5,10 @@ } .#{$ns}RichTextControl { + > .tox-tinymce { + border: 0; + } + min-height: px2rem(200px); height: auto; border: $Form-input-borderWidth solid $Form-input-borderColor; diff --git a/src/components/Tinymce.tsx b/src/components/Tinymce.tsx new file mode 100644 index 000000000..55f9edb66 --- /dev/null +++ b/src/components/Tinymce.tsx @@ -0,0 +1,600 @@ +import React from 'react'; +// Import TinyMCE +// @ts-ignore +import tinymce from 'tinymce/tinymce'; + +// A theme is also required +import 'tinymce/themes/silver'; +import 'tinymce/skins/ui/oxide/skin.css'; + +// Any plugins you want to use has to be imported +import 'tinymce/plugins/advlist'; +import 'tinymce/plugins/autolink'; +import 'tinymce/plugins/lists'; +import 'tinymce/plugins/link'; +import 'tinymce/plugins/image'; +import 'tinymce/plugins/charmap'; +import 'tinymce/plugins/print'; +import 'tinymce/plugins/preview'; +import 'tinymce/plugins/anchor'; +import 'tinymce/plugins/searchreplace'; +import 'tinymce/plugins/visualblocks'; +import 'tinymce/plugins/code'; +import 'tinymce/plugins/fullscreen'; +import 'tinymce/plugins/insertdatetime'; +import 'tinymce/plugins/media'; +import 'tinymce/plugins/table'; +import 'tinymce/plugins/paste'; +import 'tinymce/plugins/help'; +import 'tinymce/plugins/wordcount'; +import 'tinymce/plugins/hr'; +import 'tinymce/plugins/pagebreak'; +import 'tinymce/plugins/spellchecker'; +import 'tinymce/plugins/visualchars'; +import 'tinymce/plugins/template'; +import 'tinymce/plugins/nonbreaking'; +import 'tinymce/plugins/emoticons'; +import 'tinymce/plugins/emoticons/js/emojis'; + +interface TinymceEditorProps { + model: string; + onModelChange?: (value: string) => void; + onFocus?: () => void; + onBlur?: () => void; + disabled?: boolean; + config?: any; + outputFormat?: 'html' | 'text'; + reciever?: string; +} + +export default class TinymceEditor extends React.Component { + static defaultProps = { + outputFormat: 'html' + }; + config?: any; + editor?: any; + currentContent?: string; + + elementRef: React.RefObject = React.createRef(); + + componentDidMount() { + this.config = { + inline: false, + skin: false, + content_css: false, + height: 400, + language: 'zh_CN', + plugins: [ + 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker', + 'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking', + 'table emoticons template paste help' + ], + toolbar: + 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | ' + + 'bullist numlist outdent indent | link image | print preview media fullpage | ' + + 'forecolor backcolor emoticons | help', + menu: { + file: { + title: 'File', + items: 'newdocument restoredraft | preview | print ' + }, + edit: { + title: 'Edit', + items: 'undo redo | cut copy paste | selectall | searchreplace' + }, + view: { + title: 'View', + items: + 'code | visualaid visualchars visualblocks | spellchecker | preview fullscreen' + }, + insert: { + title: 'Insert', + items: + 'image link media template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor toc | insertdatetime' + }, + format: { + title: 'Format', + items: + 'bold italic underline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align | forecolor backcolor | removeformat' + }, + tools: { + title: 'Tools', + items: 'spellchecker spellcheckerlanguage | code wordcount' + }, + table: { + title: 'Table', + items: 'inserttable | cell row column | tableprops deletetable' + }, + help: {title: 'Help', items: 'help'} + }, + paste_data_images: true, + ...this.props.config, + target: this.elementRef.current, + readOnly: this.props.disabled, + setup: (editor: any) => { + this.editor = editor; + + editor.on('init', (e: Event) => { + this.initEditor(e, editor); + }); + } + }; + + tinymce.init(this.config); + } + + componentWillUnmount() { + tinymce.remove(this.editor); + } + + initEditor(e: any, editor: any) { + const {model, onModelChange, outputFormat, onFocus, onBlur} = this.props; + + const value = model || ''; + editor.setContent(value); + + if (onModelChange) { + editor.on('change keyup setcontent', (e: any) => { + const newContent = editor.getContent({format: outputFormat}); + + if (newContent !== this.currentContent) { + this.currentContent = newContent; + onModelChange(newContent); + } + }); + } + + onFocus && editor.on('focus', onFocus); + onBlur && editor.on('blur', onBlur); + } + + render() { + return