Ghost 블로그에서 목차 만드는법.
안녕하세요. 달소입니다.
이번글은 Ghost 블로그에서 기본테마 Casper에 목차를 생성하는 방법입니다.
Ghost의 장점은 기본테마가 예쁘고 사용성이 높은것도 한몫하는데 여기에 공식 블로그에서 플러그인은 아니지만 추가 셋팅하는방법을 가이드해줘서 쉽게 적용했습니다.
목차 적용 전
휑함
목차 적용 후
헤딩값으로 작성된 항목에 대해서 좌측에 목차가 생성됩니다.
적용 방법
적용을 하기위해서는 사용하는 환경에 따라서 컨테이너나 서버의 content/theme/casper/로 이동한 뒤에 진행되는 작업들입니다.
전체 코드를 수정하려면 제 코드를 복붙하시면 됩니다.
vi default.hbs
<!DOCTYPE html> <html lang="{{@site.locale}}"{{#match @custom.color_scheme "Dark"}} class="dark-mode"{{else match @custom.color_scheme "Auto"}} class="auto-color"{{/match}}> <head> {{!-- Basic meta - advanced meta is output with {ghost_head} below --}} <title>{{meta_title}}</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="HandheldFriendly" content="True" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> {{!-- Theme assets - use the {asset} helper to reference styles & scripts, this will take care of caching and cache-busting automatically --}} <link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}" /> {{!-- TOC styles --}} <link rel="stylesheet" href="<https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.css>"> <style> .gh-toc { margin-top: 4vmin; /* Aligns the TOC with the beginning of content */ } @media (min-width: 1300px) { .gh-toc { position: sticky; /* On larger screens, TOC will stay in the same spot on the page */ top: 4vmin; grid-column: wide-start / main-start; /* Place the TOC to the left of the content */ } } .gh-toc > .toc-list { position: relative; overflow: hidden; } .toc-list { list-style: none; } .gh-toc .is-active-link::before { background-color: var(--ghost-accent-color); /* Defines TOC accent color based on Accent color set in Ghost Admin */ } </style> {{!-- 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 --}} {{ghost_head}} </head> <body class="{{body_class}}{{#match @custom.title_font "=" "Elegant serif"}} has-serif-title{{/match}}{{#match @custom.body_font "=" "Modern sans-serif"}} has-sans-body{{/match}}{{#if @custom.show_publication_cover}} has-cover{{/if}}{{#is "home"}}{{#unless @custom.show_logo_in_navigation}} no-logo{{/unless}}{{/is}}"> <div class="viewport"> <header id="gh-head" class="gh-head outer"> <nav class="gh-head-inner inner"> <div class="gh-head-brand"> <a class="gh-head-logo{{#unless @site.logo}} no-image{{/unless}}" href="{{@site.url}}"> {{#if @site.logo}} <img src="{{@site.logo}}" alt="{{@site.title}}" /> {{else}} {{@site.title}} {{/if}} </a> <a class="gh-burger" role="button"> <div class="gh-burger-box"> <div class="gh-burger-inner"></div> </div> </a> </div> <div class="gh-head-menu"> {{navigation}} </div> <div class="gh-head-actions"> <div class="gh-social"> {{#if @site.facebook}} <a class="gh-social-link gh-social-facebook" href="{{facebook_url @site.facebook}}" title="Facebook" target="_blank" rel="noopener">{{> "icons/facebook"}}</a> {{/if}} {{#if @site.twitter}} <a class="gh-social-link gh-social-twitter" href="{{twitter_url @site.twitter}}" title="Twitter" target="_blank" rel="noopener">{{> "icons/twitter"}}</a> {{/if}} </div> {{#if @site.members_enabled}} {{#unless @member}} <a class="gh-head-button" href="#/portal/signup" data-portal="signup">Subscribe</a> {{else}} <a class="gh-head-button" href="#/portal/account" data-portal="account">Account</a> {{/unless}} {{/if}} </div> </nav> </header> <div class="site-content"> {{!-- All other templates get inserted here, index.hbs, post.hbs, etc --}} {{{body}}} </div> {{!-- The global footer at the very bottom of the screen --}} <footer class="site-footer outer"> <div class="inner"> <section class="copyright"><a href="{{@site.url}}">{{@site.title}}</a> © {{date format="YYYY"}}</section> <nav class="site-footer-nav"> {{navigation type="secondary"}} </nav> <div><a href="https://ghost.org/" target="_blank" rel="noopener">Powered by Ghost</a></div> </div> </footer> </div> {{!-- /.viewport --}} {{!-- Scripts - handle member signups, responsive videos, infinite scroll, floating headers, and galleries --}} <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"> </script> <script src="{{asset "built/casper.js"}}"></script> <script> $(document).ready(function () { // Mobile Menu Trigger $('.gh-burger').click(function () { $('body').toggleClass('gh-head-open'); }); // FitVids - Makes video embeds responsive $(".gh-content").fitVids(); }); </script> {{!-- Tocbot script --}} <script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.min.js"></script> {{! Initialize Tocbot after you load the script }} <script> tocbot.init({ // Where to render the table of contents. tocSelector: '.gh-toc', // Where to grab the headings to build the table of contents. contentSelector: '.gh-content', // Which headings to grab inside of the contentSelector element. headingSelector: 'h1, h2, h3, h4', }); </script> {{!-- Ghost outputs required functional scripts with this tag - it should always be the last thing before the closing body tag --}} {{ghost_foot}} </body> </html>
vi post.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 post --}} <main id="site-main" class="site-main"> <article class="article {{post_class}} {{#match @custom.post_image_style "Full"}}image-full{{else match @custom.post_image_style "=" "Small"}}image-small{{/match}}"> <header class="article-header gh-canvas"> <div class="article-tag post-card-tags"> {{#primary_tag}} <span class="post-card-primary-tag"> <a href="{{url}}">{{name}}</a> </span> {{/primary_tag}} {{#if featured}} <span class="post-card-featured">{{> "icons/fire"}} Featured</span> {{/if}} </div> <h1 class="article-title">{{title}}</h1> {{#if custom_excerpt}} <p class="article-excerpt">{{custom_excerpt}}</p> {{/if}} <div class="article-byline"> <section class="article-byline-content"> <ul class="author-list"> {{#foreach authors}} <li class="author-list-item"> {{#if profile_image}} <a href="{{url}}" class="author-avatar"> <img class="author-profile-image" src="{{img_url profile_image size="xs"}}" alt="{{name}}" /> </a> {{else}} <a href="{{url}}" class="author-avatar author-profile-image">{{> "icons/avatar"}}</a> {{/if}} </li> {{/foreach}} </ul> <div class="article-byline-meta"> <h4 class="author-name">{{authors}}</h4> <div class="byline-meta-content"> <time class="byline-meta-date" datetime="{{date format="YYYY-MM-DD"}}">{{date}}</time> {{#if reading_time}} <span class="byline-reading-time"><span class="bull">•</span> {{reading_time}}</span> {{/if}} </div> </div> </section> </div> {{#match @custom.post_image_style "!=" "Hidden"}} {{#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}} {{/match}} </header> <section class="gh-content gh-canvas"> <div class="gh-toc"></div> {{! The TOC will be inserted here }} {{content}} </section> {{!-- <section class="article-comments gh-canvas"> If you want to embed comments, this is a good place to paste your code! </section> --}} </article> </main> {{!-- A signup call to action is displayed here, unless viewed as a logged-in member --}} {{#if @site.members_enabled}} {{#unless @member}} {{#if access}} <section class="footer-cta outer"> <div class="inner"> {{#if @custom.email_signup_text}}<h2 class="footer-cta-title">{{@custom.email_signup_text}}</h2>{{/if}} <a class="footer-cta-button" href="#/portal" data-portal> <div class="footer-cta-input">Enter your email</div> <span>Subscribe</span> </a> {{!-- ^ This looks like a form element, but it's just a link to Portal, making the form validation and submission much simpler. --}} </div> </section> {{/if}} {{/unless}} {{/if}} {{!-- Read more links, just above the footer --}} {{#if @custom.show_recent_posts_footer}} {{!-- The {#get} helper below fetches some of the latest posts here so that people have something else to read when they finish this one. This query gets the latest 3 posts on the site, but adds a filter to exclude the post we're currently on from being included. --}} {{#get "posts" filter="id:-{{id}}" limit="3" as |more_posts|}} {{#if more_posts}} <aside class="read-more-wrap outer"> <div class="read-more inner"> {{#foreach more_posts}} {{> "post-card"}} {{/foreach}} </div> </aside> {{/if}} {{/get}} {{/if}} {{/post}}
수정후에 컨테이너만 재시작한번 시켜주시면됩니다.
워드프레스였으면 플러그인한방이면 됐을텐데.. 소스를 수정해야하긴하지만
공식 지원인점과 깔끔한 UI/속도 부분은 Ghost의 큰 강점이 아닐까 싶습니다.
참조 : https://ghost.org/tutorials/adding-table-of-contents/