The very first thing we're going to do is set up a basic theme shell, without any styling applied to it, that you can use as the foundation of this theme and any other you build after it.
We'll begin by getting the file and folder structure of your theme set up inside your local offline Ghost installation.
Go to content/themes
in your local installation and create a new folder for your theme. As we mentioned in Chapter 3, Preparing for Theme Development, we'll name it LearningGhost
.
Inside that folder, create five new files. The templates your Ghost theme will use are as follows:
default.hbs
index.hbs
post.hbs
tag.hbs
page.hbs
As you can see, the names of these template files correspond with the themeable areas talked about previously, with each one being responsible for the presentation of each of these areas.
Note that it's also possible to include an error.hbs
file which allows you to create your own page for 400 and 500 errors. However, Ghost provides quite a nice looking error page by default, so we won't be making a customized version in this guide.
Additionally, create a sixth file inside the LearningGhost
folder named package.json
.
This is the file you'll enter your theme's proper name and version number into, so it will show up for users when selecting themes via the admin panel. In future, this file will also allow you to specify other assets that your theme depends on, such as Ghost plugins, so they can be automatically installed along with your theme.
As well as the files listed above, your theme will also contain assets such as CSS, JavaScript, font files, and images. In order for Ghost's {{assets}}
helper (which we'll be using) to find these files, they must be contained inside a folder named assets
.
First, create a new folder within your LearningGhost
theme folder and name it assets
. Within the assets folder, create an additional four folders named:
css
fonts
images
js
When you're done, your new theme folder should look like this:
Now that you have your theme structure in place, it's time to run the first compile from the LearningGhostSource
project folder you created in the Creating a project folder section of Chapter 3, Preparing for Theme Development.
Open up a command window/terminal at LearningGhostSourcecompiler
and run the following command:
grunt stylus
This command compiles all the files in the LearningGhostSourcestylus
folder into your theme to give you a basic style sheet.
If you look at assetscss
inside theme, you should now see a file named screen.css
in there. This file contains the essential basis for your theme's styles, including a version of Normalize.css
, typography settings, and a few other basics.
Now, in the same location, run the following command:
grunt uglify
This takes the two files in LearningGhostSourcejs
, combines them, minifies them, and writes a file into your theme.
In assetsjs
, you should now see a file named all.min.js
.
One part of what this combined file does is to load Modernizr.js
, which will help to ensure that the current standard code is understood by out-of-date browsers. The other part is a custom script that ensures videos, soundcloud embeds, and anything else entered via an iframe will resize responsively in your theme.
Using Sublime Text, as per the info in Chapter 3, Preparing for Theme Development, open up each of the files mentioned in the upcoming sections.
Enter the following code into this file:
{ "name": "Learning Ghost", "version": "0.1.0" }
Simply enter the name of your theme next to the name
parameter, and the version number of your theme next to the version
option.
This file will require more information in the future, but for the time being this is all that's required.
Enter the following code into this file:
<!DOCTYPE html> <html> <head> {{! Document Settings }} <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> {{! Page Meta }} <title>{{meta_title}}</title> <meta name="description" content="{{meta_description}}" /> <meta name="HandheldFriendly" content="True" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="shortcut icon" href="{{asset "favicon.ico"}}"> {{! Styles'n'Scripts }} <link href='//fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic' rel='stylesheet' type='text/css'> <link rel="stylesheet" type="text/css" href="{{asset "css/screen.css"}}" /> <script type="text/javascript" src="{{asset "js/all.min.js"}}"></script> {{! Ghost outputs important style and meta data with this tag }} {{ghost_head}} </head> <body class="{{body_class}}"> {{! Document header }} <header class="blog_header_lg" role='banner' itemscope itemtype='http://schema.org/WPHeader'> {{#if @blog.logo}}<a href="{{@blog.url}}"><img src="{{@blog.logo}}" alt="{{@blog.title}}" /></a>{{/if}} <h1 class="blog_title_lg"><a title="{{@blog.title}}" href='{{@blog.url}}'>{{{@blog.title}}}</a></h1> <p class="blog_description_lg"><a title="{{@blog.title}}" href='{{@blog.url}}'>{{{@blog.description}}}</a></p> </header> {{! Document main }} <main class="posts_area_lg">{{{body}}}</main> {{! Document footer }} <footer class="blog_footer_lg" itemscope itemtype='https://schema.org/WPFooter'> <p><a class="subscribe icon-feed" href="{{@blog.url}}/rss/">Subscribe!</a></p> <p>All content copyright <a href="{{@blog.url}}">{{{@blog.title}}}</a> © 2013 • All rights reserved.</p> <p>Proudly published with <a class="icon-ghost" href="http://tryghost.org">Ghost</a></p> </footer> {{! Ghost outputs important scripts and data with this tag }} {{ghost_foot}} <script language="javascript" type="text/javascript">responsive_iframes();</script> </body> </html>
This is the code that will wrap every page of your theme by default. Let's take a quick look at some of the Ghost-specific things being done here.
First, we have the essential HTML5 html
, head
, and body
tags. Rather than having this type of code in separate files loaded at the beginning and end of other templates, as you might in WordPress for example, this is all handled in a single file.
This is done by placing the {{{body}}}
tag where you want the other template files of your theme to load. Note the triple curly braces to prevent HTML escaping as mentioned in Chapter 3, Preparing for Theme Development.
The meta content
Note the {{meta_title}}
and {{meta_description}}
tags used in the head section. These generate meta tag output dynamically depending on where the visitor is on the site.
The {{asset}} tag
The style.css
and all.min.js
files we just generated are loaded into the theme via the {{asset}}
tag, for example, {{asset "css/screen.css"}}
. This ensures Ghost always finds theme assets, and that they can be cached.
This tag is also used to load the default favicon: {{asset "favicon.ico"}}
.
The {{ghost_head}} tag
This tag is essential and must always be included right before the end of the head section.
The {{body_class}} tag
This tag outputs different classes depending on where the user is on the site.
In this section, we are loading the blog's logo, title, and description, each linked to the blog's main URL, so the content in the header acts as the home button. The @blog
object contains all four of these pieces of data via Handlebars paths/dot notation, as well as any blog cover image, which we will discuss further in a later section:
{{@blog.logo}}
: This outputs the blog logo image URL{{{@blog.title}}}
: This outputs the blog title string{{{@blog.description}}}
: This outputs the blog description string{{@blog.url}}
: This outputs the blog root URL{{@blog.cover}}
: This outputs the blog cover image URLNote this is a header in the HTML5 sense, that is, it contains header data relative to the overall document. It can still be styled via CSS to sit over to the side if you choose to create a twin column layout.
This is the HTML5 document footer section where we load copyright and attribution data, along with a site RSS feed if you choose.
We have a few CSS classes in place that can be used to style the theme, such as blog_title_lg
applied to the blog title. Note the _lg
appended to the class. This is an abbreviation for the Learning Ghost theme name, and is used to namespace classes to ensure that plugins that might bring in additional CSS don't conflict by inadvertently using identical class names.
You'll also see application of semantic HTML5 tags such as header
, main
, and footer
, which we will be doing throughout, as well as WAI-ARIA roles for accessibility, and Schema.org markup for SEO. We won't go into great detail on these three things in this guide, but recommend you thoroughly read up on them to inform your development.
Insert the following code into the index.hbs
file:
{{!< default}} {{#foreach posts}} <article role="article" itemscope itemtype="http://schema.org/BlogPosting" class="{{post_class}}"> <header> <time datetime="{{date format="YYYY-MM-DD"}}" itemprop="datePublished">{{date format='dddd DD MMM YYYY'}}</time> <h1 class="post_title_list_lg" itemprop="headline"><a href="{{{url}}}" rel="bookmark">{{{title}}}</a></h1> </header> {{excerpt}} <p><a href="{{url}}">Read More →</a></p> <footer> <p>{{{tags}}}</p> </footer> </article> {{/foreach}} {{pagination}}
The preceding code is responsible for the display of your index area, that is, your paginated list of latest posts accessed via your homepage. This code is loaded into the default wrapper at the point the {{{body}}}
tag was entered.
The {{!< default}} tag
This tag is essential for all template files other than default
.hbs
and should be placed on the very first line of each. The reason is that this tag tells Ghost that the template should be wrapped in the content of the default
.hbs
file.
The {{#foreach posts}}...{{/foreach}} block helper tags
These block helper tags iterate the post
object, which holds all of the data pertaining to the page's current list of posts. Everything that appears between these opening and closing tags will be repeated for each post displayed.
The {{post_class}} tag
This tag will output different CSS classes per post, with the default output being a class of post
and additional classes being added for each tag applied to the post in the format tag-<tagname>
.
The {{date}} tag
There are two options for how the date of publishing can be output. Either a relative time can be shown, for example 1 month ago, or the actual date can be shown, for example, Thursday 15th May 2014.
To show a relative time, place the date tag as follows:
{{date timeago="true"}}
To show the actual date, use the tag like this:
{{date format="dddd DD MMM YYYY"}}
When using the actual date the exact way, its output can be controlled with different settings in the format
option, which determine how the date is formatted by a script Ghost uses named Moment.js
. See the Moment.js date formatting guide for details on the options available.
The {{url}} tag
This tag outputs the URL of the post's individual post. It can be used to create Read More links, social sharing links, and so on.
By default, it will output a relative URL, for example, /post-slug/
, but it can also output an absolute URL, for example, http://www.yourdomain.com/post-slug/
by setting the absolute option to true
:
{{url absolute="true"}}
This is useful to create social sharing links, as you'll see in the subsequent post.hbs
section.
The {{title}} tag
This tag simply displays the title of the post.
The {{excerpt}} and {{content}} tags
In both the index area and the tag archive area, there are two ways of displaying the body of the post: the {{excerpt}}
tag and the {{content}}
tag.
The excerpt
tag displays a clipped portion of the post, with length determined either in words or characters, and all HTML stripped. This prevents things such as images or videos being shown, so the excerpt is text only. By default, it shows the first 50 words. To show a different content, use either the words
or characters
option:
{{excerpt words="80"}} {{excerpt characters="140"}}
The other option is the {{content}}
tag which does not strip any HTML and shows the fully formatted post body with whatever images and videos that might be included. By default, the tag shows the whole post body but it too can be limited by either words or characters:
{{content words="80"}} {{content characters="140"}}
The {{{tags}}} tag
This outputs all the tags applied to the current post, with each one automatically linked to the archive for that tag. Optionally, a prefix, separator, and suffix for the list of tags can be included, each of which can accept HTML, for example:
{{{tags prefix="<strong>Tagged with: </strong><em>" separator=" | " suffix="</em>"}}}
The {{pagination}} tag
In the index and tag archive areas, the number of posts shown per page is set via Ghost's Posts per page
admin field. Whenever there are more posts than the number saved under this setting, the {{pagination}}
tag will output links reading Older posts and Newer Posts as well as a display showing Page <current> of <total>.
Insert the following code into the post.hbs
file:
{{!< default}} {{#post}} <article role="article" itemscope itemtype="http://schema.org/BlogPosting" class="{{post_class}}"> <header> <time datetime="{{date format="YYYY-MM-DD"}}" itemprop="datePublished">{{date format="dddd DD MMM YYYY"}}</time> <h1 itemprop="headline">{{{title}}}</h1> </header> {{content}} <footer> <p>{{{tags}}}</p> <address itemscope itemtype="https://schema.org/Person"> {{#if author.image}}<div class="authorimage_nix"><img src="{{author.image}}" itemprop="image" /></div>{{/if}} <h4 class="nix_author">{{#unless author.image}}<i class="nix_icon-user"></i>{{/unless}}<span itemprop="author">{{{author.name}}}</span></h4> {{#if author.website}}<p><a href="{{author.website}}" itemprop="url" target="_blank" rel="author">{{author.website}}</a></p>{{/if}} {{#if author.location}}<div itemscope itemtype="https://schema.org/PostalAddress"><span itemprop="addressLocality">{{{author.location}}}</span></div>{{/if}} </address> {{#if author.bio}} {{{author.bio}}} {{/if}} <section class="share"> <h4>Share this post</h4> <a class="icon-twitter" href="http://twitter.com/share?text={{encode title}}&url={{url absolute="true"}}" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235'),return false;"> Twitter </a> <a class="icon-facebook" href="https://www.facebook.com/sharer/sharer.php?u={{url absolute="true"}}" onclick="window.open(this.href, 'facebook-share','width=580,height=296'),return false;"> Facebook </a> <a class="icon-google-plus" href="https://plus.google.com/share?url={{url absolute="true"}}" onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530'),return false;"> Google+ </a> </section> </footer> </article> {{/post}}
This code controls the display of single posts viewed at their own URLs. You'll see many of the same template tags here as you saw in the previous two templates.
The primary differences in how you'll create your post.hbs
file versus your index.hbs
file are:
{{post}}...{{/post}}
tags to output the single post.The {{author}} object
The following properties of the {{author}}
object can all be accessed via Handlebars paths, as was done in the default section with the {{blog}}
object:
{{{author.name}}}
: This will output the author name string{{{author.location}}}
: This will output the author location string{{{author.bio}}}
: This will output the author bio string{{author.website}}
: This will output the URL from author "website" field{{author.image}}
: This will output the author image URL{{author.cover}}
: This will give an output of the author cover image URLInsert the following code into the tag.hbs
file:
{{!< default}} <div class="tag_archive_name_lg"> <h1>{{tag.name}}</h1> </div> {{#foreach posts}} <article role="article" itemscope itemtype="http://schema.org/BlogPosting" class="{{post_class}}"> <header> <time datetime="{{date format="YYYY-MM-DD"}}" itemprop="datePublished">{{date format='dddd DD MMM YYYY'}}</time> <h2 class="post_title_list_lg" itemprop="headline"><a href="{{{url}}}" rel="bookmark">{{{title}}}</a></h2> </header> {{excerpt}} <p><a href="{{url}}">Read More →</a></p> <footer> {{{tags}}} </footer> </article> {{/foreach}} {{pagination}}
In our base theme shell, the only difference between the tag archive and the index is the inclusion of {{tag.name}}
in order to output the name of the tag whose archive is being viewed.
The tag.hbs
template is most handy if you decide to have its presentation formatted differently for specific tags, such as video
and image
.
Insert the following code into the page.hbs
file:
{{!< default}} {{#post}} <article role="article" class="{{post_class}}"> <header> <h1 itemprop="headline">{{{title}}}</h1> </header> {{content}} </article> {{/post}}
The page template is functionally very similar to the post template, but stripped down and simplified. Static pages are designed to display basic information such as About Us or Contact Details and as such the only thing shown is the page title and its content.
Author information is accessible via this template, so it can be included if you wish.
3.133.112.171