Add Tags Page and Sidebar in Ghost Blog
前言(碎碎念)
我的前端知识储备依测度为0,印象中第一次(被迫)写前端是因为课程设计要写一个微信小程序,对着微信官方docs和网上的教程照猫画虎地写了个(完全没有后端的)小破程序。
本来想着这辈子再也不会和前端打交道,结果我的blog原生功能太少,需要添加新功能,至少得要有个tags page. 这下完犊子了,Ghost跟Wordpress比起来实在是太小众,相关教程少得可怜,也没有什么开箱即用的免费的theme. 如果不是因为Wordpress是PHP我是真的想换回去...抛开php是世界最好的语言不谈,我确实得硬着头皮想办法自己写(抄)一个tag pages了.
几个月前我鼓捣了大半天,在网上找了寥寥无几的几篇blog,结合Chatgpt和Copilot,集百家之长取了个交集,姑且实现了tags page和sidebar的功能。然后没过几天,一次ghost的更新把我改过的theme给吞了...那次更新把一个软链接给反转了,导致我的theme不可逆地消失了,气得我几个月没再打开过我的blog,也让我深刻意识到了即使是小版本更新也得做好备份——你永远也不知道开发者会加什么阴间的改动。
Anyway,自己的blog总不能就这么荒废了,这几天又一次顶着发麻的头皮把tags page和sidebar加回来了。姑且在这里记录下来,以供日后不时之需。
Tags Page
不知道是不是我记错了,印象中Ghost的上古版本里是自带了tags page,不知为何后来没了。由于Ghost自带关于tags的Handlebars,所以检索并遍历所有tags并不是难事,难点在于如何渲染的不那么难看。
我们在casper文件夹下新建一个custom-page-tags.hbs
, 其中custom-
前缀是必要的, 不然Ghost检测不出来我们的template. 然后我们借用page.hbs
的内容, 将以下代码贴进去:
{{!< default}}
{{!-- The tag above means: insert everything in this file
into the {body} tag of the default.hbs template --}}
{{#post}}
{{!-- Everything inside the #post block pulls data from the page --}}
{{!--
<header class="site-header outer {{#if feature_image}}" style="background-image: url({{feature_image}}){{else}}no-cover{{/if}}">
<div class="inner">
<div class="site-header-content">
<h1 class="site-title">{{title}}</h1>
</div>
</div>
</header>
--}}
<main id="site-main" class="site-main outer">
<div class="inner posts">
{{#match @page.show_title_and_feature_image}}
<header class="article-header">
<h1 class="article-title" style="margin:inherit">{{title}}</h1>
{{#if feature_image}}
<figure class="article-image">
{{!-- This is a responsive image, it loads different sizes depending on device
https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433 --}}
<img
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(min-width: 1400px) 1400px, 92vw"
src="{{img_url feature_image size="xl"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
/>
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
</header>
{{/match}}
<style scoped>
.inner-page-tags {
margin-top: inherit;
}
@media (min-width: 900px) {
.inner-page-tags {
margin-top: 0vw;
}
}
</style>
<div class="inner inner-page-tags">
<div class="post-feed">
{{#get 'tags' limit='all' include='count.posts' order='count.posts desc'}}
{{#foreach tags}}
{{> "tag-card"}}
{{/foreach}}
{{/get}}
</div>
</div>
</div>
</main>
{{/post}}
其实就是把page.hbs
里的
<section class="gh-content gh-canvas">
{{content}}
</section>
换成了
<style scoped>
.inner-page-tags {
margin-top: inherit;
}
@media (min-width: 900px) {
.inner-page-tags {
margin-top: 0vw;
}
}
</style>
<div class="inner inner-page-tags">
<div class="post-feed">
{{#get 'tags' limit='all' include='count.posts' order='count.posts desc'}}
{{#foreach tags}}
{{> "tag-card"}}
{{/foreach}}
{{/get}}
</div>
</div>
需要注意的是,<main id="site-main" class="site-main outer">
里的class是带outer的,这样渲染方式就和author.hbs
一致了,尤其是标题的对齐方式。
然后再partials文件夹里新建tag-card.hbs
, 贴上以下代码:
<article class="post-card {{post_class}}{{#unless feature_image}} no-image{{/unless}}">
{{#if feature_image}}
<a class="post-card-image-link" href="{{url}}">
<!--<div class="post-card-image" style="background-image: url({{feature_image}})"></div>-->
<img class="post-card-image"
srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w"
sizes="(max-width: 1000px) 400px, 800px"
src="{{img_url feature_image size="m"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
loading="lazy"
/>
</a>
{{/if}}
<div class="post-card-content">
<a class="post-card-content-link" href="{{url}}">
<header class="post-card-header">
<h2 class="post-card-title">{{name}}</h2>
</header>
<section class="post-card-excerpt">
<p>{{description}}</p>
<p>A collection of {{plural count.posts empty='posts' singular='% post' plural='% posts'}}</p>
</section>
</a>
</div>
重启Ghost后,我们只需要在管理界面新建一个page,然后在post settings里选择我们刚新建的template即可。Note:如果不加custom-
前缀的话是不会出现在template列表里的。
Reference: 以上代码来源于这篇Blog, 感谢大佬们的无私分享。
Sidebar
现在我希望能加一个侧边栏,展示具有相同primary tag的其他posts,这样可以在某种程度上实现"专题"的功能。我们编辑post.hbs
, 在</main>
后面贴上以下代码:
<style scoped>
.sidebar {
--full: minmax(var(--gap),1fr);
--gap: max(4vmin,20px);
--sidewidth: min(250px, 1fr);
height: 100%; /* Full-height: remove this if you want "auto" height */
width: 250px; /* Set the width of the sidebar */
position: fixed; /* Fixed Sidebar (stay in place on scroll) */
z-index: 1; /* Stay on top */
top: 0; /* Stay at the top */
left: 0;
background-color: #111; /* Black */
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 88px;
padding-left: 15px;
}
@media (max-width: 900px) {
.sidebar {
display: none;
}
}
.sidebar .widget {
margin-bottom: 20px;
}
.sidebar .widget h2 {
font-size: 1.2em;
margin-bottom: 10px;
}
.sidebar .widget a {
color: #818181;
}
</style>
<aside class="sidebar">
<div class="widget">
<h2> Related Articles </h2>
<ul>
{{#get "posts" filter="tag:{{primary_tag.slug}}+id:-{{id}}" limit="10"}}
{{#foreach posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{else}}
<p> No related article yet. </p>
{{/foreach}}
{{/get}}
</ul>
</div>
<div class="widget">
<h2>Newest Articles</h2>
<ul>
{{#get "posts" limit="5"}}
{{#foreach posts}}
<li><a href="{{url}}">{{title}}</a></li>
{{/foreach}}
{{/get}}
</ul>
</div>
</aside>
然后重启Ghost即可。
Reference: 以上代码修改自这个教程. 我瞎改了一通,代码里有没用的部分以及可能有bug的部分,并且不同分辨率下的适配完✩全✩没✩做(突然能理解游戏开发者为什么对PC端的优化很头疼了). But anyway, it just works (∠・ω<)⌒★
补充:如果我们不希望sidebar将footer挡住的话,只需要在Ghost的code injection里加上下面一段代码即可
<style>
.site-footer {
background: #0a0b0c;
color: #fff;
margin: max(12vmin,64px) 0 0;
padding-bottom: 140px;
padding-top: 48px;
position: relative;
z-index: 2;
}
</style>