-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor highlight: add extend api for syntax highlight (#5095)
- Loading branch information
1 parent
40df922
commit d485ebd
Showing
17 changed files
with
203 additions
and
225 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 47 additions & 85 deletions
132
lib/plugins/filter/before_post_render/backtick_code_block.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
'use strict'; | ||
|
||
module.exports = ctx => { | ||
const { highlight } = ctx.extend; | ||
|
||
highlight.register('highlight.js', require('./highlight')); | ||
highlight.register('prismjs', require('./prism')); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}; |
Oops, something went wrong.