Hexo NexT主题侧边栏展示相关文章
2023-01-30 更新: NexT主题已经支持相关文章功能 (使用hexo-related-posts ),更新到最新版本即可,可参考这里。
Hexo的NexT主题展示相关文章和热门文章使用 hexo-related-popular-posts 插件,但hexo-related-popular-posts 默认展示位置是在页面底部,而页面底部本身内容较多,多数人注意不到相关文章。因此,考虑将相关文章展示在侧边栏。
初始想法是将相关文章新建一个与目录块平齐的相关文章块,随着页面的滚动,目录吸附在页面顶部,相关文章块展示在目录块下方。无奈前端了解不多,试了下觉得难度系数略高。取了个折衷方案,将相关文章与目录显示在同一div中。
概览
效果图
话不多说,先看效果。
侧边栏相关文章:
原来的侧边栏:
实现原则
- 修改尽量小,不对Hexo和NexT源码改动太多,添加新文件少改老文件,以免今后升级困难
- 复用hexo-related-popular-posts的相关文章处理逻辑
- 风格与主题保持一致
所有改动一览
修改改三个文件,新增两个文件:
~/finisky/themes/next/layout$ git status
On branch related
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: _macro/post.swig
modified: _macro/sidebar.swig
modified: ../source/css/_common/outline/sidebar/sidebar.styl
Untracked files:
(use "git add <file>..." to include in what will be committed)
_partials/post/post-related-sidebar.swig
../source/css/_common/outline/sidebar/sidebar-related.styl
获取相关文章数据
复用hexo-related-popular-posts计算出来的相关文章数据即可,源码在_partials/post/post-related.swig (隐去无关内容):
{%- set popular_posts = popular_posts_json(theme.related_posts.params, page) %}
{%- if popular_posts.json and popular_posts.json.length > 0 %}
<div class="popular-posts-header">{{ theme.related_posts.title or __('post.related_posts') }}</div>
<ul class="popular-posts">
{%- for popular_post in popular_posts.json %}
...
<div class="popular-posts-title"><a href="{{ popular_post.path }}" rel="bookmark">{{ popular_post.title }}</a></div>
{%- if popular_post.excerpt and popular_post.excerpt != '' %}
<div class="popular-posts-excerpt"><p>{{ popular_post.excerpt }}</p></div>
{%- endif %}
</li>
{%- endfor %}
</ul>
{%- endif %}
显然,可以通过popular_posts这个变量访问到相关文章数据。
从侧边栏模板开始
看下前端代码,可以找到Table of Contents的模板在themes/next/layout/_macro/sidebar.swig中。该文件定义了整个侧边栏:
<aside class="sidebar">
<div class="sidebar-inner">
{%- set display_toc = page.toc.enable and display_toc %}
{%- if display_toc %}
{%- set toc = toc(page.content, { "class": "nav", list_number: page.toc.number, max_depth: page.toc.max_depth }) %}
{%- set display_toc = toc.length > 1 and display_toc %}
{%- endif %}
<ul class="sidebar-nav motion-element">
<li class="sidebar-nav-toc">
{{ __('sidebar.toc') }}
</li>
<li class="sidebar-nav-overview">
{{ __('sidebar.overview') }}
</li>
</ul>
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
{%- if display_toc %}
<div class="post-toc motion-element">{{ toc }}</div>
{%- endif %}
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
{{ partial('_partials/sidebar/site-overview.swig', {}, {cache: theme.cache.enable}) }}
{{- next_inject('sidebar') }}
</div>
{%- if theme.back2top.enable and theme.back2top.sidebar %}
<div class="back-to-top motion-element">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
{%- endif %}
</div>
</aside>
看起来实现并不复杂,copy下Table of Contents的div,只把内容修改成popular_posts即可。实验了下发现naive了,这样修改页面渲染会出问题,js报错,页面展示异常。想到应该是js修改了这些元素的内容,验证:
~/finisky/themes/next$ grep -r "post-toc" .
...
./source/js/utils.js: const navItems = document.querySelectorAll('.post-toc li');
./source/js/utils.js: var tocElement = document.querySelector('.post-toc-wrap');
./source/js/utils.js: document.querySelectorAll('.post-toc .active').forEach(element => {
./source/js/utils.js: while (!parent.matches('.post-toc')) {
./source/js/utils.js: document.querySelector('.post-toc-wrap').style.maxHeight = sidebarWrapperHeight;
./source/js/utils.js: var hasTOC = document.querySelector('.post-toc');
可见utils.js会获取class为"post-toc"的元素并操作,会引发上述错误。
我们仅需要复用TOC的css显示风格,不会用到js的这些动态操作。同时,我们不希望改变原有的逻辑。综上,比较好的实现方式是copy下TOC的css,在此基础上修改成相关文章的css style。
添加css: sidebar-related.styl
搜索找到toc的css文件在:themes/next/source/css/_common/outline/sidebar/sidebar-toc.styl文件中。添加新的sidebar-related.styl在此目录中:
.sidebar-related {
font-size: $font-size-small;
ol {
list-style-type: disc;
margin: 0;
padding: 0 2px 5px 25px;
text-align: left;
}
}
.sidebar-related-title {
margin-top: 20px;
padding-left: 0;
li {
border-bottom-color: $sidebar-highlight;
color: $sidebar-highlight;
border-bottom: 1px solid;
cursor: pointer;
display: inline-block;
font-size: $font-size-small;
}
}
上面这个小css文件也颇费了些周折,把无用的style去掉,调整margin和padding和list-style-type。当然,也可改成自己喜欢的样式,不一定强求与原主题一致。
修改css: sidebar.styl
还要将新加的文件import到themes/next/source/css/_common/outline/sidebar/sidebar.styl中,添加一行,修改如下:
...
@import 'sidebar-toc' if (hexo-config('toc.enable'));
+@import 'sidebar-related' if (hexo-config('toc.enable'));
@import 'site-state' if (hexo-config('site_state'));
...
添加侧边栏列表: post-related-sidebar.swig
在themes/next/layout/_partials/post/文件夹中添加post-related-sidebar.swig:
{%- set popular_posts = popular_posts_json(theme.related_posts.params, page) %}
{%- if page.toc.enable and theme.related_posts.enable and (theme.related_posts.display_in_home or not is_index) and popular_posts.json and popular_posts.json.length > 0 %}
<div>
<ul class="sidebar-related-title">
<li>
{{ theme.related_posts.title or __('post.related_posts') }}
</li>
</ul>
<!--noindex-->
<div class="sidebar-related">
<ol>
{%- for popular_post in popular_posts.json %}
<li><a href="{{ popular_post.path }}" rel="bookmark"><span class="nav-text">{{ popular_post.title }}</span></a></li>
{%- endfor %}
</ol>
</div>
<!--/noindex-->
</div>
{%- endif %}
此处用到了前面定义的两个css style: sidebar-related-title和sidebar-related。同时用popular_posts变量输出了超链接和文章标题。
只有在启用了TOC和相关文章,且相关文章有内容的情况下才在侧边栏显示。
修改侧边栏: sidebar.swig
万事俱备,改一下侧边栏的文件:themes/next/layout/_macro/sidebar.swig,仅添加三行,把原始的TOC外面包一层div,再引用前文写好的post-related-sidebar.swig即可:
@@ -8,6 +8,7 @@
<aside class="sidebar">
<div class="sidebar-inner">
+ <div>
{%- set display_toc = page.toc.enable and display_toc %}
{%- if display_toc %}
{%- set toc = toc(page.content, { "class": "nav", list_number: page.toc.number, max_depth: page.toc.max_depth }) %}
@@ -36,6 +37,9 @@
{{- next_inject('sidebar') }}
</div>
+ </div>
+ {{ partial('_partials/post/post-related-sidebar.swig') }}
{%- if theme.back2top.enable and theme.back2top.sidebar %}
<div class="back-to-top motion-element">
完整sidebar.swig如下:
{% macro render(display_toc) %}
<div class="toggle sidebar-toggle">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
<aside class="sidebar">
<div class="sidebar-inner">
<div>
{%- set display_toc = page.toc.enable and display_toc %}
{%- if display_toc %}
{%- set toc = toc(page.content, { "class": "nav", list_number: page.toc.number, max_depth: page.toc.max_depth }) %}
{%- set display_toc = toc.length > 1 and display_toc %}
{%- endif %}
<ul class="sidebar-nav motion-element">
<li class="sidebar-nav-toc">
{{ __('sidebar.toc') }}
</li>
<li class="sidebar-nav-overview">
{{ __('sidebar.overview') }}
</li>
</ul>
<!--noindex-->
<div class="post-toc-wrap sidebar-panel">
{%- if display_toc %}
<div class="post-toc motion-element">{{ toc }}</div>
{%- endif %}
</div>
<!--/noindex-->
<div class="site-overview-wrap sidebar-panel">
{{ partial('_partials/sidebar/site-overview.swig', {}, {cache: theme.cache.enable}) }}
{{- next_inject('sidebar') }}
</div>
</div>
{{ partial('_partials/post/post-related-sidebar.swig') }}
{%- if theme.back2top.enable and theme.back2top.sidebar %}
<div class="back-to-top motion-element">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
{%- endif %}
</div>
</aside>
<div id="sidebar-dimmer"></div>
{% endmacro %}
至此,侧边栏已可显示相关文章,胜利在望,只差最后一步。:-)
去掉文中相关文章:post.swig
将原始文末显示的相关文章去掉,删除三行themes/next/layout/_macro/post.swig:
{### END POST BODY ###}
{#####################}
- {%- if theme.related_posts.enable and (theme.related_posts.display_in_home or not is_index) %}
- {{ partial('_partials/post/post-related.swig') }}
- {%- endif %}
{%- if not is_index %}
{{- next_inject('postBodyEnd') }}
大功告成,主页、标签页及各文章页面均无问题,cool!
小结
限于前端知识,本文实现方法可能还是不太“Hexo way”,不过算是达成了实现目标。如开头所言,更好的展示方式是创建一个与目录块平齐的相关文章块,作为future work吧。
有问题欢迎留言讨论~