hexo-renderer-marked
Advanced tools
Comparing version 2.0.0 to 3.0.0
13
index.js
@@ -5,3 +5,3 @@ /* global hexo */ | ||
var renderer = require('./lib/renderer'); | ||
const renderer = require('./lib/renderer'); | ||
@@ -14,6 +14,13 @@ hexo.config.marked = Object.assign({ | ||
smartypants: true, | ||
modifyAnchors: '', | ||
modifyAnchors: 0, | ||
autolink: true, | ||
sanitizeUrl: false, | ||
headerIds: true | ||
headerIds: true, | ||
// TODO: enable prependRoot by default in v3 | ||
prependRoot: false, | ||
external_link: { | ||
enable: false, | ||
exclude: [], | ||
nofollow: false | ||
} | ||
}, hexo.config.marked); | ||
@@ -20,0 +27,0 @@ |
'use strict'; | ||
const marked = require('marked'); | ||
const stripIndent = require('strip-indent'); | ||
const { stripHTML, highlight, slugize } = require('hexo-util'); | ||
const { encodeURL, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util'); | ||
const MarkedRenderer = marked.Renderer; | ||
const { parse } = require('url'); | ||
function Renderer() { | ||
MarkedRenderer.apply(this); | ||
const anchorId = (str, transformOption) => { | ||
return slugize(str.trim(), {transform: transformOption}); | ||
}; | ||
this._headingId = {}; | ||
} | ||
class Renderer extends MarkedRenderer { | ||
constructor() { | ||
super(); | ||
this._headingId = {}; | ||
} | ||
require('util').inherits(Renderer, MarkedRenderer); | ||
// Add id attribute to headings | ||
heading(text, level) { | ||
if (!this.options.headerIds) { | ||
return `<h${level}>${text}</h${level}>`; | ||
} | ||
const transformOption = this.options.modifyAnchors; | ||
let id = anchorId(stripHTML(text), transformOption); | ||
const headingId = this._headingId; | ||
// Add id attribute to headings | ||
Renderer.prototype.heading = function(text, level) { | ||
if (!this.options.headerIds) { | ||
return `<h${level}>${text}</h${level}>`; | ||
} | ||
// Add a number after id if repeated | ||
if (headingId[id]) { | ||
id += `-${headingId[id]++}`; | ||
} else { | ||
headingId[id] = 1; | ||
} | ||
const transformOption = this.options.modifyAnchors; | ||
let id = anchorId(stripHTML(text), transformOption); | ||
const headingId = this._headingId; | ||
// Add a number after id if repeated | ||
if (headingId[id]) { | ||
id += `-${headingId[id]++}`; | ||
} else { | ||
headingId[id] = 1; | ||
// add headerlink | ||
return `<h${level} id="${id}"><a href="#${id}" class="headerlink" title="${stripHTML(text)}"></a>${text}</h${level}>`; | ||
} | ||
// add headerlink | ||
return `<h${level} id="${id}"><a href="#${id}" class="headerlink" title="${stripHTML(text)}"></a>${text}</h${level}>`; | ||
}; | ||
function anchorId(str, transformOption) { | ||
return slugize(str.trim(), {transform: transformOption}); | ||
} | ||
// Support AutoLink option | ||
Renderer.prototype.link = function(href, title, text) { | ||
if (this.options.sanitizeUrl) { | ||
let prot; | ||
try { | ||
prot = decodeURIComponent(unescape(href)) | ||
.replace(/[^\w:]/g, '') | ||
.toLowerCase(); | ||
} catch (e) { | ||
return ''; | ||
// Support AutoLink option | ||
link(href, title, text) { | ||
const { options } = this; | ||
const { external_link } = options; | ||
if (options.sanitizeUrl) { | ||
if (href.startsWith('javascript:') || href.startsWith('vbscript:') || href.startsWith('data:')) { | ||
href = ''; | ||
} | ||
} | ||
if (!options.autolink && href === text && title == null) { | ||
return href; | ||
} | ||
if (prot.startsWith('javascript:') || prot.startsWith('vbscript:') || prot.startsWith('data:')) { | ||
return ''; | ||
let out = `<a href="${encodeURL(href)}"`; | ||
if (title) { | ||
out += ` title="${title}"`; | ||
} | ||
} | ||
if (external_link) { | ||
const target = ' target="_blank"'; | ||
const noopener = ' rel="noopener"'; | ||
const nofollowTag = ' rel="noopener external nofollow noreferrer"'; | ||
if (isExternalLink(href, options.config.url, external_link.exclude)) { | ||
if (external_link.enable && external_link.nofollow) { | ||
out += target + nofollowTag; | ||
} else if (external_link.enable) { | ||
out += target + noopener; | ||
} else if (external_link.nofollow) { | ||
out += nofollowTag; | ||
} | ||
} | ||
} | ||
if (!this.options.autolink && href === text && title == null) { | ||
return href; | ||
out += `>${text}</a>`; | ||
return out; | ||
} | ||
let out = `<a href="${href}"`; | ||
if (title) { | ||
out += ` title="${title}"`; | ||
// Support Basic Description Lists | ||
paragraph(text) { | ||
const dlTest = /(?:^|\s)(\S.+)<br>:\s+(\S.+)/; | ||
const dl = '<dl><dt>$1</dt><dd>$2</dd></dl>'; | ||
if (dlTest.test(text)) { | ||
return text.replace(dlTest, dl); | ||
} | ||
return `<p>${text}</p>\n`; | ||
} | ||
out += `>${text}</a>`; | ||
return out; | ||
}; | ||
// Prepend root to image path | ||
image(href, title, text) { | ||
const { options } = this; | ||
// Support Basic Description Lists | ||
Renderer.prototype.paragraph = text => { | ||
const dlTest = /(?:^|\s)(\S.+)<br>:\s+(\S.+)/; | ||
if (!parse(href).hostname && !options.config.relative_link | ||
&& options.prependRoot) { | ||
href = url_for.call(options, href); | ||
} | ||
const dl = '<dl><dt>$1</dt><dd>$2</dd></dl>'; | ||
let out = `<img src="${encodeURL(href)}"`; | ||
if (text) out += ` alt="${text}"`; | ||
if (title) out += ` title="${title}"`; | ||
if (dlTest.test(text)) { | ||
return text.replace(dlTest, dl); | ||
out += '>'; | ||
return out; | ||
} | ||
} | ||
return `<p>${text}</p>\n`; | ||
}; | ||
marked.setOptions({ | ||
langPrefix: '', | ||
highlight(code, lang) { | ||
return highlight(stripIndent(code), { | ||
lang, | ||
gutter: false, | ||
wrap: false | ||
}); | ||
} | ||
langPrefix: '' | ||
}); | ||
module.exports = function(data, options) { | ||
const siteCfg = Object.assign({}, { | ||
config: { | ||
url: this.config.url, | ||
root: this.config.root, | ||
relative_link: this.config.relative_link | ||
} | ||
}); | ||
// exec filter to extend renderer. | ||
const renderer = new Renderer(); | ||
this.execFilterSync('marked:renderer', renderer, {context: this}); | ||
return marked(data.text, Object.assign({ | ||
renderer: new Renderer() | ||
}, this.config.marked, options)); | ||
renderer | ||
}, this.config.marked, options, siteCfg)); | ||
}; |
{ | ||
"name": "hexo-renderer-marked", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "Markdown renderer plugin for Hexo", | ||
@@ -31,17 +31,16 @@ "main": "index", | ||
"dependencies": { | ||
"hexo-util": "1.0.0", | ||
"marked": "^0.7.0", | ||
"strip-indent": "^3.0.0" | ||
"hexo-util": "^2.1.0", | ||
"marked": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"eslint": "^6.0.1", | ||
"babel-eslint": "^10.0.1", | ||
"eslint-config-hexo": "^3.0.0", | ||
"mocha": "^6.1.4", | ||
"nyc": "^14.1.1" | ||
"eslint": "^7.2.0", | ||
"eslint-config-hexo": "^4.1.0", | ||
"hexo": "^4.2.0", | ||
"mocha": "^8.0.1", | ||
"nyc": "^15.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=8.6.0" | ||
"node": ">=10.13.0" | ||
} | ||
} |
@@ -30,6 +30,11 @@ # hexo-renderer-marked | ||
smartypants: true | ||
modifyAnchors: '' | ||
modifyAnchors: 0 | ||
autolink: true | ||
sanitizeUrl: false | ||
headerIds: true | ||
prependRoot: false | ||
external_link: | ||
enable: false | ||
exclude: [] | ||
nofollow: false | ||
``` | ||
@@ -42,6 +47,18 @@ | ||
- **smartypants** - Use "smart" typograhic punctuation for things like quotes and dashes. | ||
- **modifyAnchors** - Use for transform anchorIds. if `1` to lowerCase and if `2` to upperCase. **Must be integer**. | ||
- **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>`. | ||
- **sanitizeUrl** - Remove URLs that start with `javascript:`, `vbscript:` and `data:`. | ||
- **headerIds** - Insert header id, e.g. `<h1 id="value">text</h1>`. Useful for inserting anchor link to each paragraph with a heading. | ||
- **prependRoot** - Prepend root value to (internal) image path. | ||
* Example `_config.yml`: | ||
``` yml | ||
root: /blog/ | ||
``` | ||
* `` becomes `<img src="/blog/path/to/image.jpg" alt="text">` | ||
- **external_link** | ||
* **enable** - Open external links in a new tab. | ||
* **exclude** - Exclude hostname. Specify subdomain when applicable, including `www`. | ||
- Example: `[foo](http://bar.com)` becomes `<a href="http://bar.com" target="_blank" rel="noopener">foo</a>` | ||
* **nofollow** - Add `rel="noopener external nofollow noreferrer"` to all external links for security, privacy and SEO. [Read more](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types). _This can be enabled regardless of `external_link.enable`_ | ||
- Example: `[foo](http://bar.com)` becomes `<a href="http://bar.com" rel="noopener external nofollow noreferrer">foo</a>` | ||
@@ -61,3 +78,3 @@ ## Extras | ||
will generate this html: | ||
will generate this HTML: | ||
@@ -92,4 +109,29 @@ ```html | ||
### Extensibility | ||
This plugin overrides some default behaviours of how [marked] plugin renders the markdown into html, to integrate with the Hexo ecosystem. It is possible to override this plugin too, without resorting to forking the whole thing. | ||
For example, to override how heading like `# heading text` is rendered: | ||
``` js | ||
hexo.extend.filter.register('marked:renderer', function(renderer) { | ||
const { config } = this; // Skip this line if you don't need user config from _config.yml | ||
renderer.heading = function(text, level) { | ||
// Default behaviour | ||
// return `<h${level}>${text}</h${level}>`; | ||
// outputs <h1>heading text</h1> | ||
// If you want to insert custom class name | ||
return `<h${level} class="headerlink">${text}</h${level}>`; | ||
// outputs <h1 class="headerlink">heading text</h1> | ||
} | ||
}) | ||
``` | ||
Save the file in "scripts/" folder and run Hexo as usual. | ||
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). | ||
[Markdown]: https://daringfireball.net/projects/markdown/ | ||
[marked]: https://github.com/chjj/marked | ||
[PHP Markdown Extra]: https://michelf.ca/projects/php-markdown/extra/#def-list |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
11662
2
132
134
1
+ Addedcamel-case@4.1.2(transitive)
+ Addedcross-spawn@7.0.6(transitive)
+ Addeddeepmerge@4.3.1(transitive)
+ Addeddom-serializer@1.4.1(transitive)
+ Addeddomelementtype@2.3.0(transitive)
+ Addeddomhandler@4.3.1(transitive)
+ Addeddomutils@2.8.0(transitive)
+ Addedentities@2.2.03.0.1(transitive)
+ Addedhexo-util@2.7.0(transitive)
+ Addedhighlight.js@11.11.1(transitive)
+ Addedhtmlparser2@7.2.0(transitive)
+ Addedlower-case@2.0.2(transitive)
+ Addedmarked@1.2.9(transitive)
+ Addedno-case@3.0.4(transitive)
+ Addedpascal-case@3.1.2(transitive)
+ Addedpath-key@3.1.1(transitive)
+ Addedprismjs@1.29.0(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedtslib@2.8.1(transitive)
+ Addedwhich@2.0.2(transitive)
- Removedstrip-indent@^3.0.0
- Removedcamel-case@3.0.0(transitive)
- Removedcross-spawn@6.0.6(transitive)
- Removedhexo-util@1.0.0(transitive)
- Removedhighlight.js@9.18.5(transitive)
- Removedhtml-entities@1.4.0(transitive)
- Removedlower-case@1.1.4(transitive)
- Removedmarked@0.7.0(transitive)
- Removednice-try@1.0.5(transitive)
- Removedno-case@2.3.2(transitive)
- Removedpath-key@2.0.1(transitive)
- Removedsemver@5.7.2(transitive)
- Removedshebang-command@1.2.0(transitive)
- Removedshebang-regex@1.0.0(transitive)
- Removedstriptags@3.2.0(transitive)
- Removedupper-case@1.1.3(transitive)
- Removedwhich@1.3.1(transitive)
Updatedhexo-util@^2.1.0
Updatedmarked@^1.0.0