富文本编辑器tinymce使用Prism代码高亮插件遇到的问题及解决办法

标签: TinyMCE  Prism  line-numbers

最近写博客需要选择一款文本编辑器,选了几款觉得 TinyMCE 不错,插件比较齐全,界面也比较美观,不过在使用 Prism 的时候,却出现了问题。

Q: Prism 的插件 line-numbers 必须要在 pre 标签商添加 line-numbers 的,才能显示行号,这个时候如果使用其他方式调用 Prism 的API进行渲染,则就需要额外添加这个样式就很麻烦,有没有办法解决呢?

A:下载 line-numbers 源码,找到如下代码

Prism.hooks.add('complete', function (env) {
    if (!env.code) {
        return;
    }

    // works only for <code> wrapped inside <pre> (not inline)
    var pre = env.element.parentNode;
    var clsReg = /\s*\bline-numbers\b\s*/;
    if (
        !pre || !/pre/i.test(pre.nodeName) ||
        // Abort only if nor the <pre> nor the <code> have the class
        (!clsReg.test(pre.className) && !clsReg.test(env.element.className))
    ) {
        return;
    }

    if (env.element.querySelector('.line-numbers-rows')) {
        // Abort if line numbers already exists
        return;
    }

    if (clsReg.test(env.element.className)) {
        // Remove the class 'line-numbers' from the <code>
        env.element.className = env.element.className.replace(clsReg, ' ');
    }
    if (!clsReg.test(pre.className)) {
        // Add the class 'line-numbers' to the <pre>
        pre.className += ' line-numbers';
    }

    var match = env.code.match(NEW_LINE_EXP);
    var linesNum = match ? match.length + 1 : 1;
    var lineNumbersWrapper;

    var lines = new Array(linesNum + 1);
    lines = lines.join('<span></span>');

    lineNumbersWrapper = document.createElement('span');
    lineNumbersWrapper.setAttribute('aria-hidden', 'true');
    lineNumbersWrapper.className = 'line-numbers-rows';
    lineNumbersWrapper.innerHTML = lines;

    if (pre.hasAttribute('data-start')) {
        pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
    }

    env.element.appendChild(lineNumbersWrapper);

    _resizeElement(pre);

    Prism.hooks.run('line-numbers', env);
});

修改其中的代码部分

/*修改 原代码
if (
  !pre || !/pre/i.test(pre.nodeName) ||
  // Abort only if nor the <pre> nor the <code> have the class
  (!clsReg.test(pre.className) && !clsReg.test(env.element.className))
) {
  return;
}*/
if (
  !pre || !/pre/i.test(pre.nodeName)
) {
  return;
}

修改的理由就是
(!clsReg.test(pre.className) && !clsReg.test(env.element.className) 这句代码表示如果 pre 或者code没有 line-numbers 的样式就直接结束,意思就是说需要你手动在 pre 标签上添加 line-numers 样式; 但是它下面还有一句代码

if (!clsReg.test(pre.className)) {
    // Add the class 'line-numbers' to the <pre>
    pre.className += ' line-numbers';
}

注释说的很明白,如果pre 没有line-numbers 样式就添加该class,很明显上一句和这一句是有矛盾的,所以只需要将上面的那个判断修改一下就可以实现自动显示行号而不需要额外添加什么样式了

Q : 使用 TinyMCE 的时候 line-numbers 插件已经改好了,还是无法显示行号?

A : 下载 TinyMCE 源码,并找到插件 codesample 打开 plugin.js 源码 继续分析,发现有两处调用了 Prism 的 API
如下:
第一处:CodeSample.ts => Prism.highlightElement(node);

var insertCodeSample = function (editor, language, code) {
  editor.undoManager.transact(function () {
    var node = getSelectedCodeSample(editor);

    code = DOMUtils.DOM.encode(code);

    if (node) {
      editor.dom.setAttrib(node, 'class', 'language-' + language);
      node.innerHTML = code;
      Prism.highlightElement(node);
      editor.selection.select(node);
    } else {
      editor.insertContent('<pre id="__new" class="language-' + language + '">' + code + '</pre>');
      editor.selection.select(editor.$('#__new').removeAttr('id')[0]);
    }
  });
};  

第二处:FilterContent.ts => Prism.highlightElement(elm);

editor.on('SetContent', function () {

  var unprocessedCodeSamples = $('pre').filter(Utils.trimArg(Utils.isCodeSample)).filter(function (idx, elm) {
    return elm.contentEditable !== 'false';
  });
  if (unprocessedCodeSamples.length) {
    editor.undoManager.transact(function () {
      unprocessedCodeSamples.each(function (idx, elm) {
        $(elm).find('br').each(function (idx, elm) {
          elm.parentNode.replaceChild(editor.getDoc().createTextNode('\n'), elm);
        });

        elm.contentEditable = false;
        elm.innerHTML = editor.dom.encode(elm.textContent);
        Prism.highlightElement(elm);
        elm.className = $.trim(elm.className);
      });
    });
  }
});

第一处的 node 和第二处 elm 通过调试可以发现是 pre 标签直接包裹了源码,即 pre 标签下是没有 code 标签的,一般这两个标签是组合使用的;虽然 Prism 没有 code 标签也可以实现高亮,但通过查看 Prism API及其插件的实现,可以发现其实最好还是需要 code 标签(可能现在的API有变化),否则有些插件就用不了,比如 line-numbers 插件就依赖 code标签,所以我们要做的是给这两处给pre标签添加上 code 子标签

先写一个公共的包装的方法, 在 insertCodeSample 方法上面(位置没关系,只要能调用到就行)

// 包装code
var wrapSelectedCodeSample = function(code, language){
   return '<code class="language-'+language+'">'+code+'</code>'
}

修改第一处的代码

var insertCodeSample = function (editor, language, code) {
  editor.undoManager.transact(function () {
    var node = getSelectedCodeSample(editor);

    // 修改 原代码 code = DOMUtils.DOM.encode(code);
    code = wrapSelectedCodeSample(DOMUtils.DOM.encode(code), language);

    if (node) {
      editor.dom.setAttrib(node, 'class', 'language-' + language);
      node.innerHTML = code;
      // 修改 原代码 Prism.highlightElement(node);
      Prism.highlightElement(node.children[0]);
      editor.selection.select(node);
    } else {
      editor.insertContent('<pre id="__new" class="language-' + language + '">' + code + '</pre>');
      editor.selection.select(editor.$('#__new').removeAttr('id')[0]);
    }
  });
};

修改第二处的代码

editor.on('SetContent', function () {

  var unprocessedCodeSamples = $('pre').filter(Utils.trimArg(Utils.isCodeSample)).filter(function (idx, elm) {
    return elm.contentEditable !== 'false';
  });
  if (unprocessedCodeSamples.length) {
    editor.undoManager.transact(function () {
      unprocessedCodeSamples.each(function (idx, elm) {
        $(elm).find('br').each(function (idx, elm) {
          elm.parentNode.replaceChild(editor.getDoc().createTextNode('\n'), elm);
        });
        // 新增提取语言
        var language = null
        if($(elm)[0].className && $(elm)[0].className.match(/language-([\w#]+)\s?/)){
          language = $(elm)[0].className.match(/language-([\w#]+)\s?/)[1]
        }

        elm.contentEditable = false;
        // 修改 原代码  elm.innerHTML = editor.dom.encode(elm.textContent);
        elm.innerHTML = wrapSelectedCodeSample(editor.dom.encode(elm.textContent), language);
        // 修改 原代码 Prism.highlightElement(elm);
        Prism.highlightElement(elm.children[0]);
        elm.className = $.trim(elm.className);
      });
    });
  }
});

注意:如果是Prism.ts需要作如下修改
修改前

import { self, document, Worker } from '@ephox/dom-globals';
...
const window: any = {};
const global: any = window;
const module: any = { exports: {} };
...

修改后

import { self, window, document, Worker } from '@ephox/dom-globals';
...
// const window: any = {};
const global: any = window;
const module: any = { exports: {} };
...

window 作为import导入

改之后的效果
这里写图片描述

如果开发阶段执行命令: grunt start 报错

cannot find module 'webpack-dev-server/lib/util/createDoamin'

这个是由于webpack-dev-server下的包已经改了而grunt-webpack还引用的原来的包照成的

修改如下:
找到文件 node_modules/grunt-webpack/webpack-dev-server.js
找到代码

const createDomain = require('webpack-dev-server/lib/util/createDomain');

修改如下

const createDomain = require('webpack-dev-server/lib/utils/createDomain')
版权声明:本文为LTC19920116原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LTC19920116/article/details/81132379