メニュー

テーマのチュートリアル

新しいことを学ぶときに、具体例を使って、自分独自のものを作ってみることが最良の方法であるときがあります。
テーマづくりについて、この方法でやってみましょう。

Quark

Grav には、Spectre.css フレームワーク を使った Quark という、クリーンでモダンなテーマが付属しています。

Spectre.css は、迅速で拡張性高い開発のための、軽量で、レスポンシブで、モダンなCSSフレームワークです。

Spectre は、基本的なタイポグラフィーや要素のスタイル、 flexbox ベースのレスポンシブ・レイアウト・システム、 pure CSS コンポーネントとユーティリティを、ベストプラクティスのコーディングと一貫したデザイン言語とともに提供してくれます。

しかしながら、もう少しシンプルなものから始めたほうが、より良いこともあります。

Pure.css

このチュートリアルのために、 Yahoo! が開発した、人気のPure.css フレームワーク を使って、テーマを作っていきましょう。

Pure は、小さく、速く、レスポンシブな CSS フレームワークで、 Bootstrap や、 Foundation のような大きなフレームワークのオーバーヘッドが無い状態でサイトの開発ができます。
Pure にはいくつかのモジュールがあり、それぞれ独立して使えますが、すべて合わせても minify して gzip すると 4.0KB しかありません。

Pure.css プロジェクトサイト では、 Pure のすべての機能を読むことができます。

また、 テーマの重要なアップデート というブログ記事を読んでおいてください。
この記事では、将来的なプラグインサポートをベストな状態にするために、 Grav テーマの重要な変更の概要を説明しています。

[!訳注]
上記のブログ記事は、 Grav 1.5, 1.6 時代のものなので、これから始める方には、とくに関係なさそうでした。

ステップ1: DevTools プラグインのインストール

[!Info]
以前のバージョンのチュートリアルでは、あらかじめベーステーマを作る必要がありました。この手順はすべて、新しい DevTools プラグイン のおかげで、やらなくて済むようになりました。

新しいテーマを作る最初のステップは、DevTools プラグインをインストールする ことです。
2つの方法でインストールできます。

CLI GPMによるインストール

  • コマンドラインを、 Grav をインストールしたルートディレクトリへ移動させてください。
bin/gpm install devtools

管理パネルからのインストール

  • ログイン後、サイドバーから Plugins セクションへ移動します。
  • 右上の + Add ボタンをクリックします。
  • リストから、 DevTools を探し、 + Install ボタンをクリックします。

ステップ2: ベーステーマの作成

このステップのために、 コマンドライン を使う必要があります。
というのも、 DevTools が提供するいくつかの CLI コマンドによって、新しいテーマづくりがかなり簡単になるからです!

Grav をインストールしたルートディレクトリから、次のようなコマンドを実行してください:

bin/plugin devtools new-theme

このプロセスでは、新しいテーマづくりに必要な、いくつかの質問を訊ねられます。

[!Note]
これから新しいテーマを作るのに、 pure-blank を利用したいと思っています。が、他のテーマを継承するシンプルな inheritance を作ってもかまいません。

bin/plugin devtools new-theme

Enter Theme Name: MyTheme
Enter Theme Description: My New Theme
Enter Developer Name: Acme Corp
Enter Developer Email: contact@acme.co
Please choose a template type
  [pure-blank ] Basic Theme using Pure.css
  [inheritance] Inherit from another theme
  [copy       ] Copy another theme
 > pure-blank

SUCCESS theme mytheme -> Created Successfully

Path: /www/user/themes/my-theme

最後の行で、 DevTools コマンドが、どこに新しいテンプレートを作ったかを教えてくれます。
ここで作られたテンプレートは、完全に機能しますが、とても単純でもあります。
必要に応じて、修正したいと思うことでしょう。

新しいテーマの動作を知るために、デフォルトテーマを quark から 上記で定義した my-theme に変える必要があります。
user/config/system.yaml ファイルを編集し、以下のように変更してください:

...
pages:
    theme: my-theme
...

ブラウザで、サイトをリロードすると、テーマが変わっていることがわかるはずです。

ステップ3: テーマの基本

さて、新しいテーマができました。
これは、修正したり開発したりできます。
分解してみて、テーマを作っているものが何かを見ていきましょう。
user/themes/my-theme フォルダを見てみると、次のようになっているでしょう:

.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── blueprints.yaml
├── css
│   └── custom.css
├── fonts
├── images
│   └── logo.png
├── js
├── my-theme.php
├── my-theme.yaml
├── screenshot.jpg
├── templates
│   ├── default.html.twig
│   ├── error.html.twig
│   └── partials
│       ├── base.html.twig
│       └── navigation.html.twig
└── thumbnail.jpg

これは、サンプルの構造ですが、いくつかの要件があります:

機能するのに必要なアイテム

次のアイテムは、非常に重要です。
これらをあなたのテーマに含めない限り、確実に機能しないでしょう。

  • blueprints.yaml - 設定ファイルです。 Grav がテーマの情報を得るために使います。管理パネルでテーマ詳細を見るときに表示される form も定義できます。このフォームにより、テーマ設定を保存できます。このファイルは Forms の章で解説されています
  • my-theme.php - あなたのテーマ名をもとにファイル名が決まります。あなたのテーマに必要な、あらゆるロジックを入れられます。onPluginsInitialized() を除く プラグインイベントフック がすべて使えます。テーマ特有の onThemeInitialized() も使えます。
  • my-theme.yaml - これは、プラグインで使われる設定で、テーマで使うかもしれないオプションを設定します。
  • templates/ - ページをレンダリングする Twig テンプレートを入れるフォルダです。

[!訳注]
厳密に言えば、前の章のテーマの基本 にあったとおり、templates/ フォルダさえあれば、動くことは動きます。

リリースに必要なアイテム

次のアイテムは、 GMP を通してテーマをリリースしたい場合に必要なアイテムです。

  • CHANGELOG.md - リリースでの変更を表示するための Grav Changelog フォーマット に従ったファイル。
  • LICENSE - ライセンスファイル。特段の理由が無ければ、 MIT であるべきです。
  • README.md - ‘Readme’ ファイル。テーマに関するドキュメントを含むべきです。インストール方法、設定方法、使用方法など。
  • screenshot.jpg - 1009px x 1009px サイズのテーマのスクリーンショット。
  • thumbnail.jpg - 300px x 300px サイズのテーマのスクリーンショット。

ステップ4: ベーステンプレート

前の章 に見たとおり、 Grav のコンテンツファイルは、特定のファイル名を持ちます。たとえば、 default.md のように。
これにより、 Grav は、 default.html.twig というレンダリング用の Twig テンプレートを探します。
1つのファイルごとに、表示したいものをすべて書いていくこともできますし、それでうまく機能します。
しかし、もっと良い方法もあります。

Twig の Extends タグを使えば、好きな blocks を含むベースレイアウトが作れます。
これにより、どんな twig テンプレートも、ベーステンプレートから 拡張 できるようになり、ベーステンプレートに書いたあらゆる block を定義できます。
それでは、templates/default.html.twig ファイルを見てください。
そのコンテンツを試してみましょう:

{% extends 'partials/base.html.twig' %}

{% block content %}
    {{ page.content|raw }}
{% endblock %}

ここでは、2つのことが起こっています。

1つ目は、このテンプレートは、partials/base.html.twig にあるテンプレートを拡張しています。

[!Note]
Twig テンプレート内で、tenplates/ をパスに含める必要はありません。 Twig はすでに templates/ をテンプレートのルートディレクトリとしていているからです。

2つ目は、content ブロックがベーステンプレートから上書きされています。
ページのコンテンツは、その場所に出力されます。

[!Info]
一貫性を保つために、templates/partials フォルダを使うのは良い方法です。このフォルダには、 HTML の小さな塊や、共有される Twig テンプレートを保存します。同様に、templates/modular をモジュラーテンプレートのために、templates/forms をフォーム用に使えます。テンプレートを整理するために、いかなるサブフォルダを作っていただいてもかまいません。

templates/partials/base.html.twig ファイルを見ると、 HTML レイアウトの中身がわかります:

{% set theme_config = attribute(config.themes, config.system.pages.theme) %}
<!DOCTYPE html>
<html lang="{{ (grav.language.getActive ?: theme_config.default_lang)|e }}">
<head>
{% block head %}
    <meta charset="utf-8" />
    <title>{% if header.title %}{{ header.title|e }} | {% endif %}{{ site.title|e }}</title>

    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% include 'partials/metadata.html.twig' %}

    <link rel="icon" type="image/png" href="{{ url('theme://images/logo.png')|e }}" />
    <link rel="canonical" href="{{ page.url(true, true)|e }}" />
{% endblock head %}

{% block stylesheets %}
    {% do assets.addCss('http://yui.yahooapis.com/pure/0.6.0/pure-min.css', 100) %}
    {% do assets.addCss('https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css', 99) %}
    {% do assets.addCss('theme://css/custom.css', 98) %}
{% endblock %}

{% block javascripts %}
    {% do assets.addJs('jquery', 100) %}
{% endblock %}

{% block assets deferred %}
    {{ assets.css()|raw }}
    {{ assets.js()|raw }}
{% endblock %}

</head>
<body id="top" class="{{ page.header.body_classes|e }}">

{% block header %}
    <div class="header">
        <div class="wrapper padding">
            <a class="logo left" href="{{ (base_url == '' ? '/' : base_url)|e }}">
                <i class="fa fa-rebel"></i>
                {{ config.site.title|e }}
            </a>
            {% block header_navigation %}
            <nav class="main-nav">
                {% include 'partials/navigation.html.twig' %}
            </nav>
            {% endblock %}
        </div>
    </div>
{% endblock %}

{% block body %}
    <section id="body">
        <div class="wrapper padding">
        {% block content %}{% endblock %}
        </div>
    </section>
{% endblock %}

{% block footer %}
    <div class="footer text-center">
        <div class="wrapper padding">
            <p><a href="https://getgrav.org">Grav</a> was <i class="fa fa-code"></i> with <i class="fa fa-heart"></i> by <a href="http://www.rockettheme.com">RocketTheme</a>.</p>
        </div>
    </div>
{% endblock %}

{% block bottom %}
    {{ assets.js('bottom')|raw }}
{% endblock %}

</body>

[!Tip]
変数がレンダリングしても安全で、 HTML を含んでいる場合は、autoescape が true である状態で |raw フィルターを使ってください。

[!Info]
システム設定 で、autoescape を true にするのか、それともすべての変数ひとつひとつに対して、XSS 攻撃 への対策して、忘れずにエスケープするのかは、とても重要な問題です。

[!訳注]
ひとつひとつの変数を XSS 対策するのは、のちのち本当に面倒なことになるので、autoescapetrue でお願いします。

ステップ5: 分解しながら理解する

何が起こっているのか、より深く理解するために、base.html.twig ファイルのコードを読み通してください。
いくつかの注目すべき重要な点があります:

  1. theme_config 変数が、テーマ設定からセットされています。 Twig はダッシュマークをうまく使えないため、ダッシュのある変数(たとえば: config.themes.my-theme )を取得するためには、attribute() 関数を使います。 Twig 関数は、動的にconfig.thems から my-theme データを取得します。

  2. <html lang=... のアイテムは、もし有効化されていれば Grav のアクティブ言語をもとに設定され、そうでなければ、 theme_config 内の default_lang を設定します。

  3. {% block head %}{% endblock head %} 構文は、ベーステンプレート内で、あるエリアを定義します。{% endblock head %} タグ内の head は、必須ではありませんが、読みやすくなるので使っていることに注意してください。このブロックには、 HTML の <head> タグで使われるものを置きます。

  4. <title> タグは、そのページのフロントマターに書いた title 変数をもとに、動的に設定されます。header.title は、ショートカットメソッドですが、page.header.title と同じことです。

  5. 標準的なmeta タグをいくつか設定した後、partials/metadata.html.twig が参照され、呼び出されます(include されます)。このファイルは、systems/templates/partials フォルダにあり、ページのメタデータをループします。これは実際には、site.yaml のメタデータとページで上書きしたデータを合わせた(merge した)ものです。

  6. <link rel="icon"... では、テーマ固有の画像を指し示します。このケースでは、テーマディレクトリ下のimages/logo.png が使われます。このための構文は、{{ url('theme://images/logo.png') }} です。

  7. <link rel="canonical"... では、canonical URL(重複しないためのURL)が設定されます。 {{ page.url(true, true) }} により、URL全体が常に設定されます。

  8. 次に、stylesheets ブロックを定義します。ここでは、いくつかのアセット(css)を追加するために、 アセット管理 を使います。最初は、Pure.cssフレームワークを読み込みます。次に、便利なアイコンを使うための FontAwesome を読み込みます。最後に、テーマのcss/ フォルダにある custom.css ファイルです。ここには最初から便利なスタイルがありますが、さらに付け加えられます。また、必要であれば、さらにCSSファイルを追加できます。

  9. {{ assets.css()|raw }} による呼び出しは、テンプレートがCSSのリンクタグをレンダリングするトリガーとなるものです。

  10. javascripts ブロックも、stylesheets ブロックのように、JavaScriptファイルを置く場所です。この例では、すでにGravにバンドルされている ‘jquery’ ライブラリのみを追加しています。よって、ファイルへのパスを追加する必要はありません。

  11. {{ assets.js()|raw }} は、JavaScriptタグをレンダリングします。

  12. <body> タグでは、ページのフロントマターで定義した body_classes 変数がすべてclass属性として出力されます。

  13. header ブロックは、ページのHTMLヘッダー部分を出力します。注目すべき点は、ロゴが、{{ base_url == '' ? '/' : base_url }} というロジックとともに base_url でハイパーリンクされているところです。これは、サブディレクトリでない場合に、単に / にリンクすることを確定します。

  14. この例のテーマで、サイトタイトルは、{{ config.site.title }} とともにロゴとして出力されます。しかしもし必要なら、<img> タグと入れ替えることもできます。

  15. <nav> タグは、partials/navigation.html.twig へのリンクをincludeします。このtwigは、あらゆる 公開された ページをループし、メニューとして表示させるロジックを持ちます。デフォルトでは、入れ子になったページについて、ドロップダウンメニューに対応していますが、テーマの設定でこの設定をオフにすることもできます。メニューを生成するための方法を知るため、このナビゲーションファイルを見てみてください。

  16. {% block content %}{% endblock %} を使用すると、このベーステンプレートを利用するテンプレートから、コンテンツを入力する場所を指定します。default.html.twg で、ページコンテンツを上書きしていたことを思い出してください。

  17. footer ブロックは、シンプルなフッターです。必要に応じて修正できます。

  18. コンテンツブロックと同様、{% block bottom %}{% endblock %} ブロックは、テンプレートがカスタムの JavaScript の起動コードや解析コードを追記するためのものです。この例では、bottom アセットグループに追加されているすべての JavaScript を出力しています。詳しい内容は、アセット管理 のドキュメントをお読みください。

ステップ6: テーマのCSS

partials/base.html.twig ファイルは、アセット管理の do assets.add('theme://css/custom.css', 98) から、カスタムの CSS を参照していることにお気づきかもしれません。
このファイルには、 Pure.css フレームワークで提供されない、不足した CSS をカスタムして書き込みます。
Pure は、とてもミニマルなフレームワークなので、必要最小限のみ提供し、ほとんどの見た目の装飾的なスタイルは提供しません。

  1. user/themes/my-theme/css フォルダで、custom.css を見てください:
/* Core Styles */
* {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

body {
    font-size: 1rem;
    line-height: 1.7;
    color: #606d6e;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    color: #454B4D;
}

a {
    color: #1F8CD6;
    text-decoration: none;
}

a:hover {
    color: #175E91;
}

pre {
    background: #F0F0F0;
    margin: 1rem 0;
    border-radius: 2px;
}

blockquote {
    border-left: 10px solid #eee;
    margin: 0;
    padding: 0 2rem;
}

/* Utility Classes */
.wrapper {
    margin: 0 3rem;
}

.padding {
    padding: 3rem 1rem;
}

.left {
    float: left;
}

.right {
    float: right
}

.text-center {
    text-align: center;
}

.text-right {
    text-align: right;
}

.text-left {
    text-align: left;
}

/* Content Styling */
.header .padding {
    padding: 1rem 0;
}

.header {
    background-color: #1F8DD6;
    color: #eee;
}

.header a {
    color: #fff;
}

.header .logo {
    font-size: 1.7rem;
    text-transform: uppercase;
}

.footer {
    background-color: #eee;
}

/* Menu Settings */
.main-nav ul {
    text-align: center;
    letter-spacing: -1em;
    margin: 0;
    padding: 0;
}

.main-nav ul li {
    display: inline-block;
    letter-spacing: normal;
}

.main-nav ul li a {
    position: relative;
    display: block;
    line-height: 45px;
    color: #fff;
    padding: 0 20px;
    white-space: nowrap;
}

.main-nav > ul > li > a {
    border-radius: 2px;
}

/*Active dropdown nav item */
.main-nav ul li:hover > a {
    background-color: #175E91;
}

/* Selected Dropdown nav item */
.main-nav ul li.selected > a {
    background-color: #fff;
    color: #175E91;
}

/* Dropdown CSS */
.main-nav ul li {position: relative;}

.main-nav ul li ul {
    position: absolute;
    background-color: #1F8DD6;
    min-width: 100%;
    text-align: left;
    z-index: 999;

    display: none;
}
.main-nav ul li ul li {
    display: block;
}

/* Dropdown CSS */
.main-nav ul li ul ul {
    left: 100%;
    top: 0;
}

/* Active on Hover */
.main-nav li:hover > ul {
    display: block;
}

/* Child Indicator */
.main-nav .has-children > a {
    padding-right: 30px;
}
.main-nav .has-children > a:after {
    font-family: FontAwesome;
    content: '\f107';
    position: absolute;
    display: inline-block;
    right: 8px;
    top: 0;
}

.main-nav .has-children .has-children > a:after {
    content: '\f105';
}

上記は、とても標準的な CSS であり、 margin や、 font 、 color や、便利な class を設定しています。
基本的なコンテンツの style と、ドロップダウンメニューをレンダリングするための拡張的な style があります。
このファイルは、必要に応じて、気楽に編集してください。
もしくは、別の CSS ファイルを追加してもかまいません(head ブロックに、custom.css への参照を付け加えるだけです)。

ステップ7: テスト

このテーマが実際に動作するか確認するために、ブラウザを開いて、Gravのサイトを表示させてみてください。
以下のような表示になるはずです:

おめでとうございます!
あなたの最初のテーマができました!