Typlog themes are powered by Jinja template language. If you are not familiar with Jinja, you can learn the syntax at the offical documentation.
Blocks
We don't use {% extends %} in Typlog themes, each template inherits its own base layout which
contains many usefull code in <head>. What we actually need are blocks:
title
Redesign the document title:
{% block title %}{{ page.title }}{% endblock %}
lang
{% block lang %} is defined for the lang attribute on <html> tag:
<html lang="{% block lang %}{% endblock %}">
By default, it will use site.primary_lang or page.lang.
meta
Adding more <meta>, <link> tag in meta block:
{% block meta %}
<meta />
...
{% endblock %}
style
Adding css in style block:
{% block style %}
<style>...</style>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ static_url }}hello.css">
{% endblock }
body
This is the main block for your theme. All content should be in this block.
{% block body %}
<header>
...
</header>
<main>
...
</main>
<footer>
...
</footer>
{% endblock %}
script
This block is located at the bottom of <body> tag, we can add some scripts here if needed:
{% block script %}
<script>console.log('test');</script>
{% endblock %}
head_html
This block is located just below <body> tag, the default HTML is:
{% block head_html %}
{% if features.head_html %}{{ features.head_html|safe }}{% endif %}
{% endblock %}
foot_html
This block is located above {% block script %}, the default HTML is:
{% block foot_html %}
{% if features.foot_html %}{{ features.foot_html|safe }}{% endif %}
{% endblock %}
Variables
In the templates, we can use variables to render the real data into HTML. Here are all the variables:
site
This is a global variable, it contains the information about your site. The detail structure:
site:
id: integer
name: string
slug: string
primary_lang: string
languages: [string, string]
base_url: uri
active: boolean
summary?: string
favicon?: uri
icon?:
src: uri
width: integer
height: integer
logo?:
src: uri
width: integer
height: integer
cover?:
src: uri
width: integer
height: integer
license?: string
primary_links: [{ url, title, blank }]
secondary_links: [{ url, title, blank }]
color: string
socials: {}
sponsors: {}
list_url: string (e.g. /archive/)
posts_url?: string (e.g. /posts/, this attribute can be undefined)
audios_url?: string (e.g. /episodes/, this attribute can be undefined)
podcast?: # if enabled and has podcast information
title: string
subtitle: string
description: string
explicit: boolean
categories: []
type: string
image: uri
email: string
author: string
homepage: uri
keywords: string
copyright: string
links?: [ { icon, url, title, blank } ]
Please note, ? means this field may be None. For example:
{% if site.logo %}
{{ site.logo.src }}
{% endif %}
query
This is a global variable, it contains methods to query posts, episodes and etc.
.latest_subjects(subject_type=None, count=10, contains=None)
This will list the recent posts or episodes. This method is usually used in
home.j2 template. The subject_type value can be post and audio.
Here is an example in home.j2:
{% block body %}
<ul>
{% for post in query.latest_subjects('post') %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% endblock %}
contains is a list, which can contain: authors, tags, and content.
If there is no need to show authors, tags or full content of a post, we can pass
nothing to contains which will improve the performance of rendering.
.previous_subjects(subject, count=1, same_type=True, contains=None)
This method can list the previous posts or episodes of the given subject(post or episode).
This is usually used in item.j2 template:
{% block body %}
...
<div class="previous">
{% for post in query.previous_subjects(page, count=2) %}
<a href="{{ post.url }}>{{ post.title }}</a>
{% endfor %}
</div>
{% endblock %}
.previous_subject(subject, same_type=True, contains=None)
This method will return only the nearest previous subject. It is used in item.j2 template.
.next_subjects(subject, count=1, same_type=True, contains=None)
This method is the same as query.previous_subjects, but for next.
.next_subject(subject, same_type=True, contains=None)
This method is the same as query.previous_subject, but for next.
.tags(lang=None)
This method will list all the tags in your site:
{% block body %}
Tags:
{% for tag in query.tags() %}
<a href="{{ tag.url }}">{{ tag.title }}</a>
{% endfor %}
{% endblock %}
.years(subject_type=None)
This method will list all the years of your posts or episodes.
.authors()
This method will list all the authors, including hosts and guests in your site.
.config(name)
This is a PRO feature, it will load the JSON assets named with _config/{name}. The name should be the theme
name you registered in our theme registry. If the site doesn't has PRO plan, this method will return None.
Let's take an example. You are going to create a theme named paper:
{% set theme_conf = query.config('paper') %}
{% block style %}
{% if theme_conf and theme_conf.css_files %}
{% for src in theme_conf.css_files %}
<link rel="stylesheet" href="{{ src }}">
{% endfor %}
{% endif %}
{% endblock %}
To use this feature, the site has to create an asset file _config/paper in JSON format:
{
"css_files": [
"https://....",
"https://..."
]
}
features
This is a global variable, it contains the features this site supports:
features:
post: boolean
audio: boolean
photo: boolean
comment: boolean
subscriber: boolean
algolia: boolean
head_html: string # inject code for head_html
foot_html: string # inject code for foot_html
page
page is a context aware variable. It is different in each view.
In list.j2, the structure is:
page:
title: string
url: path
type: string
topic: object
items: []
prev_year?: integer
prev_url?: path
next_year?: integer
next_url?: path
A topic refers to a model about the list page. For instance, in a tag
page: /tags/web, this topic is the tag object, which means you
can set:
{% set tag = page.topic %}
This topic is usually used together with type. Here is an example of
how to use it:
{% macro render_tag(tag) %}
...
{% endmacro }
{% macro render_user(author) %}
...
{% endmacro }
...
{% block body %}
{% if page.type == 'tag' %}
{{ render_tag(page.topic) }}
{% elif page.type == 'author' %}
{{ render_tag(page.topic) }}
{% elif page.type == 'audio_list' %}
{{ render_podcast_info(site) }}
{% else %}
{{ render_site_info(site) }}
{% endif %}
{% endblock }
items is a list of posts and episodes of current visiting page.
In item.j2, page refers to a post, an episode, or a page object.
page:
id: integer
type: string # post, audio, page
slug: string
lang: string
title: string
cover?:
src: uri
width: integer
height: integer
subtitle?: string
content: string
url: path
tags: array of tags
authors: array of authors
published_at: datetime
updated_at: datetime
comment: string # open, closed, disabled
visibility: string # public, password, member
render_html: function # render_html(lazy=True) -> HTML
# only available with "post" type
license: string
review?:
reply_to?:
# only available with "audio" type
image: uri
explicit: boolean
season: integer
episode: integer
episode_type: string
duration: string
audio:
src: uri
size: integer
duration: integer
mimetype: string
In episode post, page contains two more attributes:
page:
# ....
hosts: array of authors
guests: array of authors
static_url
This variable will be a jsdelivr link. In development, serve-theme will replace
this variable to the local css file. It is usually used in:
{% block style %}
<link rel="stylesheet" href="{{ static_url }}hello.css">
{% endblock %}
tag
This is not a variable, but it may be used via:
query.tags()page.topicof tag type inlist.j2page.tagsinitem.j2
The structure of a tag:
tag:
id: integer
slug: string
title: string
summary: string
cover?:
src: uri
width: integer
height: integer
metadata: {}
url: path
author
This object may be one of these:
query.authors()page.topicof author type inlist.j2page.authorsin item.j2
The structure of a author:
author:
username: string
name: string
cover?:
src: uri
width: integer
height: integer
avatar: uri
bio: html
metadata: {}
url: path
# only in page.authors
role: string # primary, secondary
Filters
We can use all the built-in filters provided by Jinja. And in Typlog, there are two more filters:
xmldatetime
This filter is used to produce ISO formattted datetime, it is usually used to format .published_at:
<time datetime="{{ page.published_at|xmldatetime }}">
This is equal to:
<time datetime="{{ page.published_at.strftime('%Y-%m-%dT%H:%M:%SZ') }}">
thumbnail
This filter is used to resize the images, it accepts several image sizes:
<img src="{{ site.logo.src|thumbnail('ss') }}">
Available sizes:
s: small sizem: middle sizel: large sizess: crop to squared small sizesm: squared middle sizesl: squared large size
markdown
This filter will convert markdown text into HTML. When using this filter, make sure to apply the safe filter:
{{ my_text|markdown|safe }}
Macros
There are some built-in macros, which can be used to help you rendering the templates.
{% from "macros.j2" import render_social_icons %}
This macro is used for:
{{ render_social_icons(site) }}
{% from "macros.j2" import render_item, render_navigation %}
The `render_item` is usually used in `home.j2` and `list.j2`.
{% for item in query.latest_subjects() %}
{{ render_item(site, item) }}
{% endfor %}
The `render_navigation` macro is used in `list.j2` to render the year navigation:
{{ render_navigation(site, page) }}
{% from "macros.j2" import render_subject_visibility, render_subject_content %}
{% from "macros.j2" import render_subject_license, render_review_subject, render_subject_authors %}
These macros are used in `item.j2`. The `render_subject_content` is used to render the content part, it will
handle visibility automatically.
{{ render_subject_content(site, page) }}
Parameters for other macros:
{{ render_subject_visibility(site, page) }}
{{ render_subject_license(site, page) }}
{% if page.review %}
{{ render_review_subject(page.review) }}
{% endif %}
{{ render_subject_authors(site, page) }}
Snippets
Here are the built-in snippets which we can {% include %} them:
this is typlog brand foot
{% include "_snippets/brand_foot.j2" }
this is your site foot
{% include "_snippets/site_foot.j2" }
Used in item.j2:
{% include "_snippets/subject_share.j2" %}
{% include "_snippets/subject_foot.j2" %}
JavaScript hooks
In the rendered html, Typlog will inject a typlog.js script. This script has many features, we can
use js-? class to connect the hooks.
js-search: popup the search overlay
<input class="js-search" type="text" placeholder="Search...">
js-subscribe: popup the subscribe dialog
<button class="js-subscribe">Subscribe</button>
js-audio: render the player UI
<div class="entry-audio js-audio">
<audio src="{{ page.audio.src }}" preload="none" controls
{% if page.image %}data-image="{{ page.image|thumbnail('ss') }}"{% endif %}>
</audio>
</div>
This hook is already included in
{{ render_subject_content(site, page) }}
js-enjoy: click to send like event
<button class="js-enjoy">Enjoy</button>
This hook is already included in snippet:
{% include "_snippets/subject_share.j2" %}
Next step
Now, your theme looks pretty, and you are eager to use this theme, you have to submit the theme to Typlog theme registry.