React中如何優雅的使用UEditor

蘇格團隊發表於2018-08-31

前言

本文將介紹筆者在React的專案中使用百度的富文字編輯器Ueditor的過程。注意本文不提供一條龍式的使用方法,只是將使用過程中的一些實現思路進行總結,供以參考。react專案中匯入ueditor,會存在各種不正交的問題,需要注意。

引入

首先在ueditor官網下載最新安裝包,然後在專案入口的html中匯入(匯入方式不一,可以採用import的方式,需要自行度娘。但是無論哪種引入方式,只要想自定義功能,不正交問題就難以避免QAQ)。不管三七二十一先跑起來再說。。

<!DOCTYPE HTML>
<html lang="en-US">

<head>
    <meta charset="UTF-8">
    <title>ueditor demo</title>
</head>

<body>
    <!-- 配置檔案 -->
    <script type="text/javascript" src="path/ueditor.config.js"></script>
    <!-- 編輯器原始碼檔案 -->
    <script type="text/javascript" src="path/ueditor.all.js"></script>
	······
</body>

</html>
複製程式碼

在React專案中使用ueditor要注意

  1. 匯入的路徑,筆者使用的是專案經webpack打包之後的相對路徑。
  2. 匯入順序,配置檔案要先於原始碼。
  3. 筆者這種引入方式存在快取問題,所以修改ueditor.all.js後需要及時清理快取,測試新的程式碼。

封裝

/**
 * 封裝UEditor
 */
import React from 'react';
import './index.less';

class UEditor extends React.Component {
    constructor(props) {
        super(props);
        this.editor = {};
        this.id = '';
    }
	······
    componentDidMount() {
        let UE = window.UE;
        let id = this.id;
        if (id) {
            try {
                /* 載入之前先執行刪除操作,否則如果存在頁面切換,
            再切迴帶編輯器頁面重新載入時不重新整理無法渲染出編輯器 */
                UE.delEditor(id);
            } catch (e) {}
            let ueditor = UE.getEditor(id, {
                toolbars: [
                    ['bold', 'italic', 'underline', 'kityformula', 'diyimg']
                ],
                initialContent: '',
                autoHeightEnabled: false,
                autoFloatEnabled: false,
                elementPathEnabled: false,
                wordCount: false,
                enableAutoSave: false,
                initialFrameWidth: this.props.width,
                initialFrameHeight: this.props.height
            });
        }
    }
    render() {
        this.id = this.props.id;
        return <div styleName="content" id={this.id} />;
    }
}

export default UEditor;

複製程式碼

筆者在專案中使用了加粗,斜體,下劃線,插入圖片,公式等功能,想要自定義配置均可參照ueditor.config.js修改。具體的將一一介紹,最後實現效果如下:

1534750500204

問題總結:

1. 禁止自動增高,改用滾動條

autoHeightEnabled: false
initialFrameWidth:this.props.width
initialFrameHeight:this.props.height
複製程式碼

autoHeightEnabled可以阻止自動增高,然後再自定義容器寬度和高度。

2. 自定義全域性樣式,如容器的padding,p標籤的line-height等

解決方法:ueditor.all.js的第6800多行的render方法,在其中可以自定義全域性樣式。

1534756003450

3. 導航條切換後,無法再次渲染

解決方法:在每次ueditor例項化之前,先刪除對應的id

 UE.delEditor(id);
複製程式碼

原因分析

從例項化和解除安裝例項的原始碼來看:

getEditor

UE.getEditor = function (id, opt) {
    var editor = instances[id];
    if (!editor) {
        editor = instances[id] = new UE.ui.Editor(opt);
        editor.render(id);//渲染編輯器
    }
    return editor;
};
複製程式碼

delEditor

UE.delEditor = function (id) {
    var editor;
    if (editor = instances[id]) {
        editor.key && editor.destroy();
        delete instances[id]
    }
};
複製程式碼

UE在全域性管理了一個例項池,每次例項化都會根據id檢索,然後生成例項。從getEditor的原始碼中可以看出,ueditor的一個例項在第一次初始化時存在一個editor.render(),這是將此id的例項渲染到對應的id容器上。然而,當使用者tab切換編輯器再切回來時,此時由於該例項已在例項池中存在,於是直接執行return editor,所以少了editor.render()這一步,於是不能重新渲染。所以,在Ueditor元件每次例項化之前,先進行delEditor解除安裝。這裡需要注意,從delEditor中可以看出ueditor解除安裝例項時呼叫了例項的destroy方法。從destroy的註釋來看:銷燬編輯器例項,使用textarea代替 ,這解釋了為什麼在切換編輯器或者解除安裝編輯器時,會出現編輯器變為textarea的情況,如圖所示:

1534821873619

4. 模擬placeholder實現預置文案

解決方法:在UE的例項中自定義方法,實現填充文字模擬placeholder的效果,程式碼如下:

//模擬placeholder和控制toolbar顯示隱藏
UE.Editor.prototype.initDiy = function (placeholder) {
    var _editor = this;
    //獲取焦點
    _editor.addListener("focus", function () {
        UE.isEditored = true;
        var Text = `<p style="color: #CDCDCD">${placeholder}</p>`
        var localHtml = _editor.getContent();
        if (localHtml === Text) {
            _editor.setContent("");//點選時清空
            _editor.focus(true);
        }
        //使得其他工具條display置為none
        var list = document.querySelectorAll('.edui-editor-toolbarbox');
        list.forEach((ele) => {
            ele.style.display = 'none';
        });
        var toolbar = findKey(_editor.key);
        toolbar.style.display = 'block';
    });
    // 插入圖片時存在問題
    // _editor.addListener("blur", function () {
    //     var localHtml = _editor.getContent();
    //     if (localHtml === '') {
    //         _editor.setContent(`<p style="color: #CDCDCD">${placeholder}</p>`);
    //     }
    //     // window.activeEditor = _editor.key;
    // });
    _editor.ready(function () {
        // _editor.fireEvent("blur");
        _editor.setContent(`<p style="color: #CDCDCD">${placeholder}</p>`);//填充預置文案
    });  
}
//尋找工具條
function findKey(key) {
    let ele = document.querySelector(`#${key}`);
    let toolbar = ele.querySelector('.edui-editor-toolbarbox');
    return toolbar;
}
複製程式碼

原來,筆者實現的效果是點選時清空,失焦時還原。但是,在做自定義工具條時產生了bug(在5中我會細說),因此我採用了另一種方案:初始時設定預設文案,當使用者聚焦時清空預設,使用者失焦後不再恢復該預設文案。也就是將blur事件註釋了。。。

5. 工具條顯示在編輯器頭部,顯示為懸浮效果,預設隱藏,聚焦時出現

實現思路:將themes/default/css/ueditor.css中加入:

.edui-default .edui-editor-toolbarbox {
    position: absolute;
    ······
    top: -36px;
}
複製程式碼

首先實現頭部偏移,然後通過控制toolbar對應dom元素的display來隱藏工具條。實現效果如下:

React中如何優雅的使用UEditor

下面解釋一下為什麼編輯器失焦的時候不恢復預置文案

從4中的程式碼可以看出,我們是通過觸發focus和blur事件分別清空和填充編輯器的內容。但是當我們點選工具條時,編輯器就會觸發blur事件!!於是就會出現各種bug。以百度官網的ueditor為例,控制檯輸入:

1534841282610

為該編輯器註冊點選事件,當點選加粗按鈕時,控制檯輸出:

1534841375113

為了避免點選工具條時觸發blur事件,筆者將自定義的blur事件全部註釋了。

6. 自定義按鈕和七牛雲圖片上傳

首先,在ueditor.config.js中找到toolbars陣列,增加一個diyimg字串,然後在zh-cn.js找到labelMap陣列,在末尾加上'diyimg': '插入圖片' 。最後,在ueditor.all.js中找到btnCmds陣列,加入diyimg字串。初始化時使用這個字串,工具條上就會顯示一個按鈕,但是我們發現他顯示的是這樣的:

1534842867408

這是因為ueditor預設使用加粗的icon作為自定義按鈕的預設icon,所以為了使用預設的插入圖片的圖示,我們需要到themes/default/css/ueditor.css中,在最後一行加入:

/*自定義圖片上傳按鈕 */
.edui-default .edui-toolbar .edui-for-diyimg .edui-icon {
    background-position: -380px 0px;//這個位置是“插入圖片”的icon,其他圖示可自行調整
}
複製程式碼

新增後,顯示效果如下:

1534842821079

圖示正常顯示後,需要為該圖示新增相應的點選事件,在ueditor.all.js中加入:

//圖片上傳
UE.commands['diyimg'] = {
    execCommand : function(){
        const upload = async(e) => {
            ······//完成圖片上傳的程式碼
        }
        const fileInput = document.getElementById('diyimg');//獲取dom上隱藏的一個input標籤
        fileInput.onchange = upload;
        fileInput.click();//觸發input標籤實現檔案上傳
        return true; 
    },
    queryCommandState:function(){
    
    }
};
複製程式碼

筆者這裡不贅述圖片上傳的程式碼,度娘上很多,我簡單說說實現的思路:

先實現一個插入圖片的按鈕,然後為該按鈕註冊相應的事件diyimg,然後在頁面中新增一個input file標籤並隱藏diyimg事件會觸發該標籤的點選事件,彈出檔案上傳彈窗,此時選擇檔案點選後會觸發onchange事件,執行相應的圖片上傳程式碼。上傳成功到伺服器後,伺服器會返回圖片對應的url,此時拿到該url填入對應編輯器例項,執行編輯器的插入圖片的程式碼:

this.execCommand('insertimage', {
    src: res.data.downloadUrl,//回撥傳來的url
    width:'60'
    // height:'45'
});
複製程式碼

7. 給在編輯器內部的img等標籤新增內聯樣式

ueditor預設存在xss過濾!!!這裡以給img標籤新增style=“vertical-top”為例。

首先要找到ueditor.config.js,在其中搜尋xss,在第403行左右有程式碼:

img:    [src', 'alt', 'title', 'width', 'height', 'id', '_src', 'loadingclass', 'class', 'data-latex'],
複製程式碼

往陣列裡加入style字串,然後在ueditor.all.js中搜尋UE.commands['insertimage'] ,在第約11172行找到str,往裡面加入內聯樣式即可。

一些吐槽:

1. 在react專案裡使用script形式引入,感覺格格不入
2. 為了實現placeholder,各個事件之間存在不正交的現象。諸如點選按鈕,卻觸發了編輯器的失焦事件
3. 在使用自定義的字數限制功能時,筆者使用ueditor的contentChange去檢測內容字數,但是contentChange事件是定時的,所以計算字數會有問題。

相關文章