对于复杂的技术博文没有侧边目录栏基本看不下去,然而ghost并不自带目录栏功能,所以我们只能通过改主题文件来实现。

不过好在ghost的主题文件并不复杂,这里完全可以依照官方教程把代码粘贴进去就能用了,强烈建议先看官方教程。这篇博客主要讲解一些细节

官方教程:https://ghost.org/tutorials/adding-table-of-contents

我的博客主题修改自source,仓库:https://github.com/dedfaf/DF-Blog-Theme

教程讲解

官方教程使用了ToCBot来实现目录栏

可以用ghost install local安装本地测试环境,在其上修改后再push到生产环境

一些简单的样式调整

这里讲解一些官方教程操作完后的一些细节调整,也可以直接参考文末我的代码

默认展开所有目录

默认情况下,之后浏览到的目录只会展开当前标题的子标题,需要在脚本中加入

<script>
    tocbot.init({
        // ...
        collapseDepth: 6,
        // ...
    });
</script>

去除序号前缀

脚本中加入

<script>
    tocbot.init({
        // ...
        orderedList: false, // Ensures the list is not ordered  
        // ...
    });
</script>

去除链接下划线

.toc-list .toc-list-item a {
    text-decoration: none; /* remove underline */
}

调整目录的位置

默认的目录位置是紧贴正文的,可以修改css中目录栏的上级.gh-sidebar的偏移量来调整目录的位置。我的目录原本在左侧紧贴正文,我将其向左偏移了px,仅供参考

@media (min-width: 1300px) {
    .gh-sidebar {
            position: absolute; 
            top: 0;
            bottom: 0;
            margin-top: 4vmin;
            grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
            margin-left: -192px; /* add offset */
        }

    .gh-toc {
        position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
        top: 4vmin;
    }
}

个人最终生成的代码

重点关注两个{{!– ToC –}}注释后的部分,其余的部分和casper的代码是一样的

  1<!-- default.hbs -->
  2<!DOCTYPE html>
  3<html lang="{{@site.locale}}">
  4<head>
  5
  6    {{!-- Basic meta - advanced meta is output with {{ghost_head}} below --}}
  7    <title>{{meta_title}}</title>
  8    <meta charset="utf-8">
  9    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 10    
 11    {{!-- Preload main styles and scripts for better performance --}}
 12    <link rel="preload" as="style" href="{{asset "built/screen.css"}}">
 13    <link rel="preload" as="script" href="{{asset "built/source.js"}}">
 14    
 15    {{!-- Fonts are preloaded and defined in the default template to avoid layout shift --}}
 16    {{> "typography/fonts"}}
 17
 18    {{!-- Theme assets - use the {{asset}} helper to reference styles & scripts, this will take care of caching and cache-busting automatically --}}
 19    <link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}">
 20
 21    {{!-- Custom background color --}}
 22    <style>
 23        :root {
 24            --background-color: {{@custom.site_background_color}}
 25        }
 26    </style>
 27
 28    <script>
 29        /* The script for calculating the color contrast has been taken from
 30        https://gomakethings.com/dynamically-changing-the-text-color-based-on-background-color-contrast-with-vanilla-js/ */
 31        var accentColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color');
 32        accentColor = accentColor.trim().slice(1);
 33
 34        if (accentColor.length === 3) {
 35            accentColor = accentColor[0] + accentColor[0] + accentColor[1] + accentColor[1] + accentColor[2] + accentColor[2];
 36        }
 37
 38        var r = parseInt(accentColor.substr(0, 2), 16);
 39        var g = parseInt(accentColor.substr(2, 2), 16);
 40        var b = parseInt(accentColor.substr(4, 2), 16);
 41        var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
 42        var textColor = (yiq >= 128) ? 'dark' : 'light';
 43
 44        document.documentElement.className = `has-${textColor}-text`;
 45    </script>
 46
 47    {{!-- TOC styles --}}
 48    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.css">
 49    
 50    <style>
 51    .gh-content {
 52        position: relative;
 53    }
 54
 55    .gh-toc > .toc-list {
 56        position: relative;
 57    }
 58
 59    .toc-list {
 60        overflow: hidden;
 61        list-style: none;
 62    }
 63
 64    @media (min-width: 1300px) {
 65        .gh-sidebar {
 66            position: absolute; 
 67            top: 0;
 68            bottom: 0;
 69            margin-top: 4vmin;
 70            grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
 71            margin-left: -192px; /* add offset */
 72        }
 73    
 74        .gh-toc {
 75            position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
 76            top: 4vmin;
 77        }
 78    }
 79
 80    .toc-list .toc-list-item a {
 81        text-decoration: none; /* remove underline */
 82    }
 83
 84    .gh-toc .is-active-link::before {
 85        background-color: var(--ghost-accent-color); /* Defines TOC accent color based on Accent color set in Ghost Admin */
 86    } 
 87    </style>
 88
 89    {{!-- This tag outputs all your advanced SEO meta, structured data, and other important settings, it should always be the last tag before the closing head tag --}}
 90    {{ghost_head}}
 91
 92</head>
 93<body class="{{body_class}} has-{{#match @custom.title_font "Elegant serif"}}serif{{else match @custom.title_font "Consistent mono"}}mono{{else}}sans{{/match}}-title has-{{#match @custom.body_font "Elegant serif"}}serif{{else}}sans{{/match}}-body">
 94
 95<div class="gh-viewport">
 96    
 97    {{> "components/navigation" navigationLayout=@custom.navigation_layout}}
 98
 99    {{{body}}}
100    
101    {{> "components/footer"}}
102    
103</div>
104
105{{#is "post, page"}}
106    {{> "lightbox"}}
107{{/is}}
108
109{{!-- Scripts - handle responsive videos, infinite scroll, and navigation dropdowns --}}
110<script src="{{asset "built/source.js"}}"></script>
111
112{{!-- Tocbot script --}}
113<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.min.js"></script>
114
115{{! Initialize Tocbot after you load the script }}
116<script>
117    tocbot.init({
118        // Where to render the table of contents.
119        tocSelector: '.gh-toc',
120        // Where to grab the headings to build the table of contents.
121        contentSelector: '.gh-content',
122        // Which headings to grab inside of the contentSelector element.
123        headingSelector: 'h1, h2, h3, h4',
124        // Ensure correct positioning
125        hasInnerContainers: true,
126        collapseDepth: 6,
127        orderedList: false,
128    });
129</script>
130
131{{!-- Ghost outputs required functional scripts with this tag, it should always be the last thing before the closing body tag --}}
132{{ghost_foot}}
133
134</body>
135</html>
136
137
138<!-- post.hbs -->
139{{!< default}}
140{{!-- The tag above means: insert everything in this file into the body of the default.hbs template --}}
141
142{{#post}}
143
144<main class="gh-main">
145
146    <article class="gh-article {{post_class}}">
147
148        <header class="gh-article-header gh-canvas">
149
150            {{#if primary_tag}}
151                <a class="gh-article-tag" href="{{primary_tag.url}}">{{primary_tag.name}}</a>
152            {{/if}}
153            <h1 class="gh-article-title is-title">{{title}}</h1>
154            {{#if custom_excerpt}}
155                <p class="gh-article-excerpt is-body">{{custom_excerpt}}</p>
156            {{/if}}
157
158            {{#if @custom.show_post_metadata}}
159            <div class="gh-article-meta">
160                <div class="gh-article-author-image instapaper_ignore">
161                    {{#foreach authors}}
162                        {{#if profile_image}}
163                            <a href="{{url}}">
164                                <img class="author-profile-image" src="{{img_url profile_image size="xs"}}" alt="{{name}}">
165                            </a>
166                        {{else}}
167                            <a href="{{url}}">{{> "icons/avatar"}}</a>
168                        {{/if}}
169                    {{/foreach}}
170                </div>
171                <div class="gh-article-meta-wrapper">
172                    <h4 class="gh-article-author-name">{{authors}}</h4>
173                    <div class="gh-article-meta-content">
174                        <time class="gh-article-meta-date" datetime="{{date format="YYYY-MM-DD"}}">{{date format="DD MMM YYYY"}}</time>
175                        {{#if reading_time}}
176                            <span class="gh-article-meta-length"><span class="bull">—</span> {{reading_time}}</span>
177                        {{/if}}
178                    </div>
179                </div>
180            </div>
181            {{/if}}
182
183            {{> "feature-image"}}
184
185        </header>
186
187        <section class="gh-content gh-canvas is-body{{#if @custom.enable_drop_caps_on_posts}} drop-cap{{/if}}">
188            <aside class="gh-sidebar"><div class="gh-toc"></div></aside> {{! The TOC will be inserted here }}
189            {{content}}
190        </section>
191
192    </article>
193
194    {{#if comments}}
195        <div class="gh-comments gh-canvas">
196            {{comments}}
197        </div>
198    {{/if}}
199
200</main>
201
202{{/post}}
203
204{{#if @custom.show_related_articles}}
205    {{#get "posts" include="authors" filter="id:-{{post.id}}" limit="4" as |next|}}
206        {{#if next}}
207            <section class="gh-container is-grid gh-outer">
208                <div class="gh-container-inner gh-inner">
209                    <h2 class="gh-container-title">Read more</h2>
210                    <div class="gh-feed">
211                        {{#foreach next}}
212                            {{> "post-card" lazyLoad=true}}
213                        {{/foreach}}
214                    </div>
215                </div>
216            </section>
217        {{/if}}
218    {{/get}}
219{{/if}}