最近写博客需要选择一款文本编辑器,选了几款觉得 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 wrapped inside (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 nor the 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
env.element.className = env.element.className.replace(clsReg, ' ');
}
if (!clsReg.test(pre.className)) {
// Add the class 'line-numbers' to the
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('');
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 nor the 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.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(''">'
+ code + '
');
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+'
'
}
修改第一处的代码
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(''">'
+ code + '
');
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')