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.topic
of tag type inlist.j2
page.tags
initem.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.topic
of author type inlist.j2
page.authors
in 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.