主题安装

有两种方法来安装Themes。第一种是在这个管理界面中选最下面的『Upload A Theme』来上传你下载过的Theme .zip包,第二种是直接将.zip包解压到Ghost安装目录的content/themes/目录下。

Custom Theme

这是官方的模板项目。

https://github.com/TryGhost/Starter

几个需要注意的地方。

主要文件:

  • default.hbs - The main template file
  • index.hbs - Used for the home page
  • post.hbs - Used for individual posts
  • page.hbs - Used for individual pages
  • tag.hbs - Used for tag archives
  • author.hbs - Used for author archives

有一个整洁的技巧是,你也可以创建自定义的一次性模板,只需在模板文件中添加页面的slug即可。比如说,你可以在模板文件中添加一个页面的Slug。

  • page-about.hbs - Custom template for the /about/ page
  • tag-news.hbs - Custom template for /tag/news/ archive
  • author-ali.hbs - Custom template for /author/ali/ archive

Styles使用Gulp/PostCSS进行处理. 所以你需要安装Node](https://nodejs.org/), YarnGulp

# Install
yarn

# Run build & watch for changes
$ yarn dev

现在你可以直接编辑/assets/css/文件,这样修改就会被自动编译到 /assets/built/目录下。

The zip Gulp task packages the theme files into dist/<theme-name>.zip, which you can then upload to your site.

yarn zip

官方使用gulp来优化JavaScript和CSS,所以,需要先安装gulp相关的依赖,官方的介绍文档如下。

https://ghost.org/tutorials/how-to-use-gulp-in-a-ghost-theme/

Gulp是一个精简js、css的工具,官方地址如下。

https://www.gulpjs.com.cn/

JS的生态真是太恶劣了,版本管理是我见过的最复杂,最难解决冲突的语言了。官方的Casper Demo clone下来之后,要处理很多东西。

npm install // 安装所有npm依赖
npm install @tryghost/release-utils --save // 安装release-utils

所有npm安装好了之后,执行yarn dev,就可以在修改css文件后,自动生成built目录下的优化css了。

Handlebars中文指南

https://handlebarsjs.com/zh/guide

Ghost主题调试

通常情况下,会创建一个本地调试环境,测试开发主题完成之后,再打包上传到正式环境。

npm install ghost-cli@latest -g
ghost install local

这样就在本地搭建了测试环境。

自己做的主题,直接放到ghost-local\content\themes目录下即可,这时候,直接修改hbs文件,在浏览器中刷新,就可以实时预览效果了。

不过这里有个问题,那就是CSS是没办法hotreload的,必须要执行ghost restart才可以生效。大部分时间,都是现在Chrome Devtools里面修改DOM,然后再去修改CSS。

调试完成后,通过打包成zip,就可以在后台上传主题包了。

使用SVG Icon

Casper使用内嵌式SVG图标,通过Handlebars partials引用。你可以在/partials/icons中找到所有图标。

要使用一个图标,只需包含相关文件的名称,例如,要在 /partials/icons/rss.hbs中包含SVG图标,可以使用。

{{> "icons/rss"}}

快速修改文章

Ghost后台没有提供搜索文章的入口,所以要修改文章时,就只能通过一些条件来进行筛选,但是通过下面的方法,可以直接修改文章。

Post: http://www.your-domain.com/post-name
Edit: http://www.your-domain.com/post-name/edit

添加代码高亮

highlight.js可以自动检测语言,且在使用时比较简单,所以这里使用highlight.js来创建Ghost博客中的代码高亮问题。官网地址如下所示。

https://highlightjs.org/

不过Ghost官方是通过Prism https://ghost.org/tutorials/code-syntax-highlighting/ 来实现的。我试下来Prism必须要在MD中指定语言类型才能实现高亮。

使用 highlight.js 很简单。只需要添加三个组件。

  • 代码的样式文件
  • 分析代码确定样式的JS脚本文件
  • 用来调用脚本文件的脚本

在default.hbs中,修改header。这里使用的是官方推荐的CDN,你也可以使用BootCDN的链接。还可以在highlight.js Demo直接预览效果。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/styles/androidstudio.min.css">

在script中,增加JS代码。

<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/highlight.min.js"></script>

可以引入不同的css文件,展示不同的代码风格。

在script中,增加调用脚本。

<script>hljs.initHighlightingOnLoad();</script>

添加目录

每篇文章,都应该有一个目录用来展示文章的大纲,点击可以跳转对应的章节,原始的Casper主题是没有这个功能的,所以我们需要为其拓展这个功能,官方给出的指引中,实际上也包含了这个例子,地址如下所示。

https://ghost.org/tutorials/adding-a-table-of-contents/

首先,要引入Tocbot的库,用于生成Markdown的目录。

CSS文件如下,添加到default.hbs的header中。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.css" />

JS文件如下。

<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.min.js"></script>

在default.hbs的body完结标签之前,加入调用的JS代码。

<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.0/tocbot.min.js"></script>
<script>
    tocbot.init({
        tocSelector: '.toc',
        contentSelector: '.post-content',
        hasInnerContainers: true
    });
</script>

由于目录会添加在每篇文章里面,所以还需要修改post.hbs文件,在post-content之前,增加aside代码,添加toc,代码如下所示。

<section class="post-full-content">
    <aside class="toc-container">
        <div class="toc"></div>
    </aside>
    <div class="post-content">
        {{content}}
    </div>
</section>

最后,修改CSS文件,添加TOC相关的样式,比如右侧悬浮、高亮等等,代码如下所示。

/* 13. TCO
/* ---------------------------------------------------------- */
/* Offset headings from fixed header */
.post-content h2::before,
.post-content h3::before {
    display: block;
    content: " ";
    height: 84px;
    margin-top: -84px;
    visibility: hidden;
}

/* Adjust content wrapper */
.post-content {
    display: block;
}

/* Adjustments to wide and full width cards */
.kg-gallery-card,
.kg-width-wide,
.kg-width-full {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.kg-gallery-card > *,
.kg-width-wide > *,
.kg-width-full > *,
figure.kg-width-full img {
    margin-left: -50vw;
    margin-right: -50vw;
}

.post-full-content pre {
    max-width: 0;
}
.post-content h2,
.post-content h3 {
	outline: none;
}
body {
	overflow: visible;
}
.post-full-content {
	display: grid;
	grid-template-columns: 1fr 0.2fr;
	padding: 0 0 6vw;
	margin: 0;
}
.toc-container {
	order: 1;
}
.toc-container .toc {
	position: sticky;
	top: 70px;
	min-width: 260px;
	font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
		Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif;
	font-size: 1.6rem;
	line-height: 1.6em;
	padding: 0 0.8em;
}
.is-active-link::before {
	background-color: #3eb0ef;
}
ol.toc-list {
	margin: 0;
}

添加阅读进度

在post.hbs最顶部,添加一个自定义的progress。官方文档

<progress data-reading-progress max="4"></progress>

添加JS代码。

const readingProgress = (contentArea, progressBar) => {
    // Grab content area and progress bar
    const content = document.querySelector(contentArea);
    const progress = document.querySelector(progressBar);

    // Minutes remaining label template
    const label = value => `${value} minute${value !== 1 ? "s" : ""} remaining`;

    // Set the progress bar label to maximum time remaining if data attribute is present
    if (progress.hasAttribute('data-reading-progress')) {
        console.log('test');
        progress.dataset.readingProgress = label(progress.max);
    }

    const frameListening = () => {
        // Get the content area position,
        // the vertical centre of the browser window,
        // the minutes remaining without decimal places
        const contentBox = content.getBoundingClientRect();
        const midPoint = window.innerHeight / 2;
        const minsRemaining = Math.round(progress.max - progress.value);

        // Update the label if data attribute is present
        if (progress.hasAttribute('data-reading-progress')) {
            progress.dataset.readingProgress = label(minsRemaining);
        }

        // Default the progress bar to 0 before content is in view
        if (contentBox.top > midPoint) {
            progress.value = 0;
        }

        // Default the progress bar to maximum when the content is past view
        if (contentBox.top < midPoint) {
            progress.value = progress.max;
        }

        // Start updating the progress value when content as reached the vertical centre
        // Stop updating when the content is past the vertical centre
        if (contentBox.top <= midPoint && contentBox.bottom >= midPoint) {
            // Calculate the progress bar value
            progress.value =
                (progress.max * Math.abs(contentBox.top - midPoint)) /
                contentBox.height;
        }

        // Continue to request animation frames
        window.requestAnimationFrame(frameListening);
    };

    // Begin requesting animation frames
    window.requestAnimationFrame(frameListening);
};

// Init, main content selector and progress bar selector
readingProgress(".post-full-content", "progress");

添加CSS样式。

/* 14. Reading Progress
/* ---------------------------------------------------------- */
progress {
  -webkit-appearance: none;
  -moz-appearance: none;
       appearance: none;
  z-index: 1000;
  position: fixed;
  top: 64px;
  left: 0;
  right: 0;
  width: 100%;
  height: 2px;
}

progress[value]::-webkit-progress-bar {
  background-color: rgba(0, 0, 0, 0.5);
}

progress[value]::-webkit-progress-value {
  background-color: #3eb0ef;
}

progress::before {
  content: attr(data-reading-progress);
  position: absolute;
  background: none;
  font-size: .6em;
  margin: .6em .8em;
  color: white;
  right: 0;
  background: rgba(0, 0, 0, 0.7);
  padding: .3em .6em;
  border-radius: .8em;
}

.content {
  max-width: 600px;
  margin: 0 auto;
}

这里总结了Ghost主题开发的一些知识点,在了解这些基本知识和开发方式之后,再去制作主题或者修改主题,就比较方便了。