<![CDATA[Tutorials]]>https://ghost.org/tutorials/https://ghost.org/tutorials/favicon.pngTutorialshttps://ghost.org/tutorials/Ghost 5.8Mon, 08 Aug 2022 19:42:51 GMT60<![CDATA[How to add a tip jar]]>https://ghost.org/tutorials/tip-jar/62dada1fba1f5b003d80b620Fri, 22 Jul 2022 19:21:24 GMT

Offer your most engaged readers multiple ways to support your content by adding a tip jar to your Ghost membership site with Stripe.

This tutorial will walk you through the steps of setting up the payment link in Stripe and how to add the link to your Ghost site as a navigation item, a page, and a content snippet.

Log in to your stripe account and go to Payments → Payment links. Click New to create a new payment link.

How to add a tip jar

On the Create payment link page, select Let customers choose what to pay from the dropdown menu.

How to add a tip jar

Fill in the details for your payment link. These details are entirely up to you. When everything looks good, click Create link.

How to add a tip jar

Your payment link is now created. Click Copy to get the URL that we’ll use with Ghost.

How to add a tip jar

When a visitor clicks the link, they’ll be taken to the Stripe checkout page, where they can enter their details and complete the payment.

How to add a tip jar

Anywhere you can put a URL, you can use your payment link — we’re going to focus on adding it as a navigation item, a new page, and a snippet.

If you’ve ever added a navigation item to your Ghost site, then you’re already ahead of the curve on this one, because the process with a payment link is exactly the same.

Log into your Ghost site and go to Navigation. Add the link text and payment link URL. Click Save.

How to add a tip jar

That’s it! Your tip jar is now live and active 🎉

How to add a tip jar

Page

Maybe you want to make more of a case for a tip than just a navigation item. Another option to showcase your payment link is to create a new page in Ghost.

Go to Pages → New page. The design and content of the page are up to you. In the example below, we created a fictional publication, Coffee Talk. The page includes some copy along with a Button card that uses the Stripe payment link.

How to add a tip jar

Publish the page and add it to the navigation bar, like in the previous section, or share it in newsletters, on social media, or wherever there’s an opportunity for support.

Create a content snippet

In Ghost, snippets are user-defined reusable bits of content. With them, you can easily copy and paste content across different posts and pages.

Here, they’re perfect for quickly adding a tip jar at opportune moments in your posts.

Using our Coffee Talk publication from above, let’s say we just shared a technique for brewing the most insane cup of coffee ever known. Now’s as good a time as any to make a call for action.

How to add a tip jar

For this CTA, we used the Header card, which adds a beautiful, full-width element to the page with a customizable button (where we included the Stripe payment link).

To be able to use this on other posts and pages, we can turn the card into a snippet (which works for any post content — not just Header cards).

0:00
/

To create a snippet, select content and click the create snippet icon. Enter a name for your snippet and hit Save. Your snippet is now available on any post or page from the card menu 🤯

Summary

Tip jars offer your visitors a simple way to contribute to your content financially with a one-off payment, and thanks to Stripe and Ghost, this is easy to set up for your publication

How did you implement a tip jar on your website? Join the Forum to share your witty copy, ask questions, or just see what other Ghost users are up to.

]]>
<![CDATA[Open a theme in a code editor]]>https://ghost.org/tutorials/open-a-theme-in-a-code-editor/627e68b8d0ced4003d5a3b70Sun, 22 May 2022 19:48:56 GMT

Using a code editor makes customizing your Ghost theme a much more enjoyable experience. It provides creature comforts like line numbers, syntax highlighting, automatic formatting, error checking, and much more.

Open a Ghost theme in VS Code

Download and install VS Code. Then, to open a theme in VS Code, unzip the theme and go to File Open Folder. Choose the theme folder and click Open.

You can also drag the folder from the Desktop or file system directly into VS Code.

0:00
/

Command-line shortcut

With VS Code, you can quickly open a theme (or any folder) right from the command line.

Run code . from the theme folder to open the theme in the code editor.

0:00
/

Summary

Now that you know how to open a theme in a code editor, the world is your oyster – at least in terms of making beautiful themes 💅

If you're working on an official Ghost theme, check out our guide on editing CSS. Otherwise, a great first step is to create a custom post template.

Whatever you get up to, come chat about it with the Ghost community – we're a fun bunch who love to talk about all things Ghost.

]]>
<![CDATA[Create a custom post template]]>https://ghost.org/tutorials/create-a-custom-post-template/6286a10bc115a3003d2fa95bFri, 20 May 2022 19:43:07 GMT

It likely doesn't surprise you, but every article you publish looks the same. Because each one, without fail, uses the same post template. And, 99% of the time – that's precisely what you want: consistent styling across your publication.

But what about that 1%? What if you publish a limited-run series that needs bespoke styling? What if you want to omit the author or other metadata from certain posts? Or what if you want to load a library to show graphs and charts?

Ghost makes it easy to create custom templates for just such occasions. This tutorial will walk you through the steps to create them.

Create a template file

Our publication, History & Design, is launching a limited-run series on Midcentury modern design to promote a forthcoming book on the subject. To make these posts stand out from our usual ones, we're going to create a custom template.

Create a custom post template

The first step is to open your theme in a code editor. Rather than creating a new file from scratch, jumpstart the custom template creation process by copying the existing post.hbs file. This way, we'll have a base from which to start.

Rename the file custom-midcentury-modern.hbs.

.
├── LICENSE
├── README.md
├── assets
├── author.hbs
├── custom-midcentury-modern.hbs
├── default.hbs
├── error-404.hbs
├── error.hbs
├── gulpfile.js
├── index.hbs
├── package.json
├── page.hbs
├── partials
├── post.hbs
├── tag.hbs
└── yarn.lock
Theme files with custom template

The filename is essential. The custom- prefix tells Ghost that the file is a custom template, which makes it selectable in the Ghost Editor. Everything after the initial hyphen - specifies what authors see in the Editor's Template dropdown menu. In this example, our custom template appears as "Midcentury Modern."

Create a custom post template

Now, the author can easily choose our big splashy custom template for these special posts.

Designing the custom template

Because we just copied our current post template for our custom one, we don't actually yet have our new design.

Let's leverage Ghost's Header card to help us make the custom template. The full-width Header card features customizable sizes, text, backgrounds, and an optional call-to-action button.

This is a Header card

It really catches your attention, right?

Learn more

By substituting our existing post header with the Header card, we'll get an easy, eye-catching custom template

Editing the post template

For this example, we'll use the default Ghost theme Casper, but you can adapt this technique for any theme.

Open custom-midcentury-modern.hbs in a code editor and remove the entire <header> element.

To remove the gap between the navbar and Header card, add style="padding-top: 0" to the <article> tag.

The complete custom template now looks like this:

{{!< 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_width "Full"}}image-full{{else match @custom.post_image_width "=" "Small"}}image-small{{/match}}" style="padding-top: 0;">

        <section class="gh-content gh-canvas">
            {{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 --}}
{{#match @custom.email_signup_for_logged_out_visitors "!=" "None"}}
{{#unless @member}}{{#if access}}
    <section class="footer-cta {{#match @custom.email_signup_for_logged_out_visitors "Bottom of post"}}cta-alt{{/match}}">
        <div class="inner">
            {{#if @custom.email_signup_text}}<h2>{{@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}}
{{/match}}


{{!-- Read more links, just above the footer --}}
{{#if @custom.show_recent_posts}}
    {{!-- 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}}" include="authors" limit="3" as |more_posts|}}

        {{#if more_posts}}
            <aside class="read-more-wrap">
                <div class="read-more inner">
                    {{#foreach more_posts}}
                        {{> "post-card"}}
                    {{/foreach}}
                </div>
            </aside>
        {{/if}}

    {{/get}}
{{/if}}

{{/post}}
custom-midcentury-modern.hbs

Just zip up your theme files and upload them to Ghost. On any post, you'll now have the option of selecting your custom template. Create your Header card in the post editor and your custom template is ready for publication 🥳

Summary

From limited-run series, as in our example, to tweaks, additions, and complex integrations, custom templates are an easy, elegant solution that you now know how to implement 💪

Because the possibilities for custom templates are seemingly limitless, we'd love to see what you've created. Come show and tell with the whole Ghost community over on our Forum. It's a fantastic place to get help and meet fellow Ghost users.

]]>
<![CDATA[How to use Code Injection]]>https://ghost.org/tutorials/use-code-injection-in-ghost/6282f0c09514a4003d52f9c0Thu, 19 May 2022 18:42:27 GMT

Code Injection offers an interface for easily adding analytics, styles, custom fonts, meta tags, and scripts to a Ghost site. It's also the perfect tool for making minor edits to your theme or adding some cool effects with JavaScript. In this tutorial, learn how as well as everything you need to know about using Code Injection in Ghost.

What's Code Injection?

As the name hints, Code Injection injects code into your Ghost site. Access Code Injection from SettingsCode Injection.

How to use Code Injection

On the Code Injection page, there are two areas: Site Header and Site Footer. Ghost injects any text entered into these boxes onto every page of your site.

💡
Code Injection is also available on a per post basis via the Ghost Editor's sidebar. It works the same way as explained above but is only added to that particular post's page.

Site Header code is injected into the <head> tag. Site Footer code is injected before the closing </body> tag. Both are added after other styles and scripts used by your theme.

<html>
  <head>
    <title>Page Title</title>
      
    <!-- Code Injection Site Header added here -->  
  </head>
  <body>
  <!-- Your beautiful content -->

  <!-- Code Injection Site Footer added here -->    
  </body>
</html>

Add CSS to the Site Header

One of the most common use cases for Code Injection is to add CSS to your Ghost site to customize the look and feel of your theme.

Let's say we had a site about animals and were using the Casper theme.

How to use Code Injection

Everything's looking pretty good, but let's add some fun by using a custom typeface from Google Fonts. Find the font you want to use – for our animals site, we're going to use a font called "Luckiest Guy." Click Select this style to choose the styles and weights. Then, click the icon in the top right to view the selected font family.

How to use Code Injection

On the Selected family sidebar, find the Use on the web box.

How to use Code Injection

Copy the <link> tags and paste them into the Site Header.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Luckiest+Guy&display=swap" rel="stylesheet">
Code Injection → Site Header

Next, copy the CSS rules. Whenever you add CSS rules to the Site Header, wrap them in a <style> tag. We only want our wacky new font to affect our headings, so we'll only apply it to them. And, since this font only has a single weight, we'll specify that, too.

<style>
    h1, h2, h3, h4, h5, h6 {
        font-family: 'Luckiest Guy', sans-serif;
        font-weight: 400;
    }
</style>
Code Injection → Site Header

That's it! The Code Injection is ready to be saved.

How to use Code Injection

And so is our animals site – but now with a funky new font 💅

How to use Code Injection

The second most common use case for Code Injection is to add JavaScript to a Ghost site. When adding JS, add it to the Site Footer so that it loads properly.

As an example, let's add a typewriter effect to our animals site. To help us along, we'll use the open-source TypewriterJS library.

Load the main script into the Site Footer.

<script src="https://unpkg.com/typewriter-effect@latest/dist/core.js"></script>
Code Injection → Site Footer

Next, configure the TypewriterJS script and start it on the page. Whenever you add JS via Code Injection, wrap it in a <script> tag.

<script>
    const app = document.querySelector('.site-title + p');

    const typewriter = new Typewriter(app, {
      loop: true,
      delay: 75,
    });

    typewriter
      .typeString('356 of the best <strong>monkeys</strong>')
      .pauseFor(1000)
      .deleteChars(7)
      .typeString('<strong>parrots</strong>')
      .pauseFor(750)
      .deleteChars(7)
      .typeString('<strong>sharks</strong>')
      .pauseFor(500)
      .deleteChars(6)
      .typeString('<strong>snakes</strong>')
      .pauseFor(500)
      .deleteChars(7)
      .typeString('<strong>animals</strong> on the web')
      .pauseFor(1500)
      .deleteAll(50)
      .start();
</script>
Code Injection → Site Footer

All set! The Site Footer is ready to be saved.

How to use Code Injection

And our animals site just got a little wilder 🦁

0:00
/

Summary

Code Injection is a powerful and convenient tool for quickly adding CSS, JS, and more to your Ghost site.

Many of Ghost's best Integrations rely on Code Injection – and now that you're an expert with it – you're more than ready to add one to your publication.

Ghost integrations – official apps, plugins & tools
Ghost plugins, tools & apps to integrate withh your Ghost site for automation, analytics, marketing, support and much more! 👉
How to use Code Injection
]]>
<![CDATA[Download a code editor]]>https://ghost.org/tutorials/download-a-code-editor/62858a6dc115a3003d2fa6efThu, 19 May 2022 01:38:40 GMT

Whether you're making a small tweak to your routes or a gargantuan edit to a theme, a code editor is the right tool for the job. In this tutorial, learn how to download the free code editor, Visual Studio Code, and use it to open a Ghost theme.

Download VS Code

Microsoft's VS Code is an industry-leading, free code editor. It has loads of features to improve the code writing experience.

Download the version for your OS.  

Download a code editor

Open the downloaded file and follow the instructions to install the code editor on your system.

For an overview of the program, check out VS Code's Getting Started video.

Summary

With VS Code downloaded on your machine, you're now equipped to edit theme files to customize a theme 🛠️

We're excited to see what you get up to, so be sure to let us know in the Ghost Forum. It's a great place to learn from the community and see what we're up to.

]]>
<![CDATA[Download and upload a theme]]>https://ghost.org/tutorials/download-and-upload-a-theme/62856a1ec115a3003d2fa63aThu, 19 May 2022 00:06:10 GMT

Whether you want to customize an existing theme or upload one you created, download and upload themes quickly from the Ghost Admin.

Download a theme

From your dashboard, go to Settings.

Download and upload a theme

Next, go to the Design page.

Download and upload a theme

Click Change theme.

Download and upload a theme

Choose the Advanced toggle.

Download and upload a theme

Next to the theme you want to download, click the overflow menu (...), then Download. A copy of the theme will be downloaded to your computer 🥳

Download and upload a theme

Upload

On the Themes page, click Upload theme.

Download and upload a theme

Select the theme file to upload from your computer. After the theme uploads, choose whether you want to activate it or not. Your theme is now updated!

Download and upload a theme

Summary

You now know how to download and upload Ghost themes. That means you're well on your way to theme customization. Explore more of our tutorials to learn what's possible or head on over to our Forum and meet the Ghost community.

]]>
<![CDATA[Implementing redirects]]>https://ghost.org/tutorials/implementing-redirects/62852df5c115a3003d2fa46aWed, 18 May 2022 20:14:53 GMT

Redirects forward one URL to another. They are commonly used when removing or moving content on your site, fixing broken links, or migrating content between different domains.

In Ghost, redirects are configured by adding rules to the redirects.yaml file. The basic process is to download this file from Ghost Admin, edit the file in a code editor, and then re-upload it to the admin interface.

This tutorial will walk you through:

  • When not to use the redirects.yaml file 🙃
  • Accessing the redirects.yaml file and its basic structure
  • How to create your own redirects with common examples for reference
  • Implementing your new redirects
  • Some tips for getting started with regular expressions

When not to use redirects.yaml

Before we get started, make sure you are not trying to implement some common patterns where it is not necessary or advised to use the redirects.yaml file:

  • Page rules for www or HTTP/HTTPS redirection should always be implemented with your DNS provider.
  • Ghost automatically forces trailing slashes, so you do not need to write any page rules to accommodate for duplicate content caused by this.
  • Use dynamic routing to change the URL structure of your publication, for example, to change /tag/ to /topic/.

Accessing the redirect file in Ghost

If you're creating redirects for the first time, create a file called redirects.yaml in your code editor. Upload (and download) the redirects.yaml file from Ghost Admin (Settings Labs).

Implementing redirects

File structure

redirects.yaml has two keys: 301 and 302. Each key contains a list of redirects. The 301 key contains the permanent 301 redirects. The 302 key contains the temporary 302 redirects. A new Ghost publication has no redirects by default.

Entries to the redirects file follow this structure:

301:
  /permanent-redirect-from: /permanent-redirect-to
  /permanent-redirect-from-2: /permanent-redirect-to-2

302:
  /temporary-redirect-from: /temporary-redirect-to

Enter multiple entries on separate lines. Redirecting to external URLs is possible. It's also possible to use regular expressions.

Creating redirects in redirects.yaml

Create redirects by listing the source URL and the destination URL, separated by a colon.

The following examples demonstrate common use cases for redirects in Ghost.

⚡️ Redirect an old URL to a new one

If you update or remove a URL, it's best practice to redirect it. This prevents broken links and your visitors from landing on error pages. It also informs search engines that a page has changed, which is beneficial for your site's SEO.

For example, to redirect domain.com/old-postname/ to domain.com/new-postname/, include the following in redirects.yaml:

301:
  /old-postname/: /new-postame/

⚡️ Redirect your post structure from an existing domain

Redirects are a good option if you're migrating content over from an existing site that used a different post structure than Ghost does.

Here are some common examples of restructuring a Ghost publication:

  • Category to tag - from domain.com/category/category-slug/ to domain.com/tag/tag-slug/
  • Date to post name - from domain.com/blog/year/month/day/post-slug/ to domain.com/post-slug/
  • Search labels - from domain.com/search/label/ to domain.com/tag/tag-name

With these redirects, someone following an old link will still end up in the right place because they'll be redirected to the new content.

⚡️ Fixing URL discrepancies

As a site grows, content can be duplicated and URLs can change. Consolidate multiple versions of the same URL with redirects. In the example below, we redirect the old URLs editing-a-post and editing-posts to editing, the new one.

301:
  /editing-a-post: /editing
  /editing-posts: /editing

Implementing redirects in Ghost

Once you have created your redirects by editing your redirects.yaml file, upload it in Ghost Admin.

Once the file is in place, test your redirects by visiting any URL that you redirected. Using the example above, visiting /editing-posts will take you to /editing in the browser.

Using regular expressions

Use regular expressions (or regex) to write redirects with advanced pattern matching.

A common use case is when migrating content from a platform with a different URL structure. For example, let's say posts used to live on /blog/post-slug but now in Ghost live at /posts-slug. While it's possible to write each redirect on a new line, we can use regular expressions to match all possibilities in one line.

301:
  /blog/post-1: /post-1
  /blog/post-2: /post-2
  /blog/post-3: /post-3
Individual redirects
301:
  ^\/blog\/([a-z0-9-]+)\/$: /$1
Redirect with regex

The regex matches what comes after /blog/ like post-1 and redirects to it ($1 represents the match). With this in place, you can redirect all existing URLs without writing them out one by one.

As powerful as regex are, writing them is a bit of an art. The best resource to assist with designing regular expressions for a wide variety of use cases is regex101. Use this tool to test out your redirect regular expressions. Not only does this tool show if you're pattern works or not, but it'll also explain how it works. (Be sure to choose ECMAScript/JavaScript as the flavor.)

Summary

That’s it. You have discovered the recommended process for implementing redirects in Ghost:

  1. Create a redirects.yaml file or download it from Ghost Admin
  2. Edit the file to add new redirection rules
  3. Upload the file in Ghost Admin

This process can be repeated as often as required. All of your redirects will always be stored in one accessible place and are always managed and owned by you.

Have questions about more complex redirects or want to show off some crazy regex you wrote? Come on over to our Forum and have a chat.

]]>
<![CDATA[The complete guide to comments]]>https://ghost.org/tutorials/adding-comments/627d7639d0ced4003d5a3b28Thu, 12 May 2022 21:32:49 GMT

Give the people a voice and let them reply to your article directly with comments.

Ghost integrates with a variety of commenting platforms so that you can find a platform that meets your publication’s needs. Check out our Community Integration page to find guides on integrating your chosen platform with Ghost.

This tutorial will cover the steps you’ll take to connect most commenting platforms to your Ghost site.

The complete guide to comments
Using Disqus commenting platform on Casper

Add the code

Open a copy of your theme in the code editor and go to the post.hbs file.

🚁
Need help? See our guides on downloading a theme and opening it in a code editor.

If you’re using Casper, you’ll see the following section of code (beginning at line 76):

{{!--
  <section class="article-comments gh-canvas">
    If you want to embed comments, this is a good place to paste your code!
  </section>
--}}

Other themes might do things differently. For example, Ghost’s Ease theme uses a partial called comment.hbs. No matter these differences, the takeaway here is that you’re looking for the spot on your post page where you want your comments to appear. Generally, this will be after your main content.

The next step is to add the code for your commenting platform to this location in post.hbs. Navigate to the tool you want to use within our Community Integrations for more details on how to do this or follow the instructions provided by your commenting platform.

Once the commenting code is added, zip up your theme files and upload them to your Ghost site.

Connect and verify

Verify that the commenting tool works by testing it yourself. Navigate to one of your published posts and submit a comment – maybe something about how great your publication is 😉 – if the comment appears as expected, you’re all good to go!

Members only

With Ghost, not only can you add the commenting platform of your choice, but you can also restrict access to your publication’s members. All in one extra line of code.

Wrapping your comments section with the access helper will restrict comments to members only.

{{#if *access*}}
  <!-- Your commenting service -->
{{else}}
  <p>You're missing out on the conversation. Become a member and join in.</p>
  <a href="#/portal/">Subscribe</a>
{{/if}}

Demo

In this demo, we added Disqus comments to Casper:

Post with Comments
Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of
The complete guide to comments

Summary

Woop! You’ve now added commenting to your Ghost site, and even found a way to scope commenting to your closed group of members. And, if you’re eager to keep on commenting, come over to our Forum, where the conversation is nothing short of lively.

]]>
<![CDATA[Change the URL for tags and authors]]>https://ghost.org/tutorials/change-taxonomy-url/627d4e63d0ced4003d5a3ad9Thu, 12 May 2022 19:23:09 GMT

A taxonomy classified things based on a common relation. Ghost uses two taxonomies to classify posts: authors and tags. The authors taxonomy groups posts by their author.

For example, Ghost automatically makes all posts by Jane Austen available at sitename.com/author/jane-austen/. Similarly, the tags taxonomy groups posts by their tag, and Ghost automatically makes all posts tagged with podcast available at sitename.com/tag/podcast/.

These taxonomies save you the trouble of creating archive pages for every author and tag, and, most of the time, they’re exactly what you need.

But what if you want to customize these taxonomies? What if, instead of post authors, you have post contributors? Instead of tags, you have topics?

In this tutorial, we’ll show you how to define custom taxonomies, transforming /author/ and /tag/ into new terms, with a few lines of code ⚡

Define your taxonomies in the routes.yaml file

Download your routes.yaml file from the SettingsLabs in Ghost Admin.

This file is split into three sections, and for this tutorial, you’ll be using the “taxonomies” section. Read more about dynamic routing for an overview of the rest of the file.

This is the default taxonomy configuration:

taxonomies:
  tag: /tag/{slug}
  author: /author/{slug}

By changing the permalink structure – updating the values to the right of the colon –  you can customize the taxonomy to suit your publication’s needs:

taxonomies:
  tag: /topic/{slug}
  author: /contributor/{slug}

Save this file and upload it to your Ghost site. Your new taxonomy is now live! For example, posts tagged with travel now show up at site.com/topic/travel/, rather than /tag/travel. Similarly, author pages now show up at site.com/contributor/author-slug/, rather than /author/author-slug.

Pretty neat, right?

Summary

You’ve successfully updated your taxonomies and permalinks for tags and authors on your Ghost publication 🥳

Be sure to share your sweet new taxonomies over on our Forum – our community is always excited to see how people are using Ghost.

]]>
<![CDATA[How to add social media icons to your theme]]>https://ghost.org/tutorials/add-social-media-icons/627d04c9d0ced4003d5a3a68Thu, 12 May 2022 14:57:18 GMT

With a few quick theme edits, you can add any social media icon to your Ghost theme. This tutorial will show you how.

📢
This tutorial uses the default Ghost theme, Casper, as a working example. However, the techniques presented below can be applied to any Ghost theme.
How to add social media icons to your theme

Prepare your theme for new icons

Find the page in your theme where your icons should appear. Generally, you can look for where the theme loads the navigation bar that's present on each page.

🚦
You'll need to know how to download a local copy of your theme and open it in a code editor for this tutorial. Click the links for quick how-tos, so you can get back to adding icons!

In Casper, we'll add our icons todefault.hbs. Open this file and look for <div class="gh-head-actions">. In our example, we'll add icons for Twitter and Instagram, but you can adjust the snippet to add whichever icons you want by updating the URLs.

Add the following code on the line after <div class="gh-head-actions">.

<a class="social-media-icon" href="https://twitter.com/USERNAME">

</a>
<a class="social-media-icon" href="https://instagram.com/USERNAME">

</a>
default.hbs

Get your new icons

So far, we've prepared the file for icons, but we haven't actually added them. For that, we're going to use Simple Icons, which is a fantastic resource for getting SVG icons for any and all social media networks. Using SVG icons is recommended because they are lightweight, look good at any resolution, and can be styled with CSS.

Search for twitter. Click the icon to copy the SVG code to your clipboard.

0:00
/

Paste the SVG into the template file, between the anchor tags <a>SVG here</a>.

<a class="social-media-icon" href="https://twitter.com/ghost">
    <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>
</a>
Twitter icon added

Repeat the process for other icons you want to add. After adding Instagram, the entire default.hbs file looks like this:

<!DOCTYPE html>
<html lang="{{@site.locale}}"{{#match @custom.color_scheme "Dark"}} class="dark-mode"{{/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"}}" />

    {{!-- 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-image{{/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">
                <a class="social-media-icon" href="https://facebook.com/me">
                    <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>
                </a>
                <a class="social-media-icon" href="https://instagram.com/me">
                    <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Instagram</title><path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678c-3.405 0-6.162 2.76-6.162 6.162 0 3.405 2.76 6.162 6.162 6.162 3.405 0 6.162-2.76 6.162-6.162 0-3.405-2.76-6.162-6.162-6.162zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405c0 .795-.646 1.44-1.44 1.44-.795 0-1.44-.646-1.44-1.44 0-.794.646-1.439 1.44-1.439.793-.001 1.44.645 1.44 1.439z"/></svg>
                </a>
                {{#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}}
            </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> &copy; {{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>

{{!-- Ghost outputs required functional scripts with this tag - it should always be the last thing before the closing body tag --}}
{{ghost_foot}}

</body>
</html>

Zip and upload

Zip up the entire contents of your theme folder. Upload the new zip file to Ghost.

Style the icons

Finally, we'll use Code Injection to style the icons. See this guide if you're unfamiliar with Ghost's Code Injection feature.

In Code Injection Site Header, add the following code snippet:

<style>
.gh-head-actions {
	gap: 1em;
}

.social-media-icon svg {
	width: 1.8em;
	fill: white;
}
</style>

Adjust the width value to change the icon's size. Update the fill value to change the icon's color.

How to add social media icons to your theme
icon styles

Click Save. Refresh your site and behold your snazzy new icons ✨

Summary

With a few edits to Casper, you now have a customized theme that puts your social media icons front and center. What’s more, the techniques you learned here are applicable to any Ghost theme. If you added icons to another theme, share it with the whole Ghost community over on the Forum. It’s also a fantastic place to get help and meet other people using Ghost.

]]>
<![CDATA[A complete guide to code snippets]]>https://ghost.org/tutorials/code-snippets-in-ghost/627c07c6a64fff003d85be94Wed, 11 May 2022 19:10:16 GMT

While we can’t fix the bugs in your code, we can show you how to make it look spectacular. In this tutorial, learn how to create code blocks in Ghost and beautify them using the Prism syntax highlighting library.

Create a code block

Before we highlight our code, we need some code to highlight. To add a block of code to a post or page, type three backticks (```) followed by the name of the coding language and hit tab or enter. For example, to add a block of javascript, write: ```javascript. (You can also add a language after the fact by typing it into the top right corner of the code block.)

0:00
/

Add syntax highlighting

Syntax highlighting is the application of styling to your code based on its meaning. It’s not just an aesthetic improvement – it also improves its readability.

We’re going to use the Prism library to add syntax highlighting using two different techniques: Code Injection and theme customization. While both techniques will yield the same result, the second, more complicated one offers more options for customization.

Add Prism via Code Injection

To function, Prism requires JavaScript and CSS. We’re going to load these resources via Code Injection using a CDN. At the time of writing, Prism is on version 1.28.0. You’ll want to use the latest version available.

Prism offers several color themes. Load the CSS for the theme you prefer. We’ll load the Tomorrow Night theme via the Site Header:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer" />

When using a CDN, Prism also recommends using their autoloader script, which will automatically load the languages you need. For example, if you have JS and Python code snippets, the autoloader will ensure that those languages are highlighted properly. Add links to the JS files (prism-core.min.js and prism-autoloader.min.js) in the Site Footer:

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/components/prism-core.min.js" integrity="sha512-9khQRAUBYEJDCDVP2yw3LRUQvjJ0Pjx0EShmaQjcHa6AXiOv6qHQu9lCAIR8O+/D8FtaCoJ2c0Tf9Xo7hYH01Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-fTl/qcO1VgvKtOMApX2PdZzkziyr2stM65GYPLGuYMnuMm1z2JLJG6XVU7C/mR+E7xBUqCivykuhlzfqxXBXbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
A complete guide to code snippets

Save – and that’s it! Add a code block to the editor and publish. Your code snippets will now be looking gorgeous 💅

A complete guide to code snippets

Add Prism via theme customization

By adding Prism directly to your theme files, you have more options when it comes to styling the syntax highlighting.

Go to the Prism download page. Choose your theme and the languages you want to be supported. Scroll to the bottom of the page and download the JS and CSS.

We’re going to add these files to the Casper theme, but the process can easily be adapted to work with any Ghost theme.

Add the CSS

Add prism.css to assets/css. (There will be two other CSS files along with it: screen.css and global.css.)

Open screen.css in your code editor and import the new CSS file.

/* Import CSS reset and base styles */
@import "global.css";
@import "prism.css";

Add the JS

Add prism.js to assets/js/lib. (There’ll be another file along with it: jquery.fitvids.js.)

Upload the new theme

💡
If you want to give your theme a name to better differentiate it rom Casper, change the name field in package.json.

Recompile your assets by running yarn zip or npm run zip. (If you haven’t already installed the theme’s dependencies, you’ll need to run yarn or npm install first.)

Finally, upload the theme file to your site. Add a code block, publish, and bask in all your syntax-highlighted glory!

Style Prism

Because you’re loading the styles for Prism yourself, you have two options for customization.

First, you can edit the prism.css file and update values to better match your aesthetic. For example, here’s the rule for formatting booleans, number, and functions.

.token.boolean,
.token.number,
.token.function {
     color: #f08d49;
 }

Currently, it’s set to render as royal orange. You can update the color to be whatever value you want.

💡
On the Prism download page, check Development version at the top of the page to get an unminified CSS file that’s easier to edit.

Second, by self-loading, you also have access to a wider selection of themes made by the Prism community. To use one, just copy the CSS into your prism.css file, recompile, and upload your theme.

GitHub - PrismJS/prism-themes: A wider selection of Prism themes
A wider selection of Prism themes. Contribute to PrismJS/prism-themes development by creating an account on GitHub.
A complete guide to code snippets

For example, here’s the Synthwave ‘84 theme. (In some cases, you may need to modify your theme’s styling so it doesn’t conflict with Prism’s.)

A complete guide to code snippets

Summary

You now know two techniques for adding syntax highlighting to your Ghost site, so there’s nothing stopping you from showing off your tech skills with the world. What’s more, Prism offers a full library of plugins that you can use to extend its functionality: highlight specific lines, add a copy button, and more.

Are you a developer writing about Ghost? We’d love to read it. Come over to our Forum and share your beautiful code snippets with the community.

]]>
<![CDATA[How to add a table of contents]]>https://ghost.org/tutorials/adding-table-of-contents/627bd5216180a0003dde1bf4Wed, 11 May 2022 15:37:48 GMT

Having a table of contents on your site is a nice creature comfort for post readers – having one that's automatically generated and always up to date is nice for post authors. In this tutorial, we’ll show you how to add an automatically generated table of contents to your Ghost site in a few quick steps.

Here's an example of what we'll build:

0:00
/

Tocbot

To help us add the table of contents, we’re going to use a JavaScript (JS) library called Tocbot. This library does the heavy lifting for us, which includes finding out the post’s headings, creating links to those sections, and showing the reader where they are on the page. It’s a super cool library!

💡
Tocbot works by looking at your post’s heading tags like <h2>. Create a heading in Ghost by selecting your heading text and clicking the H icon in the popup editor.

There are two parts to the Tocbot library: 1) a JS file that handles functionality and 2) a CSS file that handles basic styling. Both need to be loaded into your Ghost theme.

In this tutorial, we’ll be using Ghost’s default theme, Casper, but the steps here apply to any Ghost theme.

Edit default.hbs

Open default.hbs in a code editor. In this file, add the Tocbot script and styles.

🚧
At the time of writing, Tocbot is on version 4.12.3. When installing it on your site, use the latest version by updating the version number in the URLs above. Advanced developers can install Tocbot directly from npm.

Add the CSS

Near the top of the file, in the head tag and right before {{ghost_head}}, add the Tocbot CSS:

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

Additional styles

We'll add some additional styles to a style tag directly after the CSS we just loaded in default.hbs.

These styles will help Tocbot look right at home in our theme. While what's shared below is specific to Casper, you can adapt the style to work with whatever theme you’re using.

Our goal for the TOC is for it to be present alongside article content on larger screens but above it on smaller ones.

<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 */
        grid-row: span 1000;
        padding-right: 8rem;
   }
}

.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>

Add the JS

In default.hbs, now near the end of the file and right before {{ghost_foot}}, add the Tocbot script:

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

Initialize the Tocbot script

Having loaded the Tocbot script, we now need to initialize it, which tells Tocbot where on the page we want our table of contents and what we want to add to it.

Add this script right after the code from the last step:

{{! 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>
💡
The classes (gh-content, gh-toc) used in this tutorial are based on the Casper theme. You’ll need to change gh-content to match the container class (what wraps around your post content) of your theme. gh-toc can be anything you want – you just need to ensure that the class in the initialization script matches the one in the template (as shown in the next step).

Edit post.hbs

Let's define where we want the table of contents to show up in our theme.

In post.hbs, add the following code snippet right before the {{content}} helper:

<div class="gh-toc"></div> {{! The TOC will be inserted here }}

🔥 Fire up your table of contents

You've made some sweet progress: Tocbot is installed and you’ve hooked it up to your Ghost theme.

As a final check, your default.hbs and post.hbs files should look like this:

<!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 */
                grid-row: span 1000;
                padding-right: 8rem;
            }
        }

        .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> &copy; {{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>
default.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">&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}}
post.hbs

You’re now ready to upload your changes to your Ghost site. Activate your new theme, refresh a post, and watch your own little table-of-contents robot work its magic 🤖

How to add a table of contents

Summary

In this tutorial, you installed a third-party JS library, modified a Ghost theme template, and added custom CSS. You’re well on your way to becoming a Ghost pro. Keep on leveling up your Ghost skills by checking out more of our tutorials or head on over to our Forum to share how you built your table of contents.

]]>
<![CDATA[Show reading time & progress]]>https://ghost.org/tutorials/reading-time/627ab7a10c5e48003d437883Tue, 10 May 2022 19:42:51 GMT

In this tutorial, we’ll see how to add reading times to your Ghost theme and use JavaScript (JS) to update the reader’s progress in real-time. (Just look at the top of this page while you're reading for a demo.)

Reading time

Letting your readers know how long it’ll take to read an article is quite simple in Ghost. The reading_time helper outputs a customizable, calculated reading time of your content.

Show reading time & progress

Add {{reading_time}} to your Ghost template to output x min read.

{{! Ensure you're in a post context }}
{{#post}}
	{{reading_time}}
	{{! outputs: 3 min read }}
{{/post}}

It’s also possible to customize the helper’s output.

{{! Ensure you're in a post context }}
{{#post}}
	{{reading_time minute="Only a minute" minutes="Takes % minutes"}}
	{{! outputs: Takes 3 minutes }}
{{/post}}

The minute attribute defines the value to use when reading time is calculated to be one minute or less. minutes defines the value to use when reading time is greater than a minute. (% is a variable that will be replaced with the number of minutes.)

And that’s all there is to it – you’re now an expert with the reading_time helper 🤓

Our docs are a great reference for the reading time helper and include some extra information about how reading time is calculated – for example, images count toward the reading time! You can also check out how we use reading time in the Casper theme.

Ghost Handlebars Theme Helpers: reading_time
Render the estimated reading time of a post in your Ghost publication with this handlebars helper ⚡️ Read more about Ghost themes!
Show reading time & progress

Show reading progress

With reading time, the reader knows how long it will likely take to read your post, but what if we could provide real-time status as they read the article? By adding some custom HTML, CSS, and JS – this is also possible.

Add the progress bar element

The first step is to add the <progress> element to the post template. We’ll add a class to make it easier to style in the next step. In post.hbs, add the following code block after {{!< default}}:

<progress class="reading-progress" value="0" max="100" aria-label="Reading progress"></progress>

The progress element shows the completion status of a task. It could be the progress of a file upload, for example. In our case, it’s the reader's progress through the article.

Style the progress bar

By default, the progress bar will be styled by the user’s browser. To maintain a consistent design, we’ll add some CSS to style the progress bar.

In Code Injection Site Header, add this code:

<style>
.reading-progress {
  position: fixed;
  top: 0;
  z-index: 999;
  width: 100%;
  height: 5px; /* Progress bar height */
  background: #c5d2d9; /* Progress bar background color */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none; /* Hide default progress bar */
}

.reading-progress::-webkit-progress-bar {
  background-color: transparent;
}

.reading-progress::-webkit-progress-value {
  background: var(--ghost-accent-color); /* Progress bar color */
}
</style>

Comments above indicate the options you’d most likely want to change. The progress bar's color is set to the accent color as defined in Ghost Admin.

Make the progress bar dynamic

The final component is to add the JS that will dynamically update the progress bar as the reader scrolls through the article. Add this code to default.hbs right before ghost_foot:

{{#is "post"}}
  <script>
    const progressBar = document.querySelector('.reading-progress');

    function updateProgress() {
      const height = document.body.clientHeight;
      const position = window.scrollY;
      const progress = position / height * 115;
      progressBar.setAttribute('value', progress);
      requestAnimationFrame(updateProgress);
    }

    requestAnimationFrame(updateProgress);
  </script>
{{/is}}
default.hbs

That’s it! Zip up your theme and upload it to your Ghost site. Hop on over to a post and start scrolling to see your real-time progress bar in action 💥

Demo

0:00
/

Summary

This tutorial walked through two ways to let readers know just how long (or short) that post is on your Ghost site. Being able to add reading time and progress to a theme means you're well on you're way to becoming a top-notch Ghost developer.

We’re eager to see your progress (bars), so come on over to our Forum to show off 😉

]]>
<![CDATA[How to add an offer banner]]>https://ghost.org/tutorials/offer-banners/6279c42774489d003d82fd22Tue, 10 May 2022 02:17:22 GMT

Offers in Ghost provide a full-featured system to encourage new member signups with special discounts.

Creating offers and discounts
The offers system in Ghost allows you to convert more paid customers by offering shareable discounts to your audience. Creating an offerThe offers page appears in Ghost Admin when you have an active Stripe connection in place. Offers can be created for any plan or tier, using a percentage or
How to add an offer banner

This tutorial will walk you through the process of adding a stunning offer banner to your site's theme.

We’ll use the Ghost theme, Headline, as our starting point. However, all the steps below will work for most themes.

Here's a preview of what we're going to create.

How to add an offer banner

Add the banner to your theme

From a local copy of the Headline theme, open default.hbs in your code editor. Add the following code block after the opening <body> tag.

<a class="gh-banner" href="https://YOUR-OFFER-URL" style="background-image: url({{asset "images/banner-background.jpg"}})">
    <div class="gh-banner-inner">
        <div class="gh-banner-left">
            <p class="gh-banner-headline">Support independent media</p>
            <p class="gh-banner-cta">Limited-time offer for new subscribers</p>
        </div>
        <div class="gh-banner-right">
            <p class="gh-banner-button">Learn more</p>
        </div>
    </div>
</a>

This code block contains the blueprint for your offer. Here's a reference of what it will look like on the page.

How to add an offer banner

Looks pretty good, right? To make the banner your own, replace https://YOUR-OFFER-URL with your own offer URL from Ghost Admin and update the banner copy to suit your publication's identity.

Add the background image

The last edit to make is to add the background image. Add the image to the theme's assets/images folder. Update the code snippet so that banner-background.jpg matches your image's filename.

How to add an offer banner
banner-background.jpg

Creating a banner background image that works on a variety of screen sizes can be a challenge. For reference, our banner background image is 1257 by 90 px.

You can omit the background image altogether by removing style="background-image: url({{asset "images/banner-background.jpg"}})" from the code snippet above. The banner will fall back to the background color defined in the next section.

That's all the changes that need to be made to the theme 💪 Zip up the theme folder and upload it to your Ghost site.

Style the banner via Code Injection

To style the banner, add the following style tag to Code Injection → Site Header. After adding in the styles, click Save.

<style>
.gh-banner {
  display: block;
  height: 90px;
  padding: 0 var(--gap);
  line-height: 1.3;
  background-color: #eeeff1; /* Banner background color */
  background-repeat: no-repeat;
  background-position: center;
}

.gh-banner-inner {
  display: flex;
  flex-direction: row;
  gap: 0.5em;
  align-items: center;
  justify-content: space-between;
  max-width: 1200px;
  height: 100%;
  margin: 0 auto;
}

.gh-banner-right {
  flex-shrink: 0;
}

.gh-banner-headline {
  font-size: 120%;
  font-weight: 700;
}

.gh-banner-button {
  padding: 0.35em 0.65em;
  font-weight: 700;
  color: #fff;
  text-align: center;
  background-color: #000;
  border-radius: 3px;
  transition: background-color 0.3s;
}

.gh-banner:hover .gh-banner-button {
  background-color: var(--ghost-accent-color);
}

@media (max-width: 500px) {
  .gh-banner {
    font-size: 1.4rem;
  }

  .gh-banner-inner {
    flex-direction: column;
    justify-content: center;
  }
}

@media (max-width: 768px) {
  .gh-banner {
    position: relative;
    color: #fff;
  }

  .gh-banner::after {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    content: "";
    background: rgba(0 0 0 / 50%);
  }

  .gh-banner-inner {
    position: relative;
    z-index: 1;
  }
}

/* Override Headline theme defaults */
.gh-banner:hover {
  opacity: 1 !important;
}

.gh-head-menu::before {
  top: 170px;
}

.gh-head-menu::after {
  top: 226px;
}
</style>

Now, an offer banner appears on every page of your site ✨

How to add an offer banner

Summary

Offers are an effective tool to grow your paid membership by providing visitors an extra incentive to sign up. In this tutorial, you learned how to customize your theme to add an offer banner. Truth be told, the hardest part of all is likely coming up with the banner design and copy. We often talk about these kinds of things in the Ghost Forum, so come on over to share your creativity and find some inspiration 🌈

]]>
<![CDATA[Change the order of posts]]>https://ghost.org/tutorials/change-post-order/6279916c74489d003d82fc9dMon, 09 May 2022 22:54:10 GMT

By default, Ghost displays posts on index pages in reverse chronological order – that’s newest to oldest. In this tutorial, we’ll find out how to edit your routes.yaml to customize the order your posts, including reverse order, by title, and more.

Download routes.yaml

The first step is to download your routes.yaml file from Settings Labs Routes Download current routes.yaml.

Change the order of posts

Open the file in your code editor.

Define a custom route

Imagine we have a movie review website, where we publish reviews on the date that the movie is released. By default, Ghost will list our posts from the most recently published to the oldest.

Change the order of posts

Reversing the order

But let’s say we wanted to reverse the order and display the oldest movies first. You only need to add one line to your routes.yaml file. In the example below, we’ve created a custom route called /movies/ that loads our posts using the channel controller. (For more on creating channels and collections, see our guide.) The order property takes two parameters: the field you want to order by and the order direction: asc for ascending and desc for descending.

routes:
  /movies/:
      controller: channel
      order: published_at asc

Save your changes and upload your changes to Ghost. Navigate to /movies/ and refresh. All posts will now be listed in ascending chronological order, from oldest to newest.

Change the order of posts

Beyond published_at

What if we wanted to have a page on our site where movies would be listed alphabetically by their titles, rather than publication date. Again, it’s just one easy change to the routes.yaml file.

routes:
  /movies/:
      controller: channel
      order: title asc
Change the order of posts

Movies now appear in alphabetical order. If you have a review website with products and you want to create a page where those products are listed in alphabetical order, then using this technique is a quick, easy solution.

Combine order with filter

Additionally, you can add the filter property to limit your results. To create page that only featured movies from the ‘80s, we could add the following filter that loads at /movies/80s/. (This works because the publication date matches the movie’s release date.)

routes:
  /movies/80s/:
    controller: channel
      order: title asc
      filter: published_at:>1979-12-31+published_at:<1990-01-01 
Change the order of posts

More than one order

One last neat option is the ability to define multiple orders. The most common use case is where you want to return featured posts first, followed by the remaining posts. In our case, we’ll imagine you have two reviews that we want to feature followed by the remaining ones. We’d update routes.yaml like this:

routes:
  /movies/:
  controller: channel
  order: featured desc, published_at desc
Change the order of posts

Now, our featured reviews of Joker and Ford v Ferrari are displayed first with custom styling, followed by the remaining posts in chronological order.

Summary

In this tutorial, we provided a glimpse of what you can do with the order property on custom routes. Whether it’s reversing the order of posts, changing the field that they’re sorted by, or building complex groups of posts – the possibilities are endless!

Have you used custom routes in an interesting way or looking for more examples? Come join us and the Ghost community on our Forum. It’s a great place to make connections and get inspired.

]]>