Skip to content
Merged
21 changes: 21 additions & 0 deletions lib/extend/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class Filter {
if (index !== -1) list.splice(index, 1);
}

queryCount(type) {
const filters = this.list(type);
return filters.length;
}

exec(type, data, options = {}) {
const filters = this.list(type);
if (filters.length === 0) return Promise.resolve(data);
Expand Down Expand Up @@ -87,6 +92,22 @@ class Filter {

return args[0];
}

execFirstSync(type, data, options = {}) {
const filters = this.list(type);
const filtersLen = filters.length;
if (filtersLen === 0) return null;

const ctx = options.context;
const args = options.args || [];

args.unshift(data);

const result = Reflect.apply(filters[0], ctx, args);
args[0] = result == null ? args[0] : result;

return args[0];
}
}

module.exports = Filter;
11 changes: 10 additions & 1 deletion lib/hexo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ class Hexo extends EventEmitter {
'update_package', // Update package.json
'load_config', // Load config
'load_theme_config', // Load alternate theme config
'load_plugins' // Load external plugins & scripts
'load_plugins', // Load external plugins & scripts
'load_highlight' // Load highlight.js or prism.js
], name => require(`./${name}`)(this)).then(() => this.execFilter('after_init', null, { context: this })).then(() => {
// Ready to go!
this.emit('ready');
Expand Down Expand Up @@ -478,13 +479,21 @@ class Hexo extends EventEmitter {
});
}

queryFilterCount(type) {
return this.extend.filter.queryCount(type);
}

execFilter(type, data, options) {
return this.extend.filter.exec(type, data, options);
}

execFilterSync(type, data, options) {
return this.extend.filter.execSync(type, data, options);
}

execFirstFilterSync(type, data, options) {
return this.extend.filter.execFirstSync(type, data, options);
}
}

Hexo.lib_dir = libDir + sep;
Expand Down
3 changes: 3 additions & 0 deletions lib/hexo/load_highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('../plugins/filter/highlight');
129 changes: 44 additions & 85 deletions lib/plugins/filter/before_post_render/backtick_code_block.js
Original file line number Diff line number Diff line change
@@ -1,111 +1,70 @@
'use strict';

let highlight, prismHighlight;

const rBacktick = /^((?:[^\S\r\n]*>){0,3}[^\S\r\n]*)(`{3,}|~{3,})[^\S\r\n]*((?:.*?[^`\s])?)[^\S\r\n]*\n((?:[\s\S]*?\n)?)(?:(?:[^\S\r\n]*>){0,3}[^\S\r\n]*)\2[^\S\r\n]?(\n+|$)/gm;
const rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/;
const rLangCaption = /([^\s]+)\s*(.+)?/;

const escapeSwigTag = str => str.replace(/{/g, '{').replace(/}/g, '}');

function backtickCodeBlock(data) {
const dataContent = data.content;

const hljsCfg = this.config.highlight || {};
const prismCfg = this.config.prismjs || {};
module.exports = ctx => {
return function backtickCodeBlock(data) {
const dataContent = data.content;

if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || (!hljsCfg.enable && !prismCfg.enable)) return;
if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || !ctx.queryFilterCount('highlight')) return;

data.content = dataContent.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
let content = _content.replace(/\n$/, '');
data.content = dataContent.replace(rBacktick, ($0, start, $2, _args, _content, end) => {
let content = _content.replace(/\n$/, '');

// neither highlight or prismjs is enabled, return escaped content directly.
if (!hljsCfg.enable && !prismCfg.enable) return escapeSwigTag($0);
// neither highlight or prismjs is enabled, return escaped content directly.
if (!ctx.queryFilterCount('highlight')) return escapeSwigTag($0);

// Extract language and caption of code blocks
const args = _args.split('=').shift();
let lang, caption;
// Extract language and caption of code blocks
const args = _args.split('=').shift();
let lang, caption;

if (args) {
const match = rAllOptions.exec(args) || rLangCaption.exec(args);
if (args) {
const match = rAllOptions.exec(args) || rLangCaption.exec(args);

if (match) {
lang = match[1];
if (match) {
lang = match[1];

if (match[2]) {
caption = `<span>${match[2]}</span>`;
if (match[2]) {
caption = `<span>${match[2]}</span>`;

if (match[3]) {
caption += `<a href="${match[3]}">${match[4] ? match[4] : 'link'}</a>`;
if (match[3]) {
caption += `<a href="${match[3]}">${match[4] ? match[4] : 'link'}</a>`;
}
}
}
}
}

// PR #3765
if (start.includes('>')) {
// heading of last line is already removed by the top RegExp "rBacktick"
const depth = start.split('>').length - 1;
const regexp = new RegExp(`^([^\\S\\r\\n]*>){0,${depth}}([^\\S\\r\\n]|$)`, 'mg');
content = content.replace(regexp, '');
}

// Since prismjs have better performance, so prismjs should have higher priority.
if (prismCfg.enable) {
if (!prismHighlight) prismHighlight = require('hexo-util').prismHighlight;

const options = {
lineNumber: prismCfg.line_number,
tab: prismCfg.tab_replace,
isPreprocess: prismCfg.preprocess,
lang,
caption
};

content = prismHighlight(content, options);
} else if (hljsCfg.enable) {
if (!highlight) highlight = require('hexo-util').highlight;
// PR #3765
if (start.includes('>')) {
// heading of last line is already removed by the top RegExp "rBacktick"
const depth = start.split('>').length - 1;
const regexp = new RegExp(`^([^\\S\\r\\n]*>){0,${depth}}([^\\S\\r\\n]|$)`, 'mg');
content = content.replace(regexp, '');
}

const options = {
hljs: hljsCfg.hljs,
autoDetect: hljsCfg.auto_detect,
gutter: hljsCfg.line_number,
tab: hljsCfg.tab_replace,
wrap: hljsCfg.wrap,
lang,
languageAttr: hljsCfg.language_attr,
caption
caption,
lines_length: content.split('\n').length
};
// setup line number by inline
_args = _args.replace('=+', '=');

if (options.gutter) {
hljsCfg.first_line_number = hljsCfg.first_line_number || 'always1';
if (hljsCfg.first_line_number === 'inline') {

// setup line number by inline
_args = _args.replace('=+', '=');
options.gutter = _args.includes('=');

// setup firstLineNumber;
options.firstLine = options.gutter ? _args.split('=')[1] || 1 : 0;
}
}

if (Array.isArray(hljsCfg.exclude_languages) && hljsCfg.exclude_languages.includes(options.lang)) {
// Only wrap with <pre><code class="lang"></code></pre>
options.wrap = false;
options.gutter = false;
options.autoDetect = false;
// setup firstLineNumber;
if (_args.includes('=')) {
options.firstLineNumber = _args.split('=')[1] || 1;
}

content = highlight(content, options);
}

return start
+ '<hexoPostRenderCodeBlock>'
+ escapeSwigTag(content)
+ '</hexoPostRenderCodeBlock>'
+ end;
});
}

module.exports = backtickCodeBlock;
content = ctx.execFirstFilterSync('highlight', content, { args: [options] });

return start
+ '<hexoPostRenderCodeBlock>'
+ escapeSwigTag(content)
+ '</hexoPostRenderCodeBlock>'
+ end;
});
};
};
2 changes: 1 addition & 1 deletion lib/plugins/filter/before_post_render/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
module.exports = ctx => {
const { filter } = ctx.extend;

filter.register('before_post_render', require('./backtick_code_block'));
filter.register('before_post_render', require('./backtick_code_block')(ctx));
filter.register('before_post_render', require('./titlecase'));
};
46 changes: 46 additions & 0 deletions lib/plugins/filter/highlight/highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

// Lazy require highlight.js
let highlight;

module.exports = ctx => {
return function highlightFilter(code, options) {
const hljsCfg = ctx.config.highlight || {};
const line_threshold = options.line_threshold || hljsCfg.line_threshold || 0;
const shouldUseLineNumbers = typeof options.line_number === 'undefined' ? hljsCfg.line_number : options.line_number;
const surpassesLineThreshold = options.lines_length > line_threshold;
const gutter = shouldUseLineNumbers && surpassesLineThreshold;
const languageAttr = typeof options.language_attr === 'undefined' ? hljsCfg.language_attr : options.language_attr;

const hljsOptions = {
autoDetect: hljsCfg.auto_detect,
caption: options.caption,
firstLine: options.firstLine,
gutter,
hljs: hljsCfg.hljs,
lang: options.lang,
languageAttr,
mark: options.mark,
tab: hljsCfg.tab_replace,
wrap: hljsCfg.wrap
};
if (hljsCfg.first_line_number === 'inline') {
if (typeof options.firstLineNumber !== 'undefined') {
hljsOptions.firstLine = options.firstLineNumber;
} else {
hljsOptions.gutter = false;
}
}

if (Array.isArray(hljsCfg.exclude_languages) && hljsCfg.exclude_languages.includes(hljsOptions.lang)) {
// Only wrap with <pre><code class="lang"></code></pre>
hljsOptions.wrap = false;
hljsOptions.gutter = false;
hljsOptions.autoDetect = false;
}

if (!highlight) highlight = require('hexo-util').highlight;

return highlight(code, hljsOptions);
};
};
14 changes: 14 additions & 0 deletions lib/plugins/filter/highlight/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

module.exports = ctx => {
const { filter } = ctx.extend;
const hljsCfg = ctx.config.highlight || {};
const prismCfg = ctx.config.prismjs || {};

// Since prismjs have better performance, so prismjs should have higher priority.
if (prismCfg.enable) {
filter.register('highlight', require('./prism')(ctx));
} else if (hljsCfg.enable) {
filter.register('highlight', require('./highlight')(ctx));
}
};
28 changes: 28 additions & 0 deletions lib/plugins/filter/highlight/prism.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

// Lazy require prismjs
let prismHighlight;

module.exports = ctx => {
return function(code, options) {
const prismjsCfg = ctx.config.prismjs || {};
const line_threshold = options.line_threshold || prismjsCfg.line_threshold || 0;
const shouldUseLineNumbers = typeof options.line_number === 'undefined' ? prismjsCfg.line_number : options.line_number;
const surpassesLineThreshold = options.lines_length > line_threshold;
const lineNumber = shouldUseLineNumbers && surpassesLineThreshold;

const prismjsOptions = {
caption: options.caption,
firstLine: options.firstLine,
isPreprocess: prismjsCfg.preprocess,
lang: options.lang,
lineNumber,
mark: options.mark,
tab: prismjsCfg.tab_replace
};

if (!prismHighlight) prismHighlight = require('hexo-util').prismHighlight;

return prismHighlight(code, prismjsOptions);
};
};
1 change: 1 addition & 0 deletions lib/plugins/filter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = ctx => {
require('./before_exit')(ctx);
require('./before_generate')(ctx);
require('./template_locals')(ctx);
require('./highlight')(ctx);

filter.register('new_post_path', require('./new_post_path'));
filter.register('post_permalink', require('./post_permalink'));
Expand Down
Loading