位元詩人 技術雜談:Build Your Own Jekyll Theme with Bootstrap

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

When you built a blog from Jekyll, a beautiful, mobile-responsive theme passed as well. However, if you want to utilize a third party web front framework like Bootstrap, the built-in CSS file became potential sources of CSS conflict and delayed page loading. Starting a Jekyll blog from blank theme seems daunting, but, with the help of Bootstrap, the process becomes agreeable and enjoyable.

Before starting our next awesome blog, let's see a sample Jekyll project and build a blueprint in mind.


$ lstree
  +-- _drafts/
  +-- _includes/        # directory for template partials
  |   +-- footer.html
  |   +-- head.html
  |   +-- header.html
  +-- _layouts/         # directory for main layouts
  |   +-- default.html
  |   +-- page.html
  |   +-- post.html
  +-- _posts/
  +-- _site/
  +-- css/              # place for CSS files
  +-- fonts/            # place for web fonts
  +-- images/
  +-- index.html        # index page
  +-- js/               # holder for JavaScript files
  +-- _config.yml       # project config file
  +-- 404.md            # page for 404 error
  +-- 500.md            # page for 50x errors
{{< / highlight >}}

Most of the process building a blank Jekyll site is filling our own template files.  They are *default.html*, *post.html*, and *page.html*, which are all in *_layouts* folder.  Besides, you also need to edit *index.html* and don't forget *_config.yml*.

Download needed CSS and JavaScript files and place them in the corresponding folders.  Although we can utilize many CDNs to boost our loading speed, I still prefer a local files as fallbacks.

*default.html* is the root of our template; all other templates inherit from it.  Therefore, let's start from here.

```html
<!DOCTYPE html>
<html lang="en">
  {% include head.html %}
  <body>
    {% raw %}
    {% include header.html %}

    {{ content }}

    {% include footer.html %}
    {% endraw %}
  </body>
</html>
{{< / highlight >}}

Here we keep a simple structure and delegate our HTML tags to other files.  You may just put everything here if you prefer.

Let's see our *head.html* file.

```html
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- custom meta tags from page -->
  <meta name="description" content="{% if page.description %}{{ page.description }}{% else %}{{ site.description }}{% endif %}">
  <meta name="keywords" content="{% if page.keywords %}{{ page.keywords }}{% else %}{{ site.keywords }}{% endif %}">

  <!-- custom title from page -->
  <title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title>

  <!-- Bootstrap CSS file -->
  <link href="{{ "/css/bootstrap.min.css" | prepend: site.baseurl }}" rel="stylesheet">

  <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  <!--[if lt IE 9]>
  <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
  <![endif]-->

  <!-- Pygments style CSS file -->
  <link href="{{ "/css/monokai.css" | prepend: site.baseurl }}" rel="stylesheet">

  <!-- our own custem CSS file -->
  <link href="{{ "/css/site.css" | prepend: site.baseurl }}" rel="stylesheet">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
  <script>
    (function() {
        if(typeof jQuery=='undefined') {
            document.write('<sc'
               + 'ript src="/js/jquery-1.11.2.min.js"></sc'
               + 'ript>');
        }
    }).call(this);
  </script>

  <!-- Bootstrap JS script -->
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  <script>
    (function() {
      if (typeof($.fn.modal) === 'undefined') {
        document.write('<sc'
               + 'ript async src="/js/bootstrap.min.js"></sc'
               + 'ript>');
      }
    }).call(this);
  </script>
</head>
{{< / highlight >}}

There are several tricks here.  First, set customized meta tags and title so that we can fill these informations from YAML front.  Second, load JavaScript from available CDNs by default but write JS code for fallbacks.  Here we use a `document.write` trick to add HTML tags here and now.

Then comes *header.html*.  The template will make navigation bar.  Simply follow standard Bootstrap navbar example here.

```html
<div class="navbar navbar-default">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse"
              data-target="#mynavbar-content">
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
         <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="{{ "/" | prepend: site.baseurl }}">
        {{ site.title }}
      </a>
    </div>

    <div class="collapse navbar-collapse" id="mynavbar-content">
      <ul class="nav navbar-nav">
        {% raw %}
        {% for page in site.pages %}
        {% if page.title %}
        {% endraw %}
        <li><a href="{{ page.url | prepend: site.baseurl }}">{{ page.title }}</a></li>
        {% raw %}
        {% endif %}
        {% endfor %}
        {% endraw %}
      </ul>
    </div>
  </div>
</div>
{{< / highlight >}}

Next comes *footer.html*.  You may refer this example and design your own footer.  Here we use nested Bootstrap grids for layout.

```html
<footer class="site-footer">
  <div class="container">
    <div class="row">
      <div class="col-sm-10 col-sm-offset-1 text-center">
        <div class="row">
      <div class="col-sm-3 col-xs-12">
        <img src="{{ "/images/mail.png" | prepend: site.baseurl }}"
        alt="send e-mail to Michelle Chen">
      </div>
      <div class="col-sm-3 col-xs-12">
        <!-- put Twitter follow icon here -->
      </div>
      <div class="col-sm-3 col-xs-12">
        <!-- put your own counter here -->
      </div>
      <div class="col-sm-12">
        <div>{{ site.description }}</div>
      </div>
        </div>
      </div>
    </div>
  </div>
</footer>
{{< / highlight >}}

Now we finished our *default.html*.  Let's re-use it in *post.html* and *page.html*.  First comes *post.html*.

```html
---
layout: default
---
<div class="container">
  <header>
    <div class="page-header">
      <h1>{{ page.title }}</h1>
    </div>
    <p>{{ page.date | date: "%b %-d, %Y" }}{% if page.author %} • {{ page.author }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}
    </p>
  </header>

  <article>
    {{ content }}
  </article>
</div>
{{< / highlight >}}

Here comes *page.html*.

```html
---
layout: default
---
<div class="container">
  <header>
    <div class="page-header">
      <h1>{{ page.title }}</h1>
    </div>
  </header>

  <article>
    {{ content }}
  </article>
</div>
{{< / highlight >}}

Don't forget *index.html*.  You may design your impressive index page here.

```html
---
layout: default
---
<div class="container">
  {% for post in paginator.posts %}
    <h1><a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a></h1>
    <p>{{ post.excerpt | strip_html }}</p>
    </div>
  {% endfor %}

  <div class="text-center">
    {% if paginator.previous_page %}
      <a class="btn btn-default" href="{{ site.baseurl }}{{ paginator.previous_page_path }}">Previous</a>
      {% endif %}
      <span>{{ paginator.page }} / {{ paginator.total_pages }}</span>
    {% if paginator.next_page %}
      <a class="btn btn-default" href="{{ site.baseurl }}{{ paginator.next_page_path }}">Next</a>
    {% endif %}
  </div>
</div>
{{< / highlight >}}

The final step is writing down your own configuration file.  Just follow the example and customize it to fit your own need.

```yaml
# Site settings
title: "Your next awesome blog"
#email: your-email@example.com
description: "site description"
keywords: "site keywords"
baseurl: ""
url: "http://example.com"

# Build settings
markdown: kramdown
permalink: pretty

# pagination
paginate: 5
paginate_path: "page:num"

highlighter: "pygments"
{{< / highlight >}}

Then, write some posts test your blog.  With the help of Bootstrap, we built a clean and responsive blog merely in steps.  You may try some Bootstrap themes for eye-candy.
關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。