富文本編輯器tinymce使用Prism代碼高亮插件遇到的問題及解決辦法

最近寫博客需要選擇一款文本編輯器,選了幾款覺得 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')
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章