Skip to content

Commit

Permalink
Merge 46021e0 into 40df922
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenjoezhang committed Dec 14, 2022
2 parents 40df922 + 46021e0 commit ffdeaee
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 225 deletions.
3 changes: 2 additions & 1 deletion lib/extend/index.js
Expand Up @@ -5,12 +5,13 @@ const Deployer = require('./deployer');
const Filter = require('./filter');
const Generator = require('./generator');
const Helper = require('./helper');
const Highlight = require('./syntax_highlight');
const Injector = require('./injector');
const Migrator = require('./migrator');
const Processor = require('./processor');
const Renderer = require('./renderer');
const Tag = require('./tag');

module.exports = {
Console, Deployer, Filter, Generator, Helper, Injector, Migrator, Processor, Renderer, Tag
Console, Deployer, Filter, Generator, Helper, Highlight, Injector, Migrator, Processor, Renderer, Tag
};
29 changes: 29 additions & 0 deletions lib/extend/syntax_highlight.js
@@ -0,0 +1,29 @@
'use strict';

class SyntaxHighlight {
constructor() {
this.store = {};
}

register(name, fn) {
if (typeof fn !== 'function') throw new TypeError('fn must be a function');

this.store[name] = fn;
}

query(name) {
return name && this.store[name];
}

exec(name, options) {
const fn = this.store[name];

if (!fn) throw new TypeError(`syntax highlighter ${name} is not registered`);
const ctx = options.context;
const args = options.args || [];

return Reflect.apply(fn, ctx, args);
}
}

module.exports = SyntaxHighlight;
3 changes: 1 addition & 2 deletions lib/hexo/default_config.js
Expand Up @@ -40,8 +40,8 @@ module.exports = {
post_asset_folder: false,
relative_link: false,
future: true,
syntax_highlighter: 'highlight.js',
highlight: {
enable: true,
auto_detect: false,
line_number: true,
tab_replace: '',
Expand All @@ -51,7 +51,6 @@ module.exports = {
hljs: false
},
prismjs: {
enable: false,
preprocess: true,
line_number: true,
tab_replace: ''
Expand Down
4 changes: 3 additions & 1 deletion lib/hexo/index.js
Expand Up @@ -11,7 +11,7 @@ const Module = require('module');
const { runInThisContext } = require('vm');
const { version } = require('../../package.json');
const { logger } = require('hexo-log');
const { Console, Deployer, Filter, Generator, Helper, Injector, Migrator, Processor, Renderer, Tag } = require('../extend');
const { Console, Deployer, Filter, Generator, Helper, Highlight, Injector, Migrator, Processor, Renderer, Tag } = require('../extend');
const Render = require('./render');
const registerModels = require('./register_models');
const Post = require('./post');
Expand Down Expand Up @@ -123,6 +123,7 @@ class Hexo extends EventEmitter {
filter: new Filter(),
generator: new Generator(),
helper: new Helper(),
highlight: new Highlight(),
injector: new Injector(),
migrator: new Migrator(),
processor: new Processor(),
Expand Down Expand Up @@ -224,6 +225,7 @@ class Hexo extends EventEmitter {
require('../plugins/filter')(this);
require('../plugins/generator')(this);
require('../plugins/helper')(this);
require('../plugins/highlight')(this);
require('../plugins/injector')(this);
require('../plugins/processor')(this);
require('../plugins/renderer')(this);
Expand Down
2 changes: 1 addition & 1 deletion lib/hexo/post.js
Expand Up @@ -417,7 +417,7 @@ class Post {
}

const options = data.markdown || {};
if (!config.highlight.enable) options.highlight = null;
if (!config.syntax_highlighter) options.highlight = null;

ctx.log.debug('Rendering post: %s', magenta(source));
// Render with markdown or other renderer
Expand Down
132 changes: 47 additions & 85 deletions lib/plugins/filter/before_post_render/backtick_code_block.js
@@ -1,111 +1,73 @@
'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.extend.highlight.query(ctx.config.syntax_highlighter)) 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.extend.highlight.query(ctx.config.syntax_highlighter)) 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.extend.highlight.exec(ctx.config.syntax_highlighter, {
context: ctx,
args: [content, options]
});

return start
+ '<hexoPostRenderCodeBlock>'
+ escapeSwigTag(content)
+ '</hexoPostRenderCodeBlock>'
+ end;
});
};
};
2 changes: 1 addition & 1 deletion lib/plugins/filter/before_post_render/index.js
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'));
};
44 changes: 44 additions & 0 deletions lib/plugins/highlight/highlight.js
@@ -0,0 +1,44 @@
'use strict';

// Lazy require highlight.js
let highlight;

module.exports = function highlightFilter(code, options) {
const hljsCfg = this.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);
};
8 changes: 8 additions & 0 deletions lib/plugins/highlight/index.js
@@ -0,0 +1,8 @@
'use strict';

module.exports = ctx => {
const { highlight } = ctx.extend;

highlight.register('highlight.js', require('./highlight'));
highlight.register('prismjs', require('./prism'));
};
26 changes: 26 additions & 0 deletions lib/plugins/highlight/prism.js
@@ -0,0 +1,26 @@
'use strict';

// Lazy require prismjs
let prismHighlight;

module.exports = function(code, options) {
const prismjsCfg = this.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);
};

0 comments on commit ffdeaee

Please sign in to comment.