안녕하세요. 달소입니다.
이번글은 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}}
by Maciej Nowakowski A Guide to Responsive Images with Ready-to-Use Te
수정후에 컨테이너만 재시작한번 시켜주시면됩니다.
워드프레스였으면 플러그인한방이면 됐을텐데.. 소스를 수정해야하긴하지만
공식 지원인점과 깔끔한 UI/속도 부분은 Ghost의 큰 강점이 아닐까 싶습니다.
참조 : https://ghost.org/tutorials/adding-table-of-contents/