npm package: @itellyou/itellyou-editor
Description: 适用于 React 的富文本协同编辑器
Latest version: 2.2.18

# 适用于 React 的富文本编辑器
（[ITELLYOU 官网，发布文章功能中可体验](https://www.itellyou.com)，网站已开通了普通用户协同编辑权限，注册两个账户，在两个浏览器或不同的电脑登录，可以同时编辑网站上已发布的 标签或问题 ）
在 itellyou-engine 基础上扩展编辑能力，提供更多丰富的插件
### 引擎（[itellyou-engine](https://github.com/itellyou-com/itellyou-engine)）
编辑器核心库，纯js编写，不依赖 React，提供编辑能力，插件接口

### 扩展功能
+ codeblock (代码块，需要额外引用 codemirror 对语言支持)
+ table (表格)
+ emoji (emoji 表情)
+ file (文件上传、预览 需要配合后台api处理上传)
+ label (文本状态)
+ lockedtext (加密文本 ，需要配合后台api加密)
+ mindmap (脑图)
+ diagram (文本绘图，PlantUML、Mermaid、Flowchart、Graphviz 需要后台api调用第三方库生成svg图片)
+ math （数学公式，同diagram 一样需要后台api调用第三方库生成svg图片）
+ mention (提及 @)
+ search (输入关键字可以查询编辑器中的符合条件的文字，并高亮显示，可以替换相应的文字)
+ translate (可以对编辑器中的文档翻译，并且可以替换，需要后台 提供翻译api，一般使用阿里云)
+ video (视频，需要后台上传api)
+ youku (在线视频)
+ image (图片，需要后台上传api)
+ toc (大纲)
### 四种满足使用环境的编辑器模式
+ FullEditor (正常完整模式，适合专业文档撰写)
+ MiniEditor (迷你模式，评论、问答等简单撰写)
+ LineEditor (行模式 适合文字评论、表情评论、图片评论)
+ MobileEditor (手机模式，适合手机h5简单撰写)
```
import { FullEditor , MiniEditor , LineEditor ,MobileEditor } from '@itellyou/itellyou-editor'
```
### 依赖
```
// 引擎
https://github.com/itellyou-com/itellyou-engine.git
// ajax 文件上传工具
https://github.com/itellyou-com/itellyou-ajax.git
// request 数据请求工具
https://github.com/itellyou-com/itellyou-request.git
```
### 本地编译（@itellyou/itellyou-ajax、@itellyou/itellyou-engine、@itellyou/itellyou-request、@itellyou/itellyou-editor 这些都是私有包，是访问不到的，需要将这4个项目在本地编译或者发布到自己的npm上）
```
clone https://github.com/itellyou-com/itellyou-editor.git
```
现在整个项目是基于 umijs.org 搭建的，主要有一些测试页面，编辑器是在 src/library 目录中
编译之前需要确认以下命令是否有全局安装,或在package.json/scripts/compile(编译脚本) 中 修改命令
主要就是先删除项目根目录下的library目录，然后将src/library中的es6代码转换成es5代码，并把转换后的代码和css复制到项目根目录下的library文件夹中

```
// rimraf 删除 library 目录
npm install rimraf -g
// cpr 复制资源文件
npm install cpr -g
```

在 itellyou-editor
```
// 安装模块
npm install

// 编译后链接到全局（相当于将包发布到本地）
npm link 
// 或只编译
npm run compile
```
编译后的文件在 itellyou-editor/library 文件夹中

### npm link 使用：
首先得执行 npm link 编译后链接到全局 ，然后在您的应用程序中链接它
```
npm link @itellyou/itellyou-editor
```
### Class Component
```
import React from 'react'
import { FullEditor } from '@itellyou/itellyou-editor'

class Editor extends React.Component {

    state = {
        content:"<p>Hello Editor</p>"
    }

    onLoad = editor => {
        console.log("编辑器加载好了，",editor)
    }

    onChange = content => {
        this.setState({
            content
        })
    }

    onSave = () => {
        console.log("我请求保存")
    }

    render(){
        const { content } = this.state
        /**
         * 
         * {
                lang,// 语言 en , zh-cn
                defaultValue, // 默认编辑器内容
                onChange, // 编辑器内容改变调用
                onLoad, //编辑器加载成功时调用
                header, // 编辑器扩展header信息，可以是 React 组件
                onSave, // 保存插件执行保存的时候调用
                onBeforeRenderImage,// 在读取图片前调用，可以在函数里面修改 图片地址
                // 表情包
                emoji: {
                    action: 'https://cdn-object.itellyou.com/emoji/',
                },
                // 加密文本插件配置,selection/locakedtext
                lockedtext: {
                    action: '/api/crypto',
                    // 参数
                    {
                        action:encrypt 加密 text 文本 ，pwd 密码，返回加密后的密文
                        action:decrypt 解密 text 加密密文 ， pwd 密码，返回解密后的文本
                    }
                },
                // 图片插件配置
                image: {
                    action: '/api/upload/image',//上传地址，粘贴图片的时候传的 图片url地址，
                    //返回参数
                    {
                        result:true,
                        data:{
                            url,// 图片地址,必须
                            preview, // 预览地址
                            download,// 下载地址
                            size , // 图片大小
                            width , // 真实宽度
                            height // 真实高度
                        }
                    }
                    display: 'block',// 默认显示模式 inline 同文本一行，block ，单独占一行
                    align: 'center',// 默认位置：left 居左，center 居中，right 居右
                },
                // 文件插件配置
                file: {
                    action: '/api/upload/file',
                    //返回参数
                    {
                        result:true,
                        data:{
                            url,// 文件地址,必须
                            preview, // 预览地址，文档预览可以配合阿里云 “智能媒体管理” - “文档转换” 使用
                            download,// 下载地址
                            size , // 文件大小
                        }
                    }
                },
                // 阿里云视频上传请求，需要阿里云sdk支持 https://cdn-object.itellyou.com/ali-sdk/aliyun-oss-sdk-5.3.1.min.js ， https://cdn-object.itellyou.com/ali-sdk/aliyun-upload-sdk-1.5.0.min.js，具体可查看阿里云视频点播服务
                video: {
                    action: {
                        create: '/api/upload/video',// 请求创建上传视频请求，
                        //请求参数
                        {
                            filename,//文件名
                            filesize,//文件大小
                        }
                        //后端调用 CreateUploadVideoRequest ，并返回 
                        {
                            result:true,
                            data:...
                        }

                        save: '/api/upload/video/save',// 保存上传后的信息，可根据需要保存
                        
                        query: '/api/upload/video/query',// 查询播放链接
                        //请求参数
                        {
                            video_id//视频编号
                        }
                        //后端使用 GetPlayInfoResponse 获取播放信息
                        GetPlayInfoResponse response = vodService.getPlayInfo(videoId);
                        if(response == null) return new ResultModel(0,"获取视频播放信息失败");
                        Map<String,Object> data = new HashMap<>();
                        GetPlayInfoResponse.VideoBase videoBase = response.getVideoBase();
                        data.put("video_id",videoBase.getVideoId());
                        data.put("cover_url",videoBase.getCoverURL());
                        data.put("title",videoBase.getTitle());
                        List<Map<String,Object>> playList = new ArrayList<>();
                        for (GetPlayInfoResponse.PlayInfo playInfo : response.getPlayInfoList()){
                            Map<String,Object> playData = new HashMap<>();
                            playData.put("url",playInfo.getPlayURL());
                            playData.put("format",playInfo.getFormat());
                            playData.put("width",playInfo.getWidth());
                            playData.put("height",playInfo.getHeight());
                            playData.put("size",playInfo.getSize());
                            playList.add(playData);
                        }
                        data.put("play_list",playList);
                        并返回数据
                        return {
                            result:true,
                            data:...
                        }
                    },
                },
                // 阿里云翻译请求
                translate: {
                    action: '/api/translate',
                },
                // @ 插件
                mention: {
                    action: '/api/user/search',
                    paramName: 'q',"q" // 查询参数
                    default: [],//默认可以 @ 的会员
                },
                // markdown 支持命令
                markdown: {
                    action: '/api/document/convert',// 粘贴时，识别到markdown代码，转行为编辑器数据格式请求。不提供则不检测
                    items: [
                        'codeblcok',
                        'code',
                        'mark',
                        'bold',
                        'strikethrough',
                        'italic',
                        'sup',
                        'sub',
                        'orderedlist',
                        'unorderedlist',
                        'tasklist',
                        'checkedtasklist',
                        'h1',
                        'h2',
                        'h3',
                        'h4',
                        'h5',
                        'h6',
                        'quote',
                        'link',
                    ],
                },
                // 是否启用协同编辑，需要后端做好api配合
                ot: false,
            }
         */
        return (
            <Editor 
            defaultValue={content} 
            onLoad={this.onLoad}
            onChange={this.onChange}
            onSave={this.onSave}
            />
        )
    }
}
export default Editor
```

### Hook
```
export default EditorTest = () => {
    const [ content , setContent ] = useState("<p>Hello</p>")

    const onChange = value => setContent(value)

    const onLoad = editor = => {
        console.log("编辑器加载了，实例：",editor)
    }

    return <Editor defaultValue={content} onLoad={onLoad} onChange={onChange} />
}
```

### 实例方法

```
// 获取编辑器内容
getContent()

// 获取不包含光标信息的内容
getPureContent()

// 获取不包含光标信息的html
getPureHtml()

```
### 协同编辑
使用 ShareDB 基于 JSON 文档的操作转换 
https://github.com/share/sharedb

需要配合 itellyou-editor-server 是一个nodejs项目，作为 协同 服务端

demo:

```
yarn start
```
打开两个浏览器，访问 /Collab ，自定义一个 账户 ，然后选择一个文档编辑。数据交互是基于 mock 作为演示

### 扩展插件
section 块
```
import SectionBase from '@itellyou/itellyou-editor/library/section/base'
import { Engine } from '@itellyou/itellyou-editor';

class PaidSection extends SectionBase {
    constructor(engine, contentView){
        super()
        this.section = Engine.section
        this.engine = engine
        this.contentView = contentView
    }

    render(container, value) {
        value = value || {};
        // 初始化 section 节点
        container.append(Engine.$("<div style='color:#fff;background:blue'>This is Section Plugin</div>"));
    }
}
// 块的类型 block、inline
PaidSection.type = 'block';
export default PaidSection
```
插件

```
const PLUGIN_NAME = "paid"
export default {
    initialize: function () {
        // 创建命令
        this.command.add(PLUGIN_NAME, {
            execute: () => {
                // 插入Section块，名称为 paid
                this.change.insertSection(PLUGIN_NAME)
            }
        }) 
        // 设置快捷键
        const options = this.options[PLUGIN_NAME] || {
            hotkey:'mod+shift+m'
        }
        
        if(!!options.hotkey){
            this.hotkey.set(options.hotkey, PLUGIN_NAME)
        }
    }
}
```
添加到引擎
```
import { FullEditor , Engine } from '@itellyou/itellyou-editor';
Engine.section.add('paid', PaidSection)
Engine.plugin.add('paid', PaidPlugin)
```
最后使用快捷键 mod+shift+m 出来插件效果，或者添加toolbar按钮执行插件