├── index.js
├── assets
└── utf8-php.zip
├── src
├── Test.js
├── index.js
├── OwnServer.js
├── QiniuServer.js
├── App.js
└── components
│ └── ReactUEditorComponent.js
├── .babelrc
├── .gitignore
├── LICENSE
├── .eslintrc.js
├── public
└── index.html
├── package.json
├── proxy.js
├── lib
└── react-ueditor-component.min.js
└── README.md
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/react-ueditor-component.min.js');
2 |
--------------------------------------------------------------------------------
/assets/utf8-php.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eschere/react-editor-component/HEAD/assets/utf8-php.zip
--------------------------------------------------------------------------------
/src/Test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default props => (
4 |
5 | {props.test}
6 |
7 | );
8 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": "> 0.25%, not dead"
7 | }
8 | ],
9 | [
10 | "@babel/preset-react"
11 | ]
12 | ]
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # pravite
26 | api
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 eschere
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true
5 | },
6 | extends: [
7 | "airbnb",
8 | "standard",
9 | ],
10 | parser: "babel-eslint",
11 | parserOptions: {
12 | parserOptions: {
13 | ecmaFeatures: {
14 | jsx: true
15 | },
16 | ecmaVersion: 2018,
17 | sourceType: "module"
18 | },
19 | },
20 | plugins: [
21 | "react",
22 | "jsx-a11y"
23 | ],
24 | rules: {
25 | "quotes": [
26 | "error",
27 | "single"
28 | ],
29 | "semi": [
30 | "error",
31 | "always"
32 | ],
33 | "no-unused-vars": [
34 | 1
35 | ],
36 | 'prefer-template': 0,
37 | 'prefer-destructuring': 0,
38 | "prefer-const": 0,
39 | "global-require": 0,
40 | "linebreak-style": 0,
41 | 'no-plusplus': ["error", { "allowForLoopAfterthoughts": true }],
42 | "no-return-assign": 0,
43 | "no-restricted-syntax": 0,
44 | "no-param-reassign": 0,
45 | "react/destructuring-assignment": 0,
46 | 'react/jsx-one-expression-per-line': 0,
47 | 'react/jsx-no-undef': 0, // 本项目自动引入React
48 | "react/self-closing-comp": 0,
49 | "react/prop-types": 0,
50 | "react/no-string-refs": 0,
51 | 'jsx-a11y/no-static-element-interactions': 0,
52 | 'jsx-a11y/click-events-have-key-events': 0,
53 | 'jsx-a11y/anchor-is-valid': 0,
54 | 'jsx-a11y/label-has-for': 0,
55 | 'import/no-unresolved': [2, { ignore: ['^@/', './'] }],
56 | 'import/no-extraneous-dependencies': 0,
57 | 'react/jsx-filename-extension': 0
58 | },
59 | globals: {
60 | React: true
61 | }
62 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
20 |
21 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ueditor-component",
3 | "version": "1.0.5",
4 | "description": "UEditor wrapped by React Component",
5 | "main": "./index.js",
6 | "scripts": {
7 | "dev": "react-scripts start",
8 | "build": "node build.js"
9 | },
10 | "keywords": [
11 | "ueditor",
12 | "rich-text",
13 | "富文本",
14 | "react"
15 | ],
16 | "author": "eschere",
17 | "license": "MIT",
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/Eschere/react-editor-component.git"
21 | },
22 | "homepage": "https://github.com/Eschere/react-editor-component#readme",
23 | "devDependencies": {
24 | "debounce": "^1.2.0",
25 | "react": "^16.8.6",
26 | "@babel/preset-env": "^7.4.4",
27 | "@babel/preset-react": "^7.0.0",
28 | "eslint": "^5.4.0",
29 | "eslint-config-airbnb": "^17.1.0",
30 | "eslint-config-standard": "^12.0.0",
31 | "eslint-plugin-babel": "^5.3.0",
32 | "eslint-plugin-import": "^2.14.0",
33 | "eslint-plugin-jsx-a11y": "^6.1.2",
34 | "eslint-plugin-node": "^8.0.0",
35 | "eslint-plugin-promise": "^4.0.1",
36 | "eslint-plugin-react": "^7.11.1",
37 | "eslint-plugin-standard": "^4.0.0",
38 | "ora": "^3.4.0",
39 | "react-dom": "^16.8.6",
40 | "react-scripts": "3.0.1",
41 | "rimraf": "^2.6.3",
42 | "uglifyjs-webpack-plugin": "^2.1.2"
43 | },
44 | "eslintConfig": {
45 | "extends": "react-app"
46 | },
47 | "browserslist": {
48 | "production": [
49 | ">0.2%",
50 | "not dead",
51 | "not op_mini all"
52 | ],
53 | "development": [
54 | "last 1 chrome version",
55 | "last 1 firefox version",
56 | "last 1 safari version"
57 | ]
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/proxy.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const url = require('url');
3 |
4 | /**
5 | * 如果你要准备ueditor和ReactUEditorComponent同时开发联调
6 | * 一下的配置可以解决两个项目在不同端口下运行时,iframe产生的跨域问题
7 | * 将ueditor运行在8080端口,/utf8-phplu路径
8 | * 将ReactUEditorComponent运行在3000端口
9 | * 浏览器访问8000端口
10 | */
11 | let server = http.createServer((req, res) => {
12 | const path = url.parse(req.url).path;
13 | console.log(path);
14 |
15 | if (path.startsWith('/utf8-php')) {
16 | let request = http.request({
17 | port: 8080,
18 | path,
19 | method: req.method,
20 | headers: req.headers
21 | }, (response) => {
22 | res.writeHead(response.statusCode, response.headers);
23 | response.pipe(res);
24 | });
25 | req.pipe(request);
26 | } else if (!path.includes('websocket')) {
27 | let request = http.request({
28 | port: 3000,
29 | path,
30 | method: req.method,
31 | headers: req.headers
32 | }, (response) => {
33 | res.writeHead(response.statusCode, response.headers);
34 | response.pipe(res);
35 | });
36 | req.pipe(request);
37 | } else {
38 | res.end();
39 | }
40 | }).listen(8000);
41 |
42 | // websocket转发
43 | server.on('upgrade', (req, client) => {
44 | const path = url.parse(req.url).path;
45 | let request = http.request({
46 | port: 3000,
47 | path,
48 | headers: req.headers
49 | });
50 |
51 | request.on('upgrade', (res, socket) => {
52 | client.write(formatProxyResponse(res));
53 | client.pipe(socket);
54 | socket.pipe(client);
55 | });
56 |
57 | request.end();
58 | });
59 |
60 | function formatProxyResponse (res) {
61 | const headers = res.headers;
62 | const keys = Object.getOwnPropertyNames(headers);
63 | let switchLine = '\r\n';
64 | let response = [`HTTP/${res.httpVersion} ${res.statusCode} ${res.statusMessage}${switchLine}`];
65 | keys.forEach((key) => {
66 | response.push(`${key}: ${headers[key]}${switchLine}`);
67 | });
68 | response.push(switchLine);
69 | return response.join('');
70 | }
71 |
--------------------------------------------------------------------------------
/src/OwnServer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactUEditorComponent from './components/ReactUEditorComponent';
3 |
4 | import {
5 | upload, headers
6 | } from './api/api';
7 |
8 | export default class extends Component {
9 | state = {
10 | value: '',
11 | serverExtra: {
12 | headers,
13 | extraData: {
14 | yourdata: '123'
15 | }
16 | }
17 | }
18 |
19 | onChange = (value) => {
20 | console.log('change', value);
21 |
22 | this.setState({
23 | value
24 | });
25 | }
26 |
27 | render () {
28 | return (
29 |
30 | 上传到自己的服务器
31 |
35 | {this.state.value}
36 |
37 |
80 |
81 |
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/QiniuServer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactUEditorComponent from './components/ReactUEditorComponent';
3 |
4 | import {
5 | uploadqiniu as upload, getToken, headers, qiniuDomain
6 | } from './api/api';
7 |
8 | export default class App extends Component {
9 | state = {
10 | value: ''
11 | }
12 |
13 | onChange = (value) => {
14 | console.log('change', value);
15 |
16 | this.setState({
17 | value
18 | });
19 | }
20 |
21 | /**
22 | * @tips
23 | * 由于react的组件更新机制,应该选用更保险的附加数据更新方法
24 | * 以下三种方法
25 | * 大多数情况下,方法一能按预想的执行顺序执行,这里只是提供了另外两种保险方法参考
26 | */
27 | // 1. 直接更新,有无法及时更新upload携带参数的风险
28 | /*
29 | beforeUpload = (file) => {
30 | this.setState({
31 | serverExtra: {
32 | headers,
33 | extraData: {
34 | tik: 123
35 | }
36 | }
37 | })
38 | return file
39 | }
40 | */
41 |
42 | // 2. setState回调中resolve一个promise
43 | // beforeUpload = file => new Promise((resolve, reject) => {
44 | // this.setState({
45 | // serverExtra: {
46 | // headers,
47 | // extraData: {
48 | // tik: 123
49 | // }
50 | // }
51 | // }, () => resolve(file));
52 | // })
53 |
54 | // 3. 在setExtraDataComplete中resolve一个promise
55 | // setExtraDataComplete会在重新设置上传携带参数时被调用
56 | // 需要将setExtraDataComplete传给ReactUEditorComponent才能生效
57 | beforeUpload = file => new Promise((resolve, reject) => {
58 | let key = 't' + Math.random().toString().slice(5, 16);
59 |
60 | // 请求服务器,获取七牛上传凭证
61 | fetch(`${getToken}?key=${key}`, {
62 | headers
63 | })
64 | .then(response => response.json())
65 | .then((data) => {
66 | // 设置七牛直传额外数据
67 | this.setState({
68 | serverExtra: {
69 | extraData: {
70 | token: data.token,
71 | key
72 | }
73 | },
74 | setExtraDataComplete: () => {
75 | resolve(file);
76 | }
77 | });
78 | });
79 | })
80 |
81 | render () {
82 | return (
83 |
84 | 上传到七牛
85 |
89 | {this.state.value}
90 |
91 |
136 |
137 |
138 | );
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import OwnServer from './OwnServer';
3 | import QiniuServer from './QiniuServer';
4 |
5 | import {
6 | upload, headers
7 | } from './api/api';
8 |
9 | let toolbars = [[
10 | 'fullscreen', /* */ 'source', '|', 'undo', 'redo', '|',
11 | 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
12 | 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
13 | 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
14 | 'directionalityltr', 'directionalityrtl', 'indent', '|',
15 | 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
16 | 'link', 'unlink', 'anchor', '|', 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
17 | 'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', /* 上传视频 , */ /* 'music', 'attachment', */ /* 'map', 'gmap', */ 'insertframe', 'insertcode', /* 'webapp', */ 'pagebreak', /* 'template', */ /* 'background', */ '|',
18 | 'horizontal', 'date', 'time', 'spechars', /* 'snapscreen', 'wordimage', */'|',
19 | 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', /* 'charts', */ '|',
20 | 'print', 'preview', 'searchreplace', 'drafts', 'help'
21 | ]];
22 |
23 | export default class extends React.Component {
24 | componentDidMount () {
25 | window.UE.getEditor('static_editor', {
26 | autoHeightEnabled: false,
27 | toolbars,
28 | // 图片转存关闭
29 | catchRemoteImageEnable: false,
30 | serverUrl: upload,
31 | serverExtra: {
32 | headers
33 | },
34 | serverOptions: {
35 | /* 上传图片配置项 */
36 | imageActionName: 'uploadimage', /* 执行上传图片的action名称 */
37 | imageFieldName: 'file', /* 提交的图片表单名称 */
38 | imageMaxSize: 2048000, /* 上传大小限制,单位B */
39 | imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */
40 | // imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */
41 | imageCompressEnable: true, /* 是否压缩图片,默认是true */
42 | imageCompressBorder: 1600, /* 图片压缩最长边限制 */
43 | imageInsertAlign: 'none', /* 插入的图片浮动方式 */
44 | imageUrlPrefix: '', /* 图片访问路径前缀 */
45 | imageResponseKey: 'fileURL', // ! 图片上传接口response中包含图片路径的键名
46 |
47 | /* 涂鸦图片上传配置项 */
48 | scrawlActionName: 'uploadscrawl', /* 执行上传涂鸦的action名称 */
49 | scrawlFieldName: 'file', /* 提交的图片表单名称 */
50 | scrawlPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */
51 | scrawlMaxSize: 2048000, /* 上传大小限制,单位B */
52 | scrawlUrlPrefix: '', /* 图片访问路径前缀 */
53 | scrawlInsertAlign: 'none',
54 | scrawlResponseKey: 'fileURL', /* 涂鸦图片上传接口response中包含图片路径的键名 */
55 |
56 | /* 上传视频配置 */
57 | videoActionName: 'uploadvideo', /* 执行上传视频的action名称 */
58 | videoFieldName: 'file', /* 提交的视频表单名称 */
59 | videoPathFormat: '/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */
60 | videoUrlPrefix: '', /* 视频访问路径前缀 */
61 | videoMaxSize: 102400000, /* 上传大小限制,单位B,默认100MB */
62 | videoResponseKey: 'fileURL', /* 涂鸦图片上传接口response中包含图片路径的键名 */
63 | videoAllowFiles: [
64 | '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg',
65 | '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid'
66 | ],
67 | /* 抓取远程图片配置 */
68 | catcherLocalDomain: ['127.0.0.1', 'localhost', 'img.baidu.com'],
69 | catcherActionName: 'catchimage', /* 执行抓取远程图片的action名称 */
70 | catcherFieldName: 'file', /* 提交的图片列表表单名称 */
71 | catcherPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */
72 | catcherUrlPrefix: '', /* 图片访问路径前缀 */
73 | catcherMaxSize: 2048000, /* 上传大小限制,单位B */
74 | catcherResponseKey: 'fileURL',
75 | catcherAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'] /* 抓取图片格式显示 */
76 | }
77 | });
78 | }
79 |
80 | render () {
81 | return (
82 |
83 | {/* 三种不同的的ueditor对比 */}
84 | {/* 直接上传到服务器 */}
85 |
86 |
87 |
88 |
89 | {/* 获取上传凭证后上传到服务器 */}
90 |
91 |
92 |
93 |
94 | {/* 未被ReactUEditorComponent包裹的ueditor */}
95 |
96 |
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/lib/react-ueditor-component.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactUEditorComponent=t(require("react")):e.ReactUEditorComponent=t(e.react)}(window,function(r){return(i={},n.m=o=[function(e,t){e.exports=r},function(e,t){function r(t,r,o){var n,i,a,l,u;function s(){var e=Date.now()-l;e ({
85 | editorReady: new Promise((resolve, reject) => {
86 | let ueditor = window.UE.getEditor(this.editorId, {
87 | ...this.ueditorOptions,
88 | ...this.props.ueditorOptions
89 | });
90 |
91 | ueditor.ready(() => {
92 | resolve(ueditor);
93 |
94 | this.observerChangeListener(ueditor);
95 |
96 | ueditor.setContent(this.props.value || '');
97 | });
98 | })
99 | }));
100 | }
101 |
102 | static getDerivedStateFromProps (nextProps, prevState) {
103 | let editorReady = prevState.editorReady;
104 | let value = nextProps.value;
105 |
106 | if (Object.prototype.hasOwnProperty.call(nextProps, 'value')) {
107 | editorReady && editorReady.then((ueditor) => {
108 | (value === prevState.content || value === ueditor.getContent()) || ueditor.setContent(value || '');
109 | });
110 | }
111 |
112 | // 只能更新severExtra
113 | if (Object.prototype.hasOwnProperty.call(nextProps.ueditorOptions, 'serverExtra')) {
114 | let serverExtraStr = JSON.stringify(nextProps.ueditorOptions.serverExtra);
115 |
116 | if (serverExtraStr === prevState.serverExtraStr) {
117 | return {
118 | ...prevState,
119 | content: value
120 | };
121 | }
122 | editorReady && editorReady.then((ueditor) => {
123 | ueditor.setExtraData && ueditor.setExtraData(nextProps.ueditorOptions.serverExtra);
124 | // 增加一层保险,react的组件更新机制有可能使ueditor参数更新在beforeUpload之后
125 | nextProps.setExtraDataComplete && nextProps.setExtraDataComplete();
126 | });
127 | return {
128 | ...prevState,
129 | serverExtraStr,
130 | content: value
131 | };
132 | }
133 |
134 | return {
135 | ...prevState,
136 | content: value
137 | };
138 | }
139 |
140 | componentWillUnmount () {
141 | this.state.editorReady.then((ueditor) => {
142 | ueditor.destroy();
143 | });
144 |
145 | this.observer.disconnect();
146 | }
147 |
148 | observerChangeListener (ueditor) {
149 | const changeHandle = () => {
150 | let onChange = this.props.onChange;
151 |
152 | if (ueditor.document.getElementById('baidu_pastebin')) {
153 | return;
154 | }
155 |
156 | onChange && onChange(ueditor.getContent());
157 | };
158 |
159 | // this.observer = new MutationObserver(changeHandle);
160 | this.observer = new MutationObserver(debounce(changeHandle, 50));
161 | this.observer.observe(ueditor.body, this.observerOptions);
162 | }
163 |
164 | render () {
165 | return (
166 | this.editorDOM = el}
169 | >
170 |
171 | );
172 | }
173 | }
174 |
175 | export default UEditor;
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## react-ueditor-component
2 |
3 | ueditor的react封装版,修改了ueditor中的获取服务器端配置实现,更符合前后端分离的思想
4 |
5 | 使用assets中的utf8-php.zip才能正常使用上传文件功能,所有ueditor源码改动都用`MARK:`做了标记,解压该文件可以查看具体改动
6 |
7 | ueditor基于官方1.4.3.3分支修改,功能还在不断完善中
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ### Features
19 | 1. 接收`value`和`onChange`,使用起来就像`input`一样简单,与`antd`的表单双向绑定配合使用更简单
20 | 2. 上传文件增加`beforeUpload`钩子,可在上传前修改需要上传的文件、数据和请求头,对接第三方OSS易如反掌
21 |
22 | ### 安装
23 | ```bash
24 | npm install react-ueditor-component --save-dev
25 | ```
26 | 或者
27 | ```bash
28 | yarn add react-ueditor-component --save
29 | ```
30 |
31 | ### 使用
32 | 下载修改后打包的[ueditor.zip](https://github.com/Eschere/react-editor-component/raw/master/assets/utf8-php.zip),或者找到`node_modules/react-ueditor-component/assets/utf8-php.zip`,解压文件,放在网站的根目录,react项目一般放在`public`文件夹下,
33 | `index.html`中`script`标签引入`ueditor`代码
34 | ```xml
35 |
36 |
37 | ```
38 | 引入`ReactUEditorComponent`组件
39 | ```js
40 | import ReactUEditorComponent from 'react-ueditor-component';
41 |
42 | export default class App extends React.Component {
43 | state = {
44 | value: '',
45 | serverExtra: {
46 | // 上传文件额外请求头
47 | headers: {
48 | Auth: 'token'
49 | },
50 | // 上传文件额外的数据
51 | extraData: {
52 | desc: 'more data'
53 | }
54 | }
55 | }
56 |
57 | onChange = (value) => this.setState(value);
58 |
59 | render () {
60 | return (
61 |
82 | )
83 | }
84 | }
85 | ```
86 | ### API
87 |
88 | | 参数 | 说明 | 类型 | 默认值 |
89 | |-|-|-|-|
90 | | value | 设置编辑器的内容 | `string` | - |
91 | | onChange | 编辑器内容变化回调 | `Function(value)` | - |
92 | | setExtraDataComplete | 设置上传文件额外数据完成事件 | `Function()` | - |
93 | | ueditorOptions | 编辑器初始化的配置,在[官方文档](https://fex.baidu.com/ueditor/#start-config)支持的参数上增加了一些内容,**除`serverExtra`外不能动态变动** | `object` | 见下文 |
94 |
95 | ### ueditorOptions
96 | 默认值:
97 | ```js
98 | {
99 | autoHeightEnabled: false,
100 | toolbars: [[
101 | 'fullscreen', /* */ 'source', '|', 'undo', 'redo', '|',
102 | 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',
103 | 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',
104 | 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',
105 | 'directionalityltr', 'directionalityrtl', 'indent', '|',
106 | 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',
107 | 'link', 'unlink', 'anchor', '|', 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',
108 | 'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', /* 上传视频 , */ /* 'music', 'attachment', */ /* 'map', 'gmap', */ 'insertframe', 'insertcode', /* 'webapp', */ 'pagebreak', /* 'template', */ /* 'background', */ '|',
109 | 'horizontal', 'date', 'time', 'spechars', /* 'snapscreen', 'wordimage', */'|',
110 | 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', /* 'charts', */ '|',
111 | 'print', 'preview', 'searchreplace', 'drafts', 'help'
112 | ]],
113 | // 图片转存关闭
114 | catchRemoteImageEnable: false, initialFrameWidth: '100%',
115 | serverOptions: {
116 | /* 上传图片配置项 */
117 | imageActionName: 'uploadimage', /* 执行上传图片的action名称 */
118 | imageFieldName: 'file', /* 提交的图片表单名称 */
119 | imageMaxSize: 2048000, /* 上传大小限制,单位B */
120 | imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */
121 | imageCompressEnable: true, /* 是否压缩图片,默认是true */
122 | imageCompressBorder: 1600, /* 图片压缩最长边限制 */
123 | imageInsertAlign: 'none', /* 插入的图片浮动方式 */
124 | imageUrlPrefix: '', /* 图片访问路径前缀 */
125 | imageResponseKey: 'url', // ! 图片上传接口response中包含图片路径的键名
126 |
127 | /* 涂鸦图片上传配置项 */
128 | scrawlActionName: 'uploadscrawl', /* 执行上传涂鸦的action名称 */
129 | scrawlFieldName: 'file', /* 提交的图片表单名称 */
130 | scrawlPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */
131 | scrawlMaxSize: 2048000, /* 上传大小限制,单位B */
132 | scrawlUrlPrefix: '', /* 图片访问路径前缀 */
133 | scrawlInsertAlign: 'none',
134 | scrawlResponseKey: 'url', /* 涂鸦图片上传接口response中包含图片路径的键名 */
135 |
136 | /* 上传视频配置 */
137 | videoActionName: 'uploadvideo', /* 执行上传视频的action名称 */
138 | videoFieldName: 'file', /* 提交的视频表单名称 */
139 | videoPathFormat: '/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */
140 | videoUrlPrefix: '', /* 视频访问路径前缀 */
141 | videoMaxSize: 102400000, /* 上传大小限制,单位B,默认100MB */,
142 | videoResponseKey: 'url',
143 | videoAllowFiles: [
144 | '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg',
145 | '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid'
146 | ]
147 | }
148 | }
149 |
150 | ```
151 | `toolbars`中被注释的内容都是当前版本不能完美支持的功能,将在后续的版本中完善
152 |
153 | #### 上传配置
154 | `ueditorOptions.serverOptions`
155 | 将后端配置迁移到前端,支持[官方文档](https://fex.baidu.com/ueditor/#server-deploy)的参数,在这基础上增加了:
156 |
157 | `imageResponseKey`: 上传图片成功后,后台返回的json数据中包含图片地址信息的字段名
158 |
159 | 比如配置为
160 | ```js
161 | {
162 | imageUrlPrefix: 'http://demo.com/', /* 图片访问路径前缀 */
163 | imageResponseKey: 'url', //
164 | }
165 | ```
166 | 上传成功后后台返回的数据为
167 | ```js
168 | {
169 | url: 'demo.jpg'
170 | }
171 | ```
172 | 则生成的图片地址为`http://demo.com/demo.jpg`(这里指编辑器的内容中插入的图片地址字符串,而不是服务器存储的地址,服务器把上传文件存在哪里完全有服务端决定)
173 |
174 |
175 | `scrawlResponseKey`: 涂鸦上传成功后,后台返回的json数据中包含图片地址信息的字段名
176 |
177 |
178 | `videoResponseKey`: 视频上传成功后,后台返回的json数据中包含视频地址信息的字段名
179 |
180 | #### 上传接口
181 | `ueditorOptions.serverUrl`
182 | type: `string`必填
183 |
184 | #### 上传接口额外数据
185 | `ueditorOptions.serverExtra`
186 | 上传接口的额外数据,可动态变动
187 | ```jsx
188 |
189 |
202 | ```
203 | 如上例子在调用上传接口时header会增加`auth: token`,
204 | body中会增加`author: author`
205 |
206 | #### 上传前钩子
207 | `ueditorOptions.beforeUpload`
208 | `File => File | Promise`
209 |
210 | 接收预上传的文件,需要返回File或者Promise,
211 | 如果返回Promise,需要resolve一个File
212 |
213 | #### 设置额外数据完成钩子
214 | `setExtraDataComplete`
215 | 应用场景:上传钩子中设置上传额外数据,防止额外数据设置完成前就已经开始上传,可以使用安全的`setExtraDataComplete`钩子
216 |
217 | 应用场景举例:七牛云上传前需要调用后台接口获取上传凭证
218 | ```jsx
219 | export default class App extends React.Component {
220 | state = {
221 | value: '',
222 | serverExtra: {
223 | // 上传文件额外的数据
224 | extraData: {}
225 | }
226 | }
227 |
228 | beforeUpload = file => new Promise((resolve, reject) => {
229 | let key = 't' + Math.random().toString().slice(5, 16);
230 |
231 | // 请求服务器,获取七牛上传凭证
232 | fetch('getuploadtoken.com', {
233 | headers
234 | })
235 | .then(response => response.json())
236 | .then((data) => {
237 | // 设置七牛直传额外数据
238 | this.setState({
239 | serverExtra: {
240 | extraData: {
241 | token: data.token,
242 | key
243 | }
244 | },
245 | setExtraDataComplete: () => {
246 | resolve(file);
247 | }
248 | });
249 | });
250 | })
251 |
252 | onChange = (value) => this.setState(value);
253 |
254 | render () {
255 | return (
256 |
268 | )
269 | }
270 | }
271 | ```
272 |
273 | ### Contribution
274 | #### 如何运行项目
275 | 请查看[wiki](https://github.com/Eschere/react-editor-component/wiki/%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E9%A1%B9%E7%9B%AE)了解如何运行项目
--------------------------------------------------------------------------------