最近寫博客需要選擇一款文本編輯器,選了幾款覺得 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')