新しいことを学ぶのに最適な方法は、例を参考に、そこから自分自身の創造物を作ってみることです。今回は、これと同じ方法論で、新しい Grav テーマを作成してみます。
Grav には、Spectre.css framework フレームワークを使用した Quark というクリーンでモダンなテーマが付属しています。
Spectre.cssは、高速で拡張性の高い開発のための、軽量でレスポンシブなモダンCSSフレームワークです。
Spectre は、基本的なタイポグラフィーとエレメントのスタイル、フレックスボックスをベースとしたレスポンシブ・レイアウト・システム、ベスト・プラクティスのコーディングと一貫したデザイン言語によるピュア CSS コンポーネントとユーティリティを提供します。
とわいえ、もっとシンプルなものから始めた方がいい場合もあります。
このチュートリアルのために、Yahoo! が開発した人気のフレームワーク Pure.css framework を利用したテーマを作成します。
Pure は、Bootstrap や Foundation のような大規模なフレームワークのオーバーヘッドなしであなたのサイトを開発するための基本的な、小さく、高速で、応答性の高い CSS フレームワークです。いくつかのモジュールが含まれており、それぞれ独立して使用することができます。
Pure.cssの全機能は、Pure.css プロジェクト・サイト でご覧いただけます。
また、テーマの更新情報は、テーマの重要な変更の概要が記載されていますので、ぜひお読みください。
以前のバージョンでは、デフォルトでベーステーマを作成する必要がありました。このプロセスは、新しい DevTools Pluginのおかげでスキップすることができます。
新しいテーマを作成する最初のステップは、DevTools Plugin をインストールすることです。これは2つの方法で行うことができます。
bin/gpm install devtools
この次のステップでは、コマンドライン を使用する必要があります。DevTools には、新しいテーマを作成するプロセスをより簡単にするための CLI コマンドがいくつか用意されているからです。
Grav をインストールしたルート・フォルダから以下のコマンドを入力します。
bin/plugin devtools new-theme
このプロセスでは、新しいテーマを作成するために必要な、いくつかの質問をします。
ここで はpure-blank を使って新しいテーマを作成しますが、他のベーステーマを継承したテンプレートを作成することも可能です
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
...
ブラウザで再読み込みすると、テーマが変更されているのが確認できるはずです。
変更と発展が可能な新しい基本テーマができたので、テーマを構成する要素を個々に見てみましょう。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
This is a sample structure but some things are required:
これらの項目は非常に重要であり、これらを含めない限り、あなたのテーマは確実に機能しません。
blueprints.yaml
- The configuration file used by Grav to get information on your theme. It can also define a form that the admin can display when viewing the theme details. This form will let you save settings for the theme. This file is documented in the Forms chapter.my-theme.php
- This file will be named according to your theme, but can be used to house any logic your theme needs. You can use any plugin event hook except onPluginsInitialized()
, however there is a theme specific onThemeInitialized()
hook specific for themes that you can use instead.my-theme.yaml
- This is the configuration used by the plugin to set options the theme might use.templates/
- This is a folder that contains the Twig templates to render your pages.これらの項目は、GPMでテーマを公開する場合に必要なものです。
CHANGELOG.md
- A file that follows the Grav Changelog Format to show changes in releases.LICENSE
- a license file, should probably be MIT unless you have a specific need for something else.README.md
- A 'Readme' that should contain any documentation for the theme. How to install it, configure it, and use it.screenshot.jpg
- 1009px x 1009px screenshot of the theme.thumbnail.jpg
- 300px x 300px screenshot of the theme.As you know from the previous chapter, each item of content in Grav has a particular filename, e.g. default.md
, which instructs Grav to look for a rendering Twig template called default.html.twig
. It is possible to put everything you need to display a page in this one file, and it would work fine. However, there is a better solution.
Utilizing the Twig Extends tag you can define a base layout with blocks that you define. This enables any twig template to extend the base template, and provides definitions for any block defined in the base. So look at the templates/default.html.twig
file and examine its content:
{% extends 'partials/base.html.twig' %}
{% block content %}
{{ page.content|raw }}
{% endblock %}
There are really two things going on here.
First, the template extends a template located in partials/base.html.twig
.
You don't need to include templates/
within Twig templates as Twig is already looking in templates/
as the root level for any template.
Second, the content
block is overridden from the base template, and the page's content is output in its place.
For consistency, it's a good idea to use the templates/partials
folder to contain Twig templates that represent either little chunks of HTML, or are shared. We also use templates/modular
for modular templates, and templates/forms
for any forms. You can create any sub-folders you like if you prefer to organize your templates differently.
If you look at the templates/partials/base.html.twig
you will see the meat of the HTML layout:
{% 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: If a variable is safe to render and contains HTML, always use the |raw
filter to make the template work with autoescape
turned on.
It is very important to either turn on the autoescape
setting in System Configuration or to remember to escape every single variable in template files to make your site safe against XSS attacks.
Please read over the code in the base.html.twig
file in order to better understand what is happening. There are several key things to note:
A theme_config
variable is set with the theme configuration. Because Twig doesn't work well with dashes, to retrieve variables with dashes (e.g. config.themes.my-theme
), we use the attribute()
Twig function to dynamically retrieve the my-theme
data from config.themes
.
The <html lang=...
item is set based on Grav's active language if enabled, else it uses the default_lang
as set in the theme_config
.
The {% block head %}{% endblock head %}
syntax defines an area in the base Twig template. Note that the use of head
in the {% endblock head %}
tag is not required, but is used here for readability. In this block we put things that are typically located in the HTML <head>
tag.
The <title>
tag is dynamically set based on the page's title
variable as set in the page header. The header.title
is a shortcut method but is equivalent to page.header.title
.
After a couple of standard meta tags are set, there is a reference to include partials/metadata.html.twig
. This file is located in the systems/templates/partials
folder and contains a loop that loops over the page's metadata. This is actually a merge of metadata from site.yaml
and any page-specific overrides.
The <link rel="icon"...
entry is set by pointing to a theme-specific image. In this case it's located in theme directory under images/logo.png
. The syntax for this is {{ url('theme://images/logo.png') }}
.
The <link rel="canonical"...
entry sets a canonical URL for the page that is always set to the full URL of the page via {{ page.url(true, true) }}
.
Now we define a block called stylesheets
, and in here we use the Asset Manager to add several assets. The first one loads the Pure.css framework. The second one loads FontAwesome to provide useful icons. The last entry points to a custom.css
file in the theme's css/
folder. In here are a few useful styles to get you started, but you can add more here. Also you can add other CSS file entries as needed.
The {{ assets.css()|raw }}
call is what triggers the template to render all the CSS link tags.
The javascripts
block, like the stylesheets
block is a good place to put your JavaScript files. In this example, we only add the 'jquery' library which is already bundled with Grav, so you don't need to provide a path to it.
The {{ assets.js()|raw }}
will render all the JavaScript tags.
The <body>
tag has a class attribute that will output anything you set in the body_classes
variable of the page's frontmatter.
The header
block has a few things that output the HTML header of the page. One important thing to note is the logo is hyperlinked to the base_url
with the logic: {{ base_url == '' ? '/' : base_url }}
. This is to ensure that if there is no subdirectory, the link is just /
.
The title of the site is output as the logo in this example theme with {{ config.site.title }}
but you could just replace this with a <img>
tag to a logo if you wanted.
The <nav>
tag actually contains a link to partials/navigation.html.twig
that contains the logic to loop over any visible pages and display them as a menu. By default it supports dropdown menus for nested pages, but this can be turned off via the theme's configuration. Have a look in this navigation file to get an idea of how the menu is generated.
The use of {% block content %}{% endblock %}
provides a placeholder that allows us to provide content from a template that extends this one. Remember we overrode this in default.html.twig
to output the page's content.
The footer
block contains a simple footer, you can easily modify this for your needs.
Similar to the content block, the {% block bottom %}{% endblock %}
is intended as a placeholder for templates to add custom JavaScript initialization or analytic codes. In this example, we output any JavaScript that was added to the bottom
Asset Group. Read more about this in the Asset Manager documentation.
You might have noticed that in the partials/base.html.twig
file we made reference to a custom theme css via Asset Manager: do assets.add('theme://css/custom.css', 98)
. This file will house any custom CSS we need to fill in the gaps not provided by the Pure.css framework. As Pure is a very minimal framework, it provides the essentials but almost no styling.
user/themes/my-theme/css
folder, take a look at 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';
}
This is pretty standard CSS that sets some basic margins, fonts, colors, and utility classes. There is some basic content styling and some more extensive styling required to render the drop-down menu. Feel free to modify this file as you need, or even add new CSS files (just ensure you add a reference in the head
block by following the example for custom.css
).
To see your theme in action, open your browser, and point it to your Grav site. You should see something like this:
おめでとうございます!最初のテーマが出来上がりました!!