Wordpress 代码高亮 修改原生区块使后台可设置高亮语言 第二部分
Wordpress 是世界上最为流行的博客工具,很多喜爱编程的同学都使用它写技术博客,涉及到代码时,没有语法高亮是一件让读者头疼的事。
这篇文章就是记录我如何为自己的博客添加代码高亮功能,同时后台支持为代码区块(Gutenberg)设置语法高亮的语言。这是一种通用的方法,可以为任何区块新增想要的功能。
由于不可抗力,文章将分为两部分,本文为第二部分:
- 第一部分:如何为 Wordpress 添加代码高亮功能。
- 第二部分:修改 Wordpress 后台区块编辑器来支持高亮语言的选择。
本文需要读者具有一定的 Wordpress 主题开发的基础知识。
将会用到的 API
Wordpress Javascript Packages Api:
- wp.element.createElement()
- wp.components.DropdownMenu()
- wp.components.MenuItem()
- wp.blockEditor.PlainText()
- wp.hooks.addFilter()
- wp.i18n.__()
准备工作
创建 myguten.js 和 myguten.css 2 个文件到主题目录下。
引入创建的文件
Wordpress 提供了添加区块功能的方法,只不过是通过 Javascript 来控制,因此要先添加用于修改区块编辑器的相关脚本文件(myguten.js)和样式文件(myguten.css),在 functions.php 中添加以下代码:
// 升级 code Block 以适配 highlightjs
function myguten_enqueue() {
wp_enqueue_script(
'myguten-script',
get_template_directory_uri() . '/js/myguten.js',
array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ),
filemtime( get_template_directory_uri() . '/js/myguten.js' )
);
wp_enqueue_style(
'myguten-style',
get_template_directory_uri() . '/css/myguten.css'
);
}
add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' );
值得注意的是,脚本要求添加三个依赖项 wp-blocks、wp-dom-ready、wp-edit-post,这样在 myguten.js 的上下文里就可以获取到 Wordpress 提供的 Api。
添加 Hook
在 myguten.js 中,为 blocks.registerBlockType
钩子添加函数:
wp.hooks.addFilter(
'blocks.registerBlockType',
'Namespace',
setBlockType
);
添加完成后,当 blocks.registerBlockType
钩子被触发,Wordpress 便会执行 setBlockType
方法,我们就在这个方法里为区块添加功能。
blocks.registerBlockType 钩子
首先要知道 blocks.registerBlockType
钩子会在什么时候触发,顾名思义,在注册一个区块类型的时候会被触发。
该钩子会传入 2 个参数给 setBlockType
方法:
settings
:Object
,该类型区块的所有配置name
:String
,该类型区块的名字
我们就是通过判断区块名来修改特定区块的配置,从而达到为区块添加、删除或者修改功能的。
接下来开始编写 setBlockType
方法。
判断区块类型
好的,让我们写的通用一点,这样方便以后修改别的类型:
function setBlockType(settings, blockName) {
switch (blockName) {
case 'core/code':
return modifyCodeBlock(settings);
default:
return settings;
}
}
通过 switch 来判断区块类型,然后调用特定的方法来修改区块并返回修改后的配置对象。
修改区块的配置对象
你需要了解它,才能正确的修改它。本文的需求只需要使用到配置对象里的其中 3 个属性:
attributes
:Object
,里面是配置对象的所有属性。edit
:Function
,这是一个方法,返回 React Element 列表,用来渲染对应类型的区块编辑器,第一个元素是编辑器的控制器部分,第二个元素是编辑器的编辑部分。save
:Function
,这是一个方法,返回 React Element,就是显示在文章中的元素。
对应的处理方法也很直接,在 attributes
里面添加新的属性,用来表示选择的高亮语言,重写 edit
和 save
方法。
添加新的属性到 attributes
我们把新的属性命名为 language
:
settings.attributes.language = {
type: String,
default: 'plain'
}
重写 save 方法
先看一下 hightlighjs
要求的 HTML 格式:
<pre><code class="language">...</code></pre>
然后按要求在 save
方法里返回即可:
settings.save = function (_ref) {
var attrs = _ref.attributes;
return wp.element.createElement("pre", null, Object(wp.element.createElement)("code", { className: attrs.language || 'plain' }, attrs.content))
}
这里的 wp.element.createElement
其实就是 React 的 createElement
方法。
重写 edit 方法
目标有两个:添加语言选择器,和添加当前选择语言的水印。
settings.edit = function (_ref) {
var attributes = _ref.attributes,
setAttributes = _ref.setAttributes,
className = _ref.className;
// Build Language Selector
var langSelector = wp.element.createElement(wp.blockEditor.BlockControls, { key: 'controls' }, wp.element.createElement(wp.components.DropdownMenu, {
className: 'animus-code-block-language-dropdown-menu',
icon: 'hammer',
label: '选择编程语言',
children: function (props) {
let langs = [
{ name: 'Plain', val: 'plain' },
{ name: 'Javascript', val: 'javascript' },
{ name: 'Python', val: 'python' },
{ name: 'PHP', val: 'php' },
{ name: 'HTML', val: 'html' },
{ name: 'CSS', val: 'css' },
{ name: 'SASS', val: 'sass' },
{ name: 'Shell', val: 'shell' }
];
let children = [];
for (let i = 0; i < langs.length; i++) {
let lang = langs[i];
let langElem = wp.element.createElement(wp.components.MenuItem, { value: lang.val, onClick: function () {
props.onClose();
setAttributes({
language: lang.val
});
} }, lang.name);
children.push(langElem);
}
return children;
}
}));
// Build Plain Text Editor
var plainTextEditor = wp.element.createElement(wp.blockEditor.PlainText, {
className: 'animus-code-editor',
value: utils_unescape(attributes.content),
onChange: function (content) {
return setAttributes({
content: utils_escape(content)
});
},
placeholder: wp.i18n.__('Write code…'),
"aria-label": wp.i18n.__('Code')
});
var codeEditorWaterMark = wp.element.createElement("div", {
className: 'animus-code-editor-watermark',
}, attributes.language);
var element = wp.element.createElement("div", {
className: className
}, plainTextEditor, codeEditorWaterMark);
return [langSelector, element];
}
附上文中的一些内容处理方法
这些方法均来自 Wordpress
:
/**
* Converts the first two forward slashes of any isolated URL from the HTML entity
* I into /.
*
* An isolated URL is a URL that sits in its own line, surrounded only by spacing
* characters.
*
* See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403
*
* @param {string} content The content of a code block.
* @return {string} The given content with the first two forward slashes of any
* isolated URL from the HTML entity I into /.
*/
function unescapeProtocolInIsolatedUrls(content) {
return content.replace(/^(\s*https?:)//([^\s<>"]+\s*)$/m, '$1//$2');
}
/**
* Returns the given content translating all [ into [.
*
* @param {string} content The content of a code block.
* @return {string} The given content with all [ into [.
*/
function unescapeOpeningSquareBrackets(content) {
return content.replace(/[/g, '[');
}
/**
* Returns the given content with all its ampersand characters converted
* into their HTML entity counterpart (i.e. & => &)
*
* @param {string} content The content of a code block.
* @return {string} The given content with its ampersands converted into
* their HTML entity counterpart (i.e. & => &)
*/
function unescapeAmpersands(content) {
return content.replace(/&/g, '&');
}
/**
* Escapes ampersands, shortcodes, and links.
*
* @param {string} content The content of a code block.
* @return {string} The given content with some characters escaped.
*/
function utils_escape(content) {
return lodash.flow(escapeAmpersands, escapeOpeningSquareBrackets, escapeProtocolInIsolatedUrls)(content || '');
}
/**
* Unescapes escaped ampersands, shortcodes, and links.
*
* @param {string} content Content with (maybe) escaped ampersands, shortcodes, and links.
* @return {string} The given content with escaped characters unescaped.
*/
function utils_unescape(content) {
return lodash.flow(unescapeProtocolInIsolatedUrls, unescapeOpeningSquareBrackets, unescapeAmpersands)(content || '');
}
/**
* Converts the first two forward slashes of any isolated URL into their HTML
* counterparts (i.e. // => //). For instance, https://youtube.com/watch?x
* becomes https://youtube.com/watch?x.
*
* An isolated URL is a URL that sits in its own line, surrounded only by spacing
* characters.
*
* See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403
*
* @param {string} content The content of a code block.
* @return {string} The given content with its ampersands converted into
* their HTML entity counterpart (i.e. & => &)
*/
function escapeProtocolInIsolatedUrls(content) {
return content.replace(/^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, '$1//$2');
}
/**
* Returns the given content with all opening shortcode characters converted
* into their HTML entity counterpart (i.e. [ => [). For instance, a
* shortcode like [embed] becomes [embed]
*
* This function replicates the escaping of HTML tags, where a tag like
* <strong> becomes <strong>.
*
* @param {string} content The content of a code block.
* @return {string} The given content with its opening shortcode characters
* converted into their HTML entity counterpart
* (i.e. [ => [)
*/
function escapeOpeningSquareBrackets(content) {
return content.replace(/\[/g, '[');
}
/**
* Returns the given content with all its ampersand characters converted
* into their HTML entity counterpart (i.e. & => &)
*
* @param {string} content The content of a code block.
* @return {string} The given content with its ampersands converted into
* their HTML entity counterpart (i.e. & => &)
*/
function escapeAmpersands(content) {
return content.replace(/&/g, '&');
}
以上便是为 Wordpress 代码区块添加高亮语言选择的方法。