2018-10-16 05:28:42 +00:00
|
|
|
/**
|
|
|
|
* Helper functions that override Hexo built-in helpers.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* <%- _list_archives() %>
|
|
|
|
* <%- _list_categories() %>
|
|
|
|
* <%- _list_tags() %>
|
2019-08-16 05:03:19 +00:00
|
|
|
* <%- _toc(content) %>
|
|
|
|
* <%- _js(url, defer, async) %>
|
|
|
|
* <%- _css(url) %>
|
|
|
|
* <%- _partial(url) %>
|
2018-10-16 05:28:42 +00:00
|
|
|
*/
|
|
|
|
const cheerio = require('cheerio');
|
2019-08-16 05:03:19 +00:00
|
|
|
const { existsSync } = require('fs');
|
|
|
|
const { relative, dirname, join, extname } = require('path');
|
2019-12-20 23:06:20 +00:00
|
|
|
const { LRUMap } = require('../utils/lru');
|
2018-10-16 05:28:42 +00:00
|
|
|
|
2019-08-15 04:05:21 +00:00
|
|
|
const __archives = [];
|
|
|
|
const __categories = [];
|
|
|
|
const __tags = [];
|
|
|
|
|
2019-12-20 23:06:20 +00:00
|
|
|
const __fragmentCache = new LRUMap(20);
|
|
|
|
|
2018-10-16 05:28:42 +00:00
|
|
|
module.exports = function (hexo) {
|
|
|
|
hexo.extend.helper.register('_list_archives', function () {
|
2019-08-15 04:05:21 +00:00
|
|
|
if (__archives.length) {
|
|
|
|
return __archives;
|
|
|
|
}
|
2018-10-16 05:28:42 +00:00
|
|
|
const $ = cheerio.load(this.list_archives(), { decodeEntities: false });
|
|
|
|
$('.archive-list-item').each(function () {
|
2019-08-15 04:05:21 +00:00
|
|
|
__archives.push({
|
2018-10-16 05:28:42 +00:00
|
|
|
url: $(this).find('.archive-list-link').attr('href'),
|
|
|
|
name: $(this).find('.archive-list-link').text(),
|
|
|
|
count: $(this).find('.archive-list-count').text()
|
|
|
|
});
|
|
|
|
});
|
2019-08-15 04:05:21 +00:00
|
|
|
return __archives;
|
2018-10-16 05:28:42 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
hexo.extend.helper.register('_list_categories', function () {
|
2019-08-15 04:05:21 +00:00
|
|
|
if (__categories.length) {
|
|
|
|
return __categories;
|
|
|
|
}
|
2018-10-16 05:28:42 +00:00
|
|
|
const $ = cheerio.load(this.list_categories({ depth: 2 }), { decodeEntities: false });
|
|
|
|
function traverse(root) {
|
|
|
|
const categories = [];
|
|
|
|
root.find('> .category-list-item').each(function () {
|
|
|
|
const category = {
|
|
|
|
url: $(this).find('> .category-list-link').attr('href'),
|
|
|
|
name: $(this).find('> .category-list-link').text(),
|
|
|
|
count: $(this).find('> .category-list-count').text()
|
|
|
|
};
|
|
|
|
if ($(this).find('> .category-list-child').length) {
|
|
|
|
category['children'] = traverse($(this).find('> .category-list-child'));
|
|
|
|
}
|
|
|
|
categories.push(category);
|
|
|
|
});
|
|
|
|
return categories;
|
|
|
|
}
|
2019-08-15 04:05:21 +00:00
|
|
|
__categories.push(...traverse($('.category-list')));
|
|
|
|
return __categories;
|
2018-10-16 05:28:42 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
hexo.extend.helper.register('_list_tags', function () {
|
2019-08-15 04:05:21 +00:00
|
|
|
if (__tags.length) {
|
|
|
|
return __tags;
|
|
|
|
}
|
2018-10-16 05:28:42 +00:00
|
|
|
const $ = cheerio.load(this.list_tags(), { decodeEntities: false });
|
|
|
|
$('.tag-list-item').each(function () {
|
2019-08-15 04:05:21 +00:00
|
|
|
__tags.push({
|
2018-10-16 05:28:42 +00:00
|
|
|
url: $(this).find('.tag-list-link').attr('href'),
|
|
|
|
name: $(this).find('.tag-list-link').text(),
|
|
|
|
count: $(this).find('.tag-list-count').text()
|
|
|
|
});
|
|
|
|
});
|
2019-08-15 04:05:21 +00:00
|
|
|
return __tags;
|
2018-10-16 05:28:42 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Export a tree of headings of an article
|
|
|
|
* {
|
|
|
|
* "1": {
|
|
|
|
* "id": "How-to-enable-table-of-content-for-a-post",
|
|
|
|
* "index": "1"
|
|
|
|
* },
|
|
|
|
* "2": {
|
|
|
|
* "1": {
|
|
|
|
* "1": {
|
|
|
|
* "id": "Third-level-title",
|
|
|
|
* "index": "2.1.1"
|
|
|
|
* },
|
|
|
|
* "id": "Second-level-title",
|
|
|
|
* "index": "2.1"
|
|
|
|
* },
|
|
|
|
* "2": {
|
|
|
|
* "id": "Another-second-level-title",
|
|
|
|
* "index": "2.2"
|
|
|
|
* },
|
|
|
|
* "id": "First-level-title",
|
|
|
|
* "index": "2"
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
hexo.extend.helper.register('_toc', (content) => {
|
|
|
|
const $ = cheerio.load(content, { decodeEntities: false });
|
|
|
|
const toc = {};
|
|
|
|
const levels = [0, 0, 0];
|
|
|
|
// Get top 3 headings that are present in the content
|
|
|
|
const tags = [1, 2, 3, 4, 5, 6].map(i => 'h' + i).filter(h => $(h).length > 0).slice(0, 3);
|
|
|
|
if (tags.length === 0) {
|
|
|
|
return toc;
|
|
|
|
}
|
|
|
|
$(tags.join(',')).each(function () {
|
|
|
|
const level = tags.indexOf(this.name);
|
|
|
|
const id = $(this).attr('id');
|
2018-10-26 04:44:21 +00:00
|
|
|
const text = $(this).text();
|
2018-10-16 05:28:42 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < levels.length; i++) {
|
|
|
|
if (i > level) {
|
|
|
|
levels[i] = 0;
|
|
|
|
} else if (i < level) {
|
|
|
|
if (levels[i] === 0) {
|
|
|
|
// if headings start with a lower level heading, set the former heading index to 1
|
|
|
|
// e.g. h3, h2, h1, h2, h3 => 1.1.1, 1.2, 2, 2.1, 2.1.1
|
|
|
|
levels[i] = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
levels[i] += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let node = toc;
|
|
|
|
for (let i of levels.slice(0, level + 1)) {
|
|
|
|
if (!node.hasOwnProperty(i)) {
|
|
|
|
node[i] = {};
|
|
|
|
}
|
|
|
|
node = node[i];
|
|
|
|
}
|
|
|
|
node.id = id;
|
|
|
|
node.text = text;
|
|
|
|
node.index = levels.slice(0, level + 1).join('.');
|
|
|
|
});
|
|
|
|
return toc;
|
|
|
|
});
|
2018-11-11 22:41:07 +00:00
|
|
|
|
|
|
|
hexo.extend.helper.register('_js', function (url, defer = false, async = false) {
|
|
|
|
const urlFor = hexo.extend.helper.get('url_for').bind(this);
|
|
|
|
if (!url.endsWith('.js') && !url.includes('?')) {
|
|
|
|
url += '.js';
|
|
|
|
}
|
|
|
|
return `<script src="${urlFor(url)}"${async ? ' async' : ''}${defer ? ' defer' : ''}></script>`;
|
|
|
|
});
|
|
|
|
|
|
|
|
hexo.extend.helper.register('_css', function (url) {
|
|
|
|
const urlFor = hexo.extend.helper.get('url_for').bind(this);
|
|
|
|
if (!url.endsWith('.css') && !url.includes('?')) {
|
|
|
|
url += '.css';
|
|
|
|
}
|
|
|
|
return `<link rel="stylesheet" href="${urlFor(url)}">`;
|
|
|
|
});
|
2019-08-16 05:03:19 +00:00
|
|
|
|
|
|
|
hexo.extend.helper.register('_partial', function (name, locals, options = {}) {
|
|
|
|
const { md5, partial, view_dir, page } = this;
|
|
|
|
const currentView = this.filename.substring(view_dir.length);
|
|
|
|
let _locals = Object.assign({}, locals, { layout: false });
|
|
|
|
|
|
|
|
let { path } = hexo.theme.getView(join(dirname(currentView), name)) || hexo.theme.getView(name);
|
|
|
|
path = join(view_dir, path.substring(0, path.length - extname(path).length) + '.locals.js');
|
|
|
|
|
|
|
|
if (!existsSync(path)) {
|
|
|
|
// fallback to default partial
|
|
|
|
return partial(name, locals, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
_locals = require(path)(this, _locals);
|
|
|
|
if (_locals === null) {
|
|
|
|
// partial should be empty
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_locals === false) {
|
|
|
|
// do not cache this fragment
|
|
|
|
return partial(name, locals, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
const language = page.lang || page.language;
|
|
|
|
const fragment = relative(view_dir, path.substring(0, path.length - '.locals.js'.length));
|
|
|
|
const cacheId = [fragment, language, md5(JSON.stringify(_locals))].join('-');
|
|
|
|
|
2019-12-20 23:06:20 +00:00
|
|
|
if (!__fragmentCache.has(cacheId)) {
|
|
|
|
__fragmentCache.set(cacheId, partial(name, _locals, { cache: false, only: options.only || false }));
|
|
|
|
}
|
|
|
|
return __fragmentCache.get(cacheId);
|
2019-08-16 05:03:19 +00:00
|
|
|
});
|
2018-10-16 05:28:42 +00:00
|
|
|
}
|