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, 感谢大佬们的无私分享。

现在我希望能加一个侧边栏,展示具有相同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>