hexo-renderer-marked
Advanced tools
Comparing version 3.1.0 to 3.2.0
@@ -15,2 +15,3 @@ /* global hexo */ | ||
autolink: true, | ||
mangle: true, | ||
sanitizeUrl: false, | ||
@@ -28,2 +29,4 @@ headerIds: true, | ||
renderer.disableNunjucks = Boolean(hexo.config.marked.disableNunjucks); | ||
hexo.extend.renderer.register('md', 'html', renderer, true); | ||
@@ -30,0 +33,0 @@ hexo.extend.renderer.register('markdown', 'html', renderer, true); |
'use strict'; | ||
const marked = require('marked'); | ||
const { escape } = require('marked/src/helpers'); | ||
const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); | ||
const MarkedRenderer = marked.Renderer; | ||
const MarkedTokenizer = marked.Tokenizer; | ||
@@ -42,5 +44,4 @@ const anchorId = (str, transformOption) => { | ||
// Support AutoLink option | ||
link(href, title, text) { | ||
const { autolink, external_link, sanitizeUrl } = this.options; | ||
const { external_link, sanitizeUrl } = this.options; | ||
const { url: urlCfg } = this.hexo.config; | ||
@@ -53,7 +54,13 @@ | ||
} | ||
if (!autolink && href === text && title == null) { | ||
return href; | ||
let out = '<a href="'; | ||
try { | ||
out += encodeURL(href); | ||
} catch (e) { | ||
out += href; | ||
} | ||
let out = `<a href="${encodeURL(href)}"`; | ||
out += '"'; | ||
if (title) { | ||
@@ -122,2 +129,94 @@ out += ` title="${title}"`; | ||
// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Lexer.js#L8-L24 | ||
const smartypants = (str, quotes) => { | ||
const [openDbl, closeDbl, openSgl, closeSgl] = typeof quotes === 'string' && quotes.length === 4 | ||
? quotes | ||
: ['\u201c', '\u201d', '\u2018', '\u2019']; | ||
return str | ||
// em-dashes | ||
.replace(/---/g, '\u2014') | ||
// en-dashes | ||
.replace(/--/g, '\u2013') | ||
// opening singles | ||
.replace(/(^|[-\u2014/([{"\s])'/g, '$1' + openSgl) | ||
// closing singles & apostrophes | ||
.replace(/'/g, closeSgl) | ||
// opening doubles | ||
.replace(/(^|[-\u2014/([{\u2018\s])"/g, '$1' + openDbl) | ||
// closing doubles | ||
.replace(/"/g, closeDbl) | ||
// ellipses | ||
.replace(/\.{3}/g, '\u2026'); | ||
}; | ||
class Tokenizer extends MarkedTokenizer { | ||
// Support AutoLink option | ||
// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Tokenizer.js#L606-L641 | ||
url(src, mangle) { | ||
const { options, rules } = this; | ||
const { mangle: isMangle, autolink } = options; | ||
if (!autolink) return; | ||
const cap = rules.inline.url.exec(src); | ||
if (cap) { | ||
let text, href; | ||
if (cap[2] === '@') { | ||
text = escape(isMangle ? mangle(cap[0]) : cap[0]); | ||
href = 'mailto:' + text; | ||
} else { | ||
// do extended autolink path validation | ||
let prevCapZero; | ||
do { | ||
prevCapZero = cap[0]; | ||
cap[0] = rules.inline._backpedal.exec(cap[0])[0]; | ||
} while (prevCapZero !== cap[0]); | ||
text = escape(cap[0]); | ||
if (cap[1] === 'www.') { | ||
href = 'http://' + text; | ||
} else { | ||
href = text; | ||
} | ||
} | ||
return { | ||
type: 'link', | ||
raw: cap[0], | ||
text, | ||
href, | ||
tokens: [ | ||
{ | ||
type: 'text', | ||
raw: text, | ||
text | ||
} | ||
] | ||
}; | ||
} | ||
} | ||
// Override smartypants | ||
inlineText(src, inRawBlock) { | ||
const { options, rules } = this; | ||
const { quotes, smartypants: isSmarty } = options; | ||
// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Tokenizer.js#L643-L658 | ||
const cap = rules.inline.text.exec(src); | ||
if (cap) { | ||
let text; | ||
if (inRawBlock) { | ||
text = cap[0]; | ||
} else { | ||
text = escape(isSmarty ? smartypants(cap[0], quotes) : cap[0]); | ||
} | ||
return { | ||
type: 'text', | ||
raw: cap[0], | ||
text | ||
}; | ||
} | ||
} | ||
} | ||
module.exports = function(data, options) { | ||
@@ -132,2 +231,5 @@ const { post_asset_folder, marked: markedCfg, source_dir } = this.config; | ||
const tokenizer = new Tokenizer(); | ||
this.execFilterSync('marked:tokenizer', tokenizer, {context: this}); | ||
let postPath = ''; | ||
@@ -143,4 +245,5 @@ if (path && post_asset_folder && prependRoot && postAsset) { | ||
return marked(text, Object.assign({ | ||
renderer | ||
renderer, | ||
tokenizer | ||
}, markedCfg, options, { postPath })); | ||
}; |
{ | ||
"name": "hexo-renderer-marked", | ||
"version": "3.1.0", | ||
"version": "3.2.0", | ||
"description": "Markdown renderer plugin for Hexo", | ||
@@ -9,3 +9,3 @@ "main": "index", | ||
"test": "mocha test/index.js", | ||
"test-cov": "nyc npm run test" | ||
"test-cov": "nyc --reporter=lcovonly npm run test" | ||
}, | ||
@@ -12,0 +12,0 @@ "directories": { |
@@ -30,4 +30,6 @@ # hexo-renderer-marked | ||
smartypants: true | ||
quotes: '“”‘’' | ||
modifyAnchors: 0 | ||
autolink: true | ||
mangle: true | ||
sanitizeUrl: false | ||
@@ -42,2 +44,3 @@ headerIds: true | ||
nofollow: false | ||
disableNunjucks: false | ||
``` | ||
@@ -50,4 +53,11 @@ | ||
- **smartypants** - Use "smart" typograhic punctuation for things like quotes and dashes. | ||
- **quotes** - Defines the double and single quotes used for substituting regular quotes if **smartypants** is enabled. | ||
* Example: '«»“”' | ||
* "double" will be turned into «single» | ||
* 'single' will be turned into “single” | ||
* Both double and single quotes substitution must be specified, otherwise it will be silently ignored. | ||
- **modifyAnchors** - Transform the anchorIds into lower case (`1`) or upper case (`2`). | ||
- **autolink** - Enable autolink for URLs. E.g. `https://hexo.io` will become `<a href="https://hexo.io">https://hexo.io</a>`. | ||
- **mangle** - Escape autolinked email address with HTML character references. | ||
* This is to obscure email address from _basic_ crawler used by spam bot, while still readable to web browsers. | ||
- **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`. | ||
@@ -65,3 +75,3 @@ - **headerIds** - Insert header id, e.g. `<h1 id="value">text</h1>`. Useful for inserting anchor link to each paragraph with a heading. | ||
* `` becomes `<img src="/2020/01/02/foo/image.jpg">` | ||
* Requires `prependRoot:` to be enabled. | ||
* Requires **prependRoot** to be enabled. | ||
- **external_link** | ||
@@ -73,3 +83,6 @@ * **enable** - Open external links in a new tab. | ||
- Example: `[foo](http://bar.com)` becomes `<a href="http://bar.com" rel="noopener external nofollow noreferrer">foo</a>` | ||
- **disableNunjucks**: If true, Nunjucks tags `{{ }}` or `{% %}` (usually used by [tag plugins](https://hexo.io/docs/tag-plugins)) will not be rendered. | ||
For more options, see [Marked](https://marked.js.org/using_advanced#options). Due to the customizations implemented by this plugin, some of the Marked's options may not work as expected. Feel free to raise an [issue](https://github.com/hexojs/hexo-renderer-marked/issues) to us for clarification. | ||
## Extras | ||
@@ -141,6 +154,49 @@ | ||
Notice `renderer.heading = function (text, level) {` corresponds to [this line](https://github.com/hexojs/hexo-renderer-marked/blob/a93ebeb1e8cc11e754630c0a1506da9a1489b2b0/lib/renderer.js#L21). Refer to [renderer.js](https://github.com/hexojs/hexo-renderer-marked/blob/master/lib/renderer.js) on how this plugin overrides the default methods. For other methods not covered by this plugin, refer to marked's [documentation](https://marked.js.org/#/USING_PRO.md). | ||
Notice `renderer.heading = function (text, level) {` corresponds to [this line](https://github.com/hexojs/hexo-renderer-marked/blob/a93ebeb1e8cc11e754630c0a1506da9a1489b2b0/lib/renderer.js#L21). Refer to [renderer.js](https://github.com/hexojs/hexo-renderer-marked/blob/master/lib/renderer.js) on how this plugin overrides the default methods. For other methods not covered by this plugin, refer to marked's [documentation](https://marked.js.org/using_pro#renderer). | ||
#### Tokenizer | ||
It is also possible to customize the [tokenizer](https://marked.js.org/using_pro#tokenizer). | ||
``` js | ||
const { escape } = require('marked/src/helpers'); | ||
// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Lexer.js#L8-L24 | ||
// Replace dashes only | ||
const smartypants = (str) => { | ||
return str | ||
// em-dashes | ||
.replace(/---/g, '\u2014') | ||
// en-dashes | ||
.replace(/--/g, '\u2013') | ||
}; | ||
hexo.extend.filter.register('marked:tokenizer', function(tokenizer) { | ||
const { smartypants: isSmarty } = this.config.marked; | ||
tokenizer.inlineText = function(src, inRawBlock) { | ||
const { rules } = this; | ||
// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Tokenizer.js#L643-L658 | ||
const cap = rules.inline.text.exec(src); | ||
if (cap) { | ||
let text; | ||
if (inRawBlock) { | ||
text = cap[0]; | ||
} else { | ||
text = escape(isSmarty ? smartypants(cap[0], quotes) : cap[0]); | ||
} | ||
return { | ||
// `type` value is a corresponding renderer method | ||
// https://marked.js.org/using_pro#inline-level-renderer-methods | ||
type: 'text', | ||
raw: cap[0], | ||
text | ||
}; | ||
} | ||
} | ||
}); | ||
``` | ||
[Markdown]: https://daringfireball.net/projects/markdown/ | ||
[marked]: https://github.com/chjj/marked | ||
[PHP Markdown Extra]: https://michelf.ca/projects/php-markdown/extra/#def-list |
18117
241
197