目的

通过添加  <!-- more -->  标记,可以手动切割文章,实现在列表页面仅显示文章摘要。但像这样在文章内部手动添加标记的方法,对文章进行了侵入式的改造,添加了与内容实际无关的信息,并不是很理想。
为了能够实现自动添加摘要,可以通过现有的 hexo 插件来实现,如:hexo-excerpt,hexo-auto-excerpt。但两者各自存在一定的局限,并不能很好的实现预期的效果,因此必须手动对其插件代码进行改造。

插件对比

hexo-excerpt:作用在生成器执行过程中,通过 dom 树自动提取文章内容,摘要提取相对完整灵活,但是只能作用在首页列表;
hexo-auto-excerpt:作用在文章后置过滤器执行过程中,通过指定固定的字符数自动提取文章内容,可以为所用列表项添加摘要;

插件实现

在参考了上述两个插件的源码之后,发现将其实现机制组合起来,可以有效的解决当前的痛点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
const htmlparser = require("htmlparser2");
const domutils = require("domutils");
const defaults = require("lodash.defaults");

// hexo-excerpt 中提供的方法,具体见下文
const Filter = require("./lib/dom-filter");
const DEFAULT_CONFIG = {
depth: 2,
excerpt_excludes: [],
more_excludes: [],
hideWholePostExcerpts: false,
};

// 注册过滤器,参考 hexo-auto-excerpt
hexo.extend.filter.register("after_post_render", excerpt);

// 全局参数
let excerptFilter;
let moreFilter;
let opts;
/**
* 提取文章摘要, 此部分提取自 hexo-excerpt
* @param {Document} post 文章
*/
function excerpt(post) {
init();
//honour the <!-- more --> !!!
if (
/<!--\s*more\s*-->/.test(post.content) ||
post.content.indexOf('<a id="more"></a>') !== -1
) {
return post;
}

let nodes = [];

let parser = new htmlparser.Parser(
new htmlparser.DomHandler((err, dom) => {
if (!err) {
nodes = dom;
}
}),
{
decodeEntities: false,
}
);

parser.write(post.content);
parser.done();

// tracks how many tag nodes we found
let stopIndex = 1;
// tracks how many nodes we found in total
let index = 0;
for (; index < nodes.length && stopIndex <= opts.depth; index++) {
if (nodes[index].type === "tag" && excerptFilter.match(nodes[index])) {
stopIndex++;
}
}

// set correct excerpt and more nodes values
let excerptNodes = nodes.slice(0, index);
let moreNodes = nodes.slice(index);

// filter nodes
excerptNodes = excerptFilter.filter(excerptNodes);
moreNodes = moreFilter.filter(moreNodes);

// If the hideWholePostExcerpts option is set to true (the default), don't show
// excerpts for short posts (i.e. ones where the excerpt is the whole post)
if (moreNodes.length != 0 || !opts.hideWholePostExcerpts) {
post.excerpt = excerptNodes
.map((node) => domutils.getOuterHTML(node))
.join("");
post.more = moreNodes.map((node) => domutils.getOuterHTML(node)).join("");
}

return post;
}

/**
* 初始化配置
*/
function init() {
const config = hexo.config;
let legacy = {};
if (config.excerpt_depth) {
hexo.log.warn(
"excerpt_depth is deprecated, please use excerpt.depth instead."
);
legacy.depth = config.excerpt_depth;
}

opts = opts || defaults({}, config.excerpt, legacy, DEFAULT_CONFIG);
opts.depth = parseInt(opts.depth);
if (!Array.isArray(opts.excerpt_excludes))
opts.excerpt_excludes = [opts.excerpt_excludes];
if (!Array.isArray(opts.more_excludes))
opts.more_excludes = [opts.more_excludes];
excerptFilter = excerptFilter || new Filter(hexo, opts.excerpt_excludes);
moreFilter = moreFilter || new Filter(hexo, opts.more_excludes);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const domutils = require("domutils");
const CSSselect = require("css-select");

function Filter(hexo, excludes) {
this.hexo = hexo;
this.selectors = excludes.map(this._compile.bind(this));
}

Filter.prototype._compile = function (selector) {
try {
return CSSselect.compile(selector);
} catch (err) {
this.hexo.log.error(
"hexo-excerpt: Ignore invalid CSS selector: " + selector
);
return (n) => false;
}
};

Filter.prototype.match = function (node) {
// not match any
return !this.selectors.some((s) => s(node));
};

Filter.prototype.filter = function (nodes) {
// remove inner nodes that doesn't match
domutils
.filter((n) => !this.match(n), nodes)
.forEach((node) => {
domutils.removeElement(node);
});

// only keep top level nodes that match
return nodes.filter(this.match.bind(this));
};

module.exports = Filter;

其他

不完备方法

一开始是查看 hexo-excerpt 的源码,准备在标签页生成期处理,参考 hexo-generator-tag 的实现过程,最终可以实现在标签页提取摘要,但对于分类页面,仍然需要做相同的处理,随即放弃,此处仅作记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const pagination = require("hexo-pagination");
hexo.extend.generator.register("tag", generator);
function generator(db) {
const config = this.config;
const perPage = config.tag_generator.per_page;
const paginationDir = config.pagination_dir || "page";
const orderBy = config.tag_generator.order_by || "-date";
const tags = db.tags;
let tagDir;

init();

const pages = tags.reduce((result, tag) => {
if (!tag.length) return result;

const posts = tag.posts.sort(orderBy);
// 预处理 posts,提取摘要,excerpt 方法定义在上文
posts.data = posts.data.map(excerpt);
const data = pagination(tag.path, posts, {
perPage: perPage,
layout: ["tag", "archive", "index"],
format: paginationDir + "/%d/",
data: {
tag: tag.name,
},
});
return result.concat(data);
}, []);

// generate tag index page, usually /tags/index.html
if (config.tag_generator.enable_index_page) {
tagDir = config.tag_dir;
if (tagDir[tagDir.length - 1] !== "/") {
tagDir += "/";
}

pages.push({
path: tagDir,
layout: ["tag-index", "tag", "archive", "index"],
// posts: db.posts,
data: {
base: tagDir,
total: 1,
current: 1,
current_url: tagDir,
// posts: db.posts,
prev: 0,
prev_link: "",
next: 0,
next_link: "",
tags: tags,
},
});
}
return pages;
}

辅助

在研究时为了查看 hexo 的 post 具体的结构定义的打印函数,尝试过程中,并没有找到如何进行调试,因此将其打印后查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 文章属性打印
* 查看其文章结构
* @param {Document} document 文章
*/
function documentPrint(document) {
if (document instanceof Array) {
console.log("documentPrint ====== begin");
document.forEach((item) => documentPrint(item));
console.log("documentPrint ====== end");
return;
}
for (const key in document) {
if (document.hasOwnProperty(key)) {
const value = document[key];
if (!"more、_content、raw".includes(key)) {
console.log("====> %o: %o", key, value);
} else {
console.log("====> %o: %o", key, "[blog]");
}
}
}
}

评论