Skip to content

在wangEditor上使用Code-prettify进行代码高亮

代码显示全部都是白色,看起来比较费时间,而且也没有行数显示,于是查到了使用Google的Code-pretty这个插件进行上色。这样上色的好处是,原来的代码不需要加入一些颜色的代码,只需要在显示的时候JS加上去就好了。而且之前看到很多博客都使用Code-pretty来上色。

code-prettify

下载google/code-prettify

github:code-prettify

最简单的使用demo

html

<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title>Examples</title>
        <link href="prettify.css" rel="stylesheet">
        <script type="text/javascript" src="prettify.js"></script>
    </head>
<body>
   <pre>
        <code class="prettyprint lang-js linenums">
            var a = 0;
            alert(a);
        </code>
    </pre>
    <script type="text/javascript">
        prettyPrint();
    </script>
</body>
</html>

引用CSS、JS,然后写一个prettyPrint()脚本。

修改wangEditor

问题变成了,如何让后台添加precode标签的时候,自动加入class标签,标记code的类型,方便上色。最开始,我以为code-prettify不支持pre里面含有code标签,而wangEditor的代码编辑器就是<pre><code>的格式,需要修改wangEditor源码。

注意到wangEditor的文件下面有src文件夹,因此可以尝试去修改src,然而我引用的是.min.js,所以新问题是如何在修改src后自动生成.min,js文件。

搭建windows下的gulp环境

83a5cbb0c31010fdfa8d8acc6cb7b1f3.png 注意到有一个不认识的gulpfile.jspackage.json,于是百度了一下,查到gulpjs插件,是前端开发中对代码进行自动化构建的工具,能对JS/coffee/sass/less/html/image/css等文件进行测试、检查、合并、压缩、格式化,简化重复的工作。只要我搭建一样的环境,就能够修改wangEditor的源码了。

  1. 安装node.js ec0731ea84868e50b554bb5f966217d1.png

gulp是基于node.js开发的,所以需要装node.js,进入node.js下载界面,下载最新的稳定版本(2018年3月19日最新稳定版本8.10.0LTS),安装到任意位置。

shell
# 【查看node.js版本】
node -v
shell
# 【查看npm版本】
npm -v
  1. 安装cnpm

npm(node package manager)是nodejs的包管理器,由于源都在国外,因此淘宝开发了一个淘宝NPM镜像,每10分钟和官网同步一次。cnpm的使用方法和npm完全一样,只需要把npm替换成cnpm执行即可,win+R,输入cmd进入命令行,执行安装命令:

shell
npm install cnpm -g --registry=https://registry.npm.taobao.org
shell
# 【查看cnpm版本】
cnpm -v

注意,安装完后要关闭cmd再重新打开cmd,否则直接使用cnpm会出错。

  1. 安装全局gulp
shell
# 【安装gulp】
cnpm install gulp -g
# 【查看gulp版本】
gulp -v
  1. package.json文件

这一步wangEditor作者已经写好,不需要进行。

  1. 把gulp部署到wangEditor项目里面

除了安装全局的,还需要在wangEditor项目里面装一个专门的,注意要先进入项目目录。

shell
# 进入项目目录
G:
cd G:\test\plugin\wangEditor
# 安装依赖包
cnpm install --save-dev
# 安装gulp-less
cnpm install gulp-less --save-dev
# 安装gulp
cnpm install gulp --save-dev

--save-dev的意思是只安装开发需要的包。完成后会在wangEditor目录下生成很深很深的node_modules文件夹。其中,less是一种CSS的生成器,wangEditor作者采用了这种生成器来写CSS。

  1. gulpfile.js文件

这一步wangEditor作者也写好了,不需要进行。

  1. 运行gulp

打开phpstorm(我是用phpstorm开发的),再打开wangEditor\gulpfile.js,在文件上右键,会发现多了一个Show Gulp Tasksbd11aa2caaa86ea37a727eb25e6f4c6a.png 点击后,就会弹出来相关的东西,右键可以运行一个默认程序。 2142f157e4817cc8bd3ead41b4ffb36a.png 这个程序就是更新程序,如果没有任何错误,会提示这些东西: 8dc2881f57b498ddb5403426dafe43d0.png 我们修改任意src里面的js、less文件,再运行这里的gulpfile.js就会执行更新,.min,js、.min,css就会更新。但其实,只需要修改后ctrl+s保存,这个gulpfile.js就会自动执行更新,很方便。

修改wangEditor源代码(错误尝试)

目标是把:

html
<pre>
    <code>
          xxxxx
    </code>
</pre>

修改为:

html
<pre class="prettyprint lang-xxxx linenums">
     xxxxx
</pre>

wangEditor的src文件夹目录结构是这样的: 3f1b025be260fcacd75d07bcf2755067.png

修改src/js/menus/code/index.js

这个js写了菜单code的所有代码,先对这个进行修改。代码思路很清晰:

text
* 如果选中了文字,用code进行包裹,效果类似:`我是被code包裹的文字`
* 否则,如果光标在<pre><code>里面,则说明要更新,调用更新的函数
* 否则,调用创建新的<pre><code>区域函数

跟着这个思路修改就行。

  1. onclick里,修改编辑内容的入口参数,增加codeclass

注意到源代码:

js
if (this._active) {
    // 选中状态,将编辑内容
    this._createPanel($startElem.html())
} else {
    // 未选中状态,将创建内容
    this._createPanel()
}

看到用了html()这个方法,估计这个$startElem就是指的<code><pre>的dom,于是类似地去写,把第3行改为:

js
this._createPanel($startElem.attr('class').split(' ')[1],$startElem.html())

获取<pre class="prettyprint lang-xxxx linenums">中的lang-xxx(代码类别)

完整代码:

js
onClick: function (e) {
    const editor = this.editor
    const $startElem = editor.selection.getSelectionStartElem()
    const $endElem = editor.selection.getSelectionEndElem()
    const isSeleEmpty = editor.selection.isSelectionEmpty()
    const selectionText = editor.selection.getSelectionText()
    let $code

    if (!$startElem.equal($endElem)) {
        // 跨元素选择,不做处理
        editor.selection.restoreSelection()
        return
    }
    if (!isSeleEmpty) {
        // 选取不是空,用 <code> 包裹即可
        $code = $(`<code>${selectionText}</code>`)
        editor.cmd.do('insertElem', $code)
        editor.selection.createRangeByElem($code, false)
        editor.selection.restoreSelection()
        return
    }

    // 选取是空,且没有夸元素选择,则插入 <pre><code></code></prev>
    if (this._active) {
        // 选中状态,将编辑内容
        this._createPanel($startElem.attr('class').split(' ')[1],$startElem.html())
    } else {
        // 未选中状态,将创建内容
        this._createPanel()
    }
},
  1. _createPanel里,增加select的内容

这里有个技巧,如果option里面,有selected=selected属性,就可以让它成为select的默认选中值。

js
_createPanel: function (codeclass,value) {
    // value - 要编辑的内容
    codeclass = codeclass || 'lang-c'
    value = value || ''
    const type = !value ? 'new' : 'edit'
    const textId = getRandom('texxt')
    const btnId = getRandom('btn')
    const selectId = getRandom('select')
    const sval = new Array('c','java','python','bash','a','b','d')
    const stxt = new Array('C语言','Java','Python','Bash命令行','按键精灵','易语言','其他')
    var sh = ''
    for (var i=0;i<sval.length;i++){
        if (codeclass === 'lang-'+sval[i]) {
            sh += `<option value=lang-${sval[i]} selected=selected>${stxt[i]}</option>`
        }else{
            sh += `<option value=lang-${sval[i]}>${stxt[i]}</option>`
        }
    }
    const panel = new Panel(this, {
        width: 500,
        // 一个 Panel 包含多个 tab
        tabs: [
            {
                // 标题
                title: '插入代码',
                // 模板
                tpl: `<div>
                    <div class="w-e-select-container">
                        <select id="${selectId}" value="${codeclass}">
                        ${sh}
                        </select>
                    </div>
                    <textarea id="${textId}" style="height:145px;;">${value}</textarea>
                    <div class="w-e-button-container">
                        <button id="${btnId}" class="right">插入</button>
                    </div>
                <div>`,
                // 事件绑定
                events: [
                    // 插入代码
                    {
                        selector: '#' + btnId,
                        type: 'click',
                        fn: () => {
                            const $text = $('#' + textId)
                            let text = $text.val() || $text.html()
                            text = replaceHtmlSymbol(text)
                            const $codeclass = $('#' + selectId)
                            let codeclass = $codeclass.val()
                            codeclass = replaceHtmlSymbol(codeclass)
                            if (type === 'new') {
                                // 新插入
                                this._insertCode(codeclass,text)
                            } else {
                                // 编辑更新
                                this._updateCode(codeclass,text)
                            }

                            // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
                            return true
                        }
                    }
                ]
            } // first tab end
        ] // tabs end
    }) // new Panel end

    // 显示 panel
    panel.show()

    // 记录属性
    this.panel = panel
},
  1. 修改_insertCode、_updateCode、_tryChangeActive
js
// 插入代码
_insertCode: function (codeclass,value) {
    const editor = this.editor
    editor.cmd.do('insertHTML', `<pre class="prettyprint ${codeclass} linenums">${value}</pre><p><br></p>`)
},

// 更新代码
_updateCode: function (codeclass,value) {
    const editor = this.editor
    const $selectionELem = editor.selection.getSelectionContainerElem()
    if (!$selectionELem) {
        return
    }
    $selectionELem.html(value)
    $selectionELem.attr('class','prettyprint '+codeclass+ ' linenums')
    editor.selection.restoreSelection()
    //BWB:特别地恢复标签:
},

// 试图改变 active 状态
tryChangeActive: function (e) {
    const editor = this.editor
    const $elem = this.$elem
    const $selectionELem = editor.selection.getSelectionContainerElem()
    if (!$selectionELem) {
        return
    }
    //const $parentElem = $selectionELem.parent()
    //if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') {
    if ($selectionELem.getNodeName() === 'PRE') {
        this._active = true
        $elem.addClass('w-e-active')
    } else {
        this._active = false
        $elem.removeClass('w-e-active')
    }
}

修改text/index.js

text控制了编辑器的操作,比如光标变化、按下键盘按键,因此需要对这里也进行修改,因为我们改变了标签。

将所有的:

js
if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE')

改为:

js
if(selectionNodeName !== 'PRE')

修改src/less/panel.less

.w-e-panel-tab-content.w-e-button-container:after后面增加select的CSS

less
.w-e-select-container{
    width:120px;
    display:block;
    height:30px;
    margin-bottom:10px;

    select{
        background: #f2f2f2;
        border: none;
        padding-left: 10px;
        width: 100%;
        height: 30px;
        color:#242424;
        display:block;
        font-size:14px;

        option{
            background:#fff;
            border: none;
            padding:0px 2px;
        }
    }

}

修改src/less/text.less

修改pre标签的CSS即可,这里由于我没有使用wangEditor自带的CSS,因此没有做。

修改wangEditor源代码(正确尝试)

其实呢,Code-prettify是支持<pre><code>标签的,但是class需要写在code里面:

html
<pre>
     <code class="prettyprint lang-xxxx linenums">
     ....

这种标签竟然是HTML5的,还是要多看readme文件呀。因此wangEditor只需要改src/js/menus/code/index.js即可,注意这里和前面会有一点不同:

js
onClick: function (e) {
    const editor = this.editor
    const $startElem = editor.selection.getSelectionStartElem()
    const $endElem = editor.selection.getSelectionEndElem()
    const isSeleEmpty = editor.selection.isSelectionEmpty()
    const selectionText = editor.selection.getSelectionText()
    let $code

    if (!$startElem.equal($endElem)) {
        // 跨元素选择,不做处理
        editor.selection.restoreSelection()
        return
    }
    if (!isSeleEmpty) {
        // 选取不是空,用 <code> 包裹即可
        $code = $(`<code>${selectionText}</code>`)
        editor.cmd.do('insertElem', $code)
        editor.selection.createRangeByElem($code, false)
        editor.selection.restoreSelection()
        return
    }

    // 选取是空,且没有夸元素选择,则插入 <pre><code></code></prev>
    if (this._active) {
        // 选中状态,将编辑内容
        this._createPanel($startElem.attr('class').split(' ')[1],$startElem.html())
    } else {
        // 未选中状态,将创建内容
        this._createPanel()
    }
},

_createPanel: function (codeclass,value) {
    // value - 要编辑的内容
    codeclass = codeclass || 'lang-c'
    value = value || ''
    const type = !value ? 'new' : 'edit'
    const textId = getRandom('texxt')
    const btnId = getRandom('btn')
    const selectId = getRandom('select')
    const sval = new Array('c','java','PHP','python','matlab','html','css','js','bash','ajjl','eyy','txt')
    const stxt = new Array('C/C++','Java','PHP','Python','Matlab','HTML','CSS','JavaScript','Bash命令行','按键精灵','易语言','普通文本')
    var sh = ''
    for (var i=0;i<sval.length;i++){
        if (codeclass === 'lang-'+sval[i]) {
            sh += `<option value=lang-${sval[i]} selected=selected>${stxt[i]}</option>`
        }else{
            sh += `<option value=lang-${sval[i]}>${stxt[i]}</option>`
        }
    }
    const panel = new Panel(this, {
        width: 500,
        // 一个 Panel 包含多个 tab
        tabs: [
            {
                // 标题
                title: '插入代码',
                // 模板
                tpl: `<div>
                    <div class="w-e-select-container">
                        <select id="${selectId}" value="${codeclass}">
                        ${sh}
                        </select>
                    </div>
                    <textarea id="${textId}" style="height:145px;;">${value}</textarea>
                    <div class="w-e-button-container">
                        <button id="${btnId}" class="right">插入</button>
                    </div>
                <div>`,
                // 事件绑定
                events: [
                    // 插入代码
                    {
                        selector: '#' + btnId,
                        type: 'click',
                        fn: () => {
                            const $text = $('#' + textId)
                            let text = $text.val() || $text.html()
                            text = replaceHtmlSymbol(text)
                            const $codeclass = $('#' + selectId)
                            let codeclass = $codeclass.val()
                            codeclass = replaceHtmlSymbol(codeclass)
                            if (type === 'new') {
                                // 新插入
                                this._insertCode(codeclass,text)
                            } else {
                                // 编辑更新
                                this._updateCode(codeclass,text)
                            }

                            // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭
                            return true
                        }
                    }
                ]
            } // first tab end
        ] // tabs end
    }) // new Panel end

    // 显示 panel
    panel.show()

    // 记录属性
    this.panel = panel
},

// 插入代码
_insertCode: function (codeclass,value) {
    const editor = this.editor
    editor.cmd.do('insertHTML', `<pre><code class="prettyprint ${codeclass} linenums">${value}</code></pre><p><br></p>`)
},

// 更新代码
_updateCode: function (codeclass,value) {
    const editor = this.editor
    const $selectionELem = editor.selection.getSelectionContainerElem()
    if (!$selectionELem) {
        return
    }
    $selectionELem.html(value)
    $selectionELem.attr('class','prettyprint '+codeclass+' linenums')
    editor.selection.restoreSelection()
},

// 试图改变 active 状态
tryChangeActive: function (e) {
    const editor = this.editor
    const $elem = this.$elem
    const $selectionELem = editor.selection.getSelectionContainerElem()
    if (!$selectionELem) {
        return
    }
    const $parentElem = $selectionELem.parent()
    if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') {
        this._active = true
        $elem.addClass('w-e-active')
    } else {
        this._active = false
        $elem.removeClass('w-e-active')
    }
}

最终效果: 0922f5fdfd940f311ee49fbdfbaad87c.png

参考网页

转载请注明出处https://bananaoven.com/articles/172.html | 香蕉微波炉
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。