趁着开学事情不多,好好折腾了一下这个新玩具主题,简直有点到爱不释手的地步了。记录一下魔改的过程,以供自鉴;如果能帮到有类似需求的朋友们就更好啦!

你好 Stellar!

博客组织方式之我思

众所周知 Stellar 最吸引人的特性之一是强大的 Wiki 文档系统,但一番操作发现并不是所有的文章都适合用 Wiki 进行组织。

  • 向 Wiki 项目中添加文章的过程有点繁琐,得改目录树。
  • Wiki 项目中的文章居然不在主页显示!即是说假如我的某篇得意之作被收录在 Wiki 项目里,我就无法在主页展示出来;这要求我必须在我的强迫症和虚荣心中舍彼取此。当然有一个方法是项目 wiki/[project] 里收录一篇,再复制一篇到主页 _post,但这个解法实在是太丑陋。
  • 某些主题不明确的文章不适合收到一个集子里。本来打算是专门弄一个「其他」项目,但仔细想想假以时日这个「其他」一定会膨胀到难以忽视。

我仔细审视了一下我的博客内容,大概分成四类。

从左到右从上往下分别记为 A/B/C/D 类型内容
从左到右从上往下分别记为 A/B/C/D 类型内容

首先 A 型内容很适合做成 Wiki 项目。一方面它们已经成型,后续的增补内容很少;另一方面它们大多数是作为有机整体的课程笔记系列,不太有机会需要单拎一篇出来放到主页。

而 B 型内容多数是散乱的课程笔记/自学记录,质量良莠不齐,显然我想要展示一部分,隐藏一部分;但被隐藏的内容并不代表没有价值,我还是需要挂在博客上供我自己参考回顾,只是我个人并不是特别满意罢了。

C 型内容是我的读书/观影/游戏记录,以时间为经连缀起我的精神生活碎片,显然是需要不断增补更新的。并且这其中有一些认真写的书评/影评/游戏评,是我想放在主页上展示的。

最后的 D 型内容是我日常的一些文字随笔,没有特定主题,也无需时间串联,整个形式是十分随意的,但这一部分却是我很想展示在主页的内容。

哇!简直乱糟糟!不仅每种内容的组织形式不同,结构完整度不同,我想要展示的欲望也不同!而当中仅仅只有需求 A 是原生 Stellar 可以解决的!如果谁能想到办法四个需求一次满足的话,那 TA 一定是天才了吧!

我们需要「专栏」!

还好!Stellar 提供了一种有别于 Wiki 项目的简化组织形式 —— 专栏,这让我的大业又推进了一步。

简而言之,「专栏」就是没有目录树的 Wiki 项目,其单纯的以时间为索引松散的连缀一系列指定主题的文章。但这同时引入了一个新问题:专栏没有 index 页,每次点进去默认显示的都是最新的文章,而我想要的是一个类似目录或者扉页的东西。

Wiki 项目能够很容易的实现这一点,只要建一个 index 并把它指定为目录树的开头就行。真是成也目录树,败也目录树!

为了解决这个问题,我定义了一个新的 front-matter 字段:topic_pin

themes/stellar/scripts/events/lib/merge_posts.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (let tid of Object.keys(topic.tree)) {
let topicObject = topic.tree[tid]

const pinnedPages = topicObject.pages.filter(
page => page.topic_pin === true)
const normalPages = topicObject.pages.filter(
page => page.topic_pin !== true)

if (topicObject.order_by == '-date') {
pinnedPages.sort((a, b) => new Date(b.date) - new Date(a.date))
normalPages.sort((a, b) => new Date(b.date) - new Date(a.date))
} else {
pinnedPages.sort((a, b) => new Date(a.date) - new Date(b.date))
normalPages.sort((a, b) => new Date(a.date) - new Date(b.date))
}

topicObject.pages = [...pinnedPages, ...normalPages]

topicObject.homepage = topicObject.pages[0]
}

在专栏中创建一个 index 页,再在 front-matter 中设置 topic_pin: true 就大功告成啦!

……把思路逆转过来!

试着把 B 型内容和 C 型内容用改良后的专栏来组织,效果很好!在此期间还发现一件有趣的事:由于专栏不要求 markdown 文件存储在 _post 之外,收录不收录到专栏并不影响文章本身在主页上的显示。

之前研究 Wiki 项目产生了惯性思维,一直在想怎样把项目内的亮点文章挪到主页来显示;没想到专栏的这个特性使得需求逆转了:现在需要考虑的是如何隐藏某些在主页的非亮点文章了

等等!这不是我在老博客中使用过的 hexo-hide-posts 插件吗!

激动的配置好这个插件,隐藏文章 —— 果然没我想象的那么简单。Stellar 的 topic 并不是一个常规的 generator,不能直接写在白名单里;这就是说当我在主页隐藏某个文章的同时,这个文章同时也在专栏中被隐藏了。

好嘛!到头来还是逆转回去了。异议阿里!

只好想了个折衷的办法,虽然逆转再逆转的思路确实不太优雅,但实现起来却很简洁(毕竟插件把大部分逻辑都抽象好了,不用白不用):

themes/stellar/scripts/filters/topic_hidden_handler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
hexo.extend.filter.register('before_generate', function() {
const hexo = this;

const PostModel = hexo.database.model('Post');
const allPosts = PostModel.toArray();

const hiddenTopicPosts = allPosts.filter(post =>
post.hidden === true && post.topic && post.topic.length > 0
);

if (hiddenTopicPosts.length > 0) {
hexo.locals.set('hidden_topic_posts', function() {
return hiddenTopicPosts;
});
}
});

这个 handler 会在 hexo-hide-posts 作用之前收集所有被 hidden: true 标记的文章,并在之后 merge_posts.js 脚本收集专栏文章的时候返还 hiddenTopicPosts 中对应专栏的隐藏文章。天哪说起来都好绕。

最终效果见我的专栏,你会发现不是所有的专栏文章都会显示在主页上喔。是不是很不错!

静态 timeline 标签组件化

之前提到,C 型内容是根据时间组织的,而 Stellar 正好提供了「时间线」组件,非常契合我的需求。我想在 C 型内容首页的右侧边栏挂上时间线,每次读完一本新书,打完一部游戏,就能往上串一条新的经历,多有满足感!

但事情比我想象中的棘手。仔细阅读完教程后我发现,「时间线」实际上有两种:

  • 静态时间线:放在正文部分,是一个 {% timeline %} 标签,内容是写死在 markdown 里的。
  • 动态时间线:这才是侧边栏组件,需要配置 API 从其他地方提取动态数据;但这不是我想要的。

我的需求是把静态时间线配置到侧边栏组件上,这一点原生 Stellar 并不支持。

又注意到侧边栏组件中有一个叫 markdown 的,非常的灵活,支持在侧边栏显示任意形式的 markdown 语法文本。尝试将 {% timeline %} 标签写进去,果然不渲染。

那么我们让它渲染不就行了嘛!照猫画虎根据 markdown 组件的逻辑扩展了 timeline 组件,新增 content 字段,并且以有无 api 字段决定是动态还是静态时间线。

核心修改逻辑
themes\stellar\layout\_partial\widgets\timeline.ejs
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
if (item.api) {
el += `<div class="tag-plugin timeline data-service ds-${(item.type || 'timeline')}"`;
['api'].forEach(key => {
if (item[key]) { el += ' data-' + key + '="' + item[key] + '"'; }
});
['user', 'hide', 'limit'].forEach(key => {
if (item[key]) { el += ' ' + key + '="' + item[key] + '"'; }
});
el += '>';
el += '</div>';
} else {
el += '<div class="tag-plugin timeline data-service ds-timeline">';

if (item.content) {
if (item.content.includes('<!-- node')) {
var nodes = parseStaticTimelineNodes(item.content);
nodes.forEach((node, i) => {
el += '<div class="timenode" index="' + i + '">';
el += '<div class="header">';
el += node.header;
el += '</div>';
el += '<div class="body fs14">';
el += markdown(node.body);
el += '</div>';
el += '</div>';
});
} else {
el += '<div class="timenode" index="0">';
el += '<div class="header">Content</div>';
el += '<div class="body fs14">';
el += markdown(item.content);
el += '</div>';
el += '</div>';
}
}

el += '</div>';
}

还得小修一下 themes\stellar\source\css\_components\widgets\timeline.styl 使得静态时间线组件的标题与内容排列更加紧凑,比较繁琐,故按下不表。

你可以去探索页//游戏评主页的右侧边栏看到实现的效果。是不是挺像那么回事的?

Giscus 评论区主题自定义

之前的老博客用的是早已停止开源的 Valine,听说还有隐私泄露的风险,所以趁着升级博客把评论系统也给换一换(之前的评论都要无了 555)。

Giscus 基于 Github Discussion 来做映射简直是一个天才般的想法,配置起来也无比的简单,就是可能对没有 Github 账号的朋友不太友好。

除此之外还有一个让我强迫症发作的点在于 Giscus 组件是一个 iframe,不受我博客的 CSS 样式管辖,这就造成我没法在评论区用上美丽的落霞孤鹜字体。

研究了一下,成功用 Giscus 提供的自定义主题接口解决了,顺便还调整了一下边框样式,加了夜间模式。有需要的朋友可以在 这里 自取我的样式文件,直接托管到你自己的网站上再修改 Giscus 的 data-theme 字段就好啦!记得 url 不要加 www 前缀,不然会 CORS。

其他想要做的事

O1
完成博客从 NexT.Pisces 到 Stellar 的迁移!

计划在学期内结束。

正常 40%
KR1
定义并实现所有博客内容的组织形式
定义博客内容组织形式
新增专栏目录页支持
新增专栏文章主页隐藏功能
已完成 100%
KR2
迁移旧博客上的所有博文

计划顺序:D => C => B => A

未完成 10%
KR3
其他想要的小特性!
静态 timeline 标签组件化
giscus 评论区换成 LXGW 字体
加入「开往」独立博客计划
为读书/观影/游戏记录页添加类似豆瓣的封面展示
集成 hexo-blog-encrypt 插件实现部分贴文的密码访问
研究怎样把本地资源远程托管
未完成 10%