Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(deps): adapt to marked@12 #280

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 48 additions & 31 deletions lib/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ let JSDOM,
createDOMPurify;

const { encodeURL, slugize, stripHTML, url_for, isExternalLink, escapeHTML: escape, unescapeHTML: unescape } = require('hexo-util');
const MarkedRenderer = marked.Renderer;
const MarkedTokenizer = marked.Tokenizer;
const { basename, dirname, extname, join } = require('path').posix;
const rATag = /<a(?:\s+?|\s+?[^<>]+\s+?)?href=["'](?:#)([^<>"']+)["'][^<>]*>/i;
const rDlSyntax = /(?:^|\s)(\S.+)<br>:\s+(\S.+)/;
Expand All @@ -16,17 +14,27 @@ const anchorId = (str, transformOption) => {
return slugize(stripHTML(unescape(str)).trim(), { transform: transformOption });
};

class Renderer extends MarkedRenderer {
constructor(hexo) {
super();
this._headingId = {};
this.hexo = hexo;
function mangleEmail(text) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but why not use the plugin?
https://www.npmjs.com/package/marked-mangle

Copy link
Member Author

@stevenjoezhang stevenjoezhang Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the official plugin is also an option, but I think using our custom implementation would be more convenient. The official marked plugin is implemented inside the tokenizer, while we have already customized links in the renderer (for example, modifying links that start with javascript:). This way, we can simply add a handling logic for mailto: links in a similar place.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we could re-implement all Hexo specific feature using marked extension API.

We could even implement the old filter API on top of the marked extension API.

let out = '';
let i,
ch;

const l = text.length;
for (i = 0; i < l; i++) {
ch = text.charCodeAt(i);
if (Math.random() > 0.5) {
ch = 'x' + ch.toString(16);
}
out += '&#' + ch + ';';
}

return out;
}

const renderer = {
// Add id attribute to headings
heading(text, level) {
const { anchorAlias, headerIds, modifyAnchors } = this.options;
const { _headingId } = this;
const { anchorAlias, headerIds, modifyAnchors, _headingId } = this.options;

if (!headerIds) {
return `<h${level}>${text}</h${level}>`;
Expand Down Expand Up @@ -57,17 +65,25 @@ class Renderer extends MarkedRenderer {

// add headerlink
return `<h${level} id="${id}"><a href="#${id}" class="headerlink" title="${stripHTML(text)}"></a>${text}</h${level}>`;
}
},

link(href, title, text) {
const { external_link, sanitizeUrl } = this.options;
const { url: urlCfg } = this.hexo.config;
const { external_link, sanitizeUrl, hexo, mangle } = this.options;
const { url: urlCfg } = hexo.config;

if (sanitizeUrl) {
if (href.startsWith('javascript:') || href.startsWith('vbscript:') || href.startsWith('data:')) {
href = '';
}
}
if (mangle) {
if (href.startsWith('mailto:')) {
const email = href.substring(7);
const mangledEmail = mangleEmail(email);

href = `mailto:${mangledEmail}`;
}
}

let out = '<a href="';

Expand Down Expand Up @@ -99,7 +115,7 @@ class Renderer extends MarkedRenderer {

out += `>${text}</a>`;
return out;
}
},

// Support Basic Description Lists
paragraph(text) {
Expand All @@ -112,11 +128,12 @@ class Renderer extends MarkedRenderer {
}

return `<p>${text}</p>\n`;
}
},

// Prepend root to image path
image(href, title, text) {
const { hexo, options } = this;
const { options } = this;
const { hexo } = options;
const { relative_link } = hexo.config;
const { lazyload, figcaption, prependRoot, postPath } = options;

Expand All @@ -142,11 +159,7 @@ class Renderer extends MarkedRenderer {
}
return out;
}
}

marked.setOptions({
langPrefix: ''
});
};

// https://github.com/markedjs/marked/blob/b6773fca412c339e0cedd56b63f9fa1583cfd372/src/Lexer.js#L8-L24
const smartypants = (str, quotes) => {
Expand All @@ -171,15 +184,15 @@ const smartypants = (str, quotes) => {
.replace(/\.{3}/g, '\u2026');
};

class Tokenizer extends MarkedTokenizer {
const tokenizer = {
// Support AutoLink option
url(src, mangle) {
const { options } = this;
const { autolink } = options;
url(src) {
const { autolink } = this.options;

if (!autolink) return;
return super.url(src, mangle);
}
// return false to use original url tokenizer
return false;
},

// Override smartypants
inlineText(src) {
Expand All @@ -202,7 +215,7 @@ class Tokenizer extends MarkedTokenizer {
};
}
}
}
};

module.exports = function(data, options) {
const { post_asset_folder, marked: markedCfg, source_dir } = this.config;
Expand All @@ -213,10 +226,9 @@ module.exports = function(data, options) {
this.execFilterSync('marked:use', marked.use, { context: this });

// exec filter to extend renderer.
const renderer = new Renderer(this);
this.execFilterSync('marked:renderer', renderer, { context: this });

const tokenizer = new Tokenizer();
// exec filter to extend tokenizer
this.execFilterSync('marked:tokenizer', tokenizer, { context: this });

const extensions = [];
Expand Down Expand Up @@ -250,8 +262,13 @@ module.exports = function(data, options) {
}
sanitizer = function(html) { return DOMPurify.sanitize(html, param); };
}
return sanitizer(marked(text, Object.assign({

marked.use({
renderer,
tokenizer
}, markedCfg, options, { postPath })));
});
return sanitizer(marked.parse(text, Object.assign({
// headerIds was removed in marked v8.0.0, but we still need it
headerIds: true
}, markedCfg, options, { postPath, hexo: this, _headingId: {} })));
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"dompurify": "^3.0.3",
"hexo-util": "^3.1.0",
"jsdom": "^20.0.1",
"marked": "^4.3.0"
"marked": "^12.0.1"
},
"devDependencies": {
"c8": "^8.0.0",
Expand Down
4 changes: 3 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const { sep } = require('path');
describe('Marked renderer', () => {
const hexo = new Hexo(__dirname, {silent: true});
const defaultCfg = JSON.parse(JSON.stringify(Object.assign(hexo.config, {
marked: {}
marked: {
mangle: true
}
})));

before(async () => {
Expand Down
Loading