Skip to content

Commit e47decc

Browse files
southerntofuKeats
andauthored
Make themes more flexible (#1004)
* Site templates can replace theme templates * Integrate test case within test_site/ * Full backwards-compatibility with testcase in test_site * Refine test case * Call parent's block in child template for test case * Check both templates are applied * Follow testing advice * Test for 'include' in themes and shortcodes * Documentation for themes and how to extend them Co-authored-by: Vincent Prouillet <[email protected]>
1 parent 2230968 commit e47decc

File tree

12 files changed

+84
-94
lines changed

12 files changed

+84
-94
lines changed

components/site/src/lib.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,7 @@ impl Site {
9191
);
9292
let mut tera_theme = Tera::parse(&theme_tpl_glob)
9393
.map_err(|e| Error::chain("Error parsing templates from themes", e))?;
94-
rewrite_theme_paths(
95-
&mut tera_theme,
96-
tera.templates.values().map(|v| v.name.as_ref()).collect(),
97-
&theme,
98-
);
94+
rewrite_theme_paths(&mut tera_theme, &theme);
9995
// TODO: we do that twice, make it dry?
10096
if theme_path.join("templates").join("robots.txt").exists() {
10197
tera_theme

components/utils/src/templates.rs

Lines changed: 16 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -61,52 +61,21 @@ pub fn render_template(
6161
}
6262
}
6363

64-
/// Rewrites the path from extend/macros of the theme used to ensure
65-
/// that they will point to the right place (theme/templates/...)
66-
/// Include is NOT supported as it would be a pain to add and using blocks
67-
/// or macros is always better anyway for themes
68-
/// This will also rename the shortcodes to NOT have the themes in the path
69-
/// so themes shortcodes can be used.
70-
pub fn rewrite_theme_paths(tera_theme: &mut Tera, site_templates: Vec<&str>, theme: &str) {
71-
let mut shortcodes_to_move = vec![];
72-
let mut templates = HashMap::new();
73-
let old_templates = ::std::mem::replace(&mut tera_theme.templates, HashMap::new());
74-
75-
// We want to match the paths in the templates to the new names
76-
for (key, mut tpl) in old_templates {
77-
tpl.name = format!("{}/templates/{}", theme, tpl.name);
78-
// First the parent if there is one
79-
// If a template with the same name is also in site, assumes it overrides the theme one
80-
// and do not change anything
81-
if let Some(ref p) = tpl.parent.clone() {
82-
if !site_templates.contains(&p.as_ref()) {
83-
tpl.parent = Some(format!("{}/templates/{}", theme, p));
84-
}
85-
}
86-
87-
// Next the macros import
88-
let mut updated = vec![];
89-
for &(ref filename, ref namespace) in &tpl.imported_macro_files {
90-
updated.push((format!("{}/templates/{}", theme, filename), namespace.to_string()));
91-
}
92-
tpl.imported_macro_files = updated;
93-
94-
if tpl.name.starts_with(&format!("{}/templates/shortcodes", theme)) {
95-
let new_name = tpl.name.replace(&format!("{}/templates/", theme), "");
96-
shortcodes_to_move.push((key, new_name.clone()));
97-
tpl.name = new_name;
98-
}
99-
100-
templates.insert(tpl.name.clone(), tpl);
101-
}
102-
103-
tera_theme.templates = templates;
104-
105-
// and then replace shortcodes in the Tera instance using the new names
106-
for (old_name, new_name) in shortcodes_to_move {
107-
let tpl = tera_theme.templates.remove(&old_name).unwrap();
108-
tera_theme.templates.insert(new_name, tpl);
64+
/// Rewrites the path of duplicate templates to include the complete theme path
65+
/// Theme templates will be injected into site templates, with higher priority for site
66+
/// templates. To keep a copy of the template in case it's being extended from a site template
67+
/// of the same name, we reinsert it with the theme path prepended
68+
pub fn rewrite_theme_paths(tera_theme: &mut Tera, theme: &str) {
69+
let theme_basepath = format!("{}/templates/", theme);
70+
let mut new_templates = HashMap::new();
71+
for (key, template) in &tera_theme.templates {
72+
let mut tpl = template.clone();
73+
tpl.name = format!("{}{}", theme_basepath, key);
74+
new_templates.insert(tpl.name.clone(), tpl);
10975
}
76+
// Contrary to tera.extend, hashmap.extend does replace existing keys
77+
// We can safely extend because there's no conflicting paths anymore
78+
tera_theme.templates.extend(new_templates);
11079
}
11180

11281
#[cfg(test)]
@@ -117,7 +86,7 @@ mod tests {
11786
#[test]
11887
fn can_rewrite_all_paths_of_theme() {
11988
let mut tera = Tera::parse("test-templates/*.html").unwrap();
120-
rewrite_theme_paths(&mut tera, vec!["base.html"], "hyde");
89+
rewrite_theme_paths(&mut tera, "hyde");
12190
// special case to make the test work: we also rename the files to
12291
// match the imports
12392
for (key, val) in &tera.templates.clone() {
@@ -133,7 +102,7 @@ mod tests {
133102
);
134103
assert_eq!(
135104
tera.templates["hyde/templates/child.html"].parent,
136-
Some("hyde/templates/index.html".to_string())
105+
Some("index.html".to_string())
137106
);
138107
}
139108
}

docs/content/documentation/themes/creating-a-theme.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,6 @@ theme, with live reload working as expected.
5151
Make sure to commit every directory (including `content`) in order for other people
5252
to be able to build the theme from your repository.
5353

54-
### Caveat
55-
56-
Please note that [include paths](https://tera.netlify.com/docs#include) can only be used in normal templates.
57-
Theme templates should use [macros](https://tera.netlify.com/docs#macros) instead.
58-
5954
## Submitting a theme to the gallery
6055

6156
If you want your theme to be featured in the [themes](@/themes/_index.md) section
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
+++
2+
title = "Extending a theme"
3+
weight = 30
4+
+++
5+
6+
When your site uses a theme, you can replace parts of it in your site's templates folder. For any given theme template, you can either override a single block in it, or replace the whole template. If a site template and a theme template collide, the site template will be given priority. Whether a theme template collides or not, theme templates remain accessible from any template within `theme_name/templates/`.
7+
8+
## Replacing a template
9+
10+
When a site template and a theme template have the same path, for example `templates/page.html` and `themes/theme_name/templates/page.html`, the site template is the one that will be used. This is how you can replace a whole template for a theme.
11+
12+
## Overriding a block
13+
14+
If you don't want to replace a whole template, but override parts of it, you can [extend the template](https://tera.netlify.app/docs/#inheritance) and redefine some specific blocks. For example, if you want to override the `title` block in your theme's page.html, you can create a page.html file in your site templates with the following content:
15+
16+
```
17+
{% extends "theme_name/templates/page.html" %}
18+
{% block title %}{{ page.title }}{% endblock %}
19+
```
20+
21+
If you extend `page.html` and not `theme_name/templates/page.html` specifically, it will extend the site's page template if it exists, and the theme's page template otherwise. This makes it possible to override your theme's base template(s) from your site templates, as long as the theme templates do not hardcode the theme name in template paths. For instance, children templates in the theme should use `{% extends 'index.html' %}`, not `{% extends 'theme_name/templates/index.html' %}`.

test_site/templates/index.html

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,11 @@
1-
<!DOCTYPE html>
2-
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8">
5-
<meta name="apple-mobile-web-app-capable" content="yes">
6-
<meta name="viewport" content="width=device-width, initial-scale=1">
7-
<meta name="description" content="{{ config.description }}">
8-
<meta name="author" content="{{ config.extra.author.name }}">
9-
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet">
10-
<link href="{{ get_url(path="/site.css", cachebust=true) | safe }}" rel="stylesheet">
11-
<title>{{ config.title }}</title>
12-
</head>
131

14-
<body>
15-
<div class="content">
16-
{% block content %}
17-
<div class="list-posts">
18-
{% for page in section.pages %}
19-
<article>
20-
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
21-
</article>
22-
{% endfor %}
23-
</div>
24-
{% endblock content %}
2+
{% extends 'sample/templates/index.html' %}
3+
{% block content %}
4+
<div class="list-posts">
5+
{% for page in section.pages %}
6+
<article>
7+
<h3 class="post__title"><a href="{{ page.permalink }}">{{ page.title }}</a></h3>
8+
</article>
9+
{% endfor %}
2510
</div>
26-
<script src="{{ get_url(path="scripts/hello.js") | safe }}"
27-
integrity="sha384-{{ get_file_hash(path="scripts/hello.js") }}"></script>
28-
</body>
29-
</html>
11+
{% endblock content %}

test_site/templates/section.html

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
{% extends "index.html" %}
2-
1+
{% extends "sample/templates/section-specific-extends.html" %}
32
{% block content %}
4-
{% for page in section.pages %}
3+
{% for page in section.pages %}
54
{{page.title}}
65
{% endfor %}
7-
{{ section.relative_path | safe }}
8-
{% for sub in section.subsections %}
9-
{% set subsection = get_section(path=sub) %}
10-
{{subsection.title}}
11-
Sub-pages: {{subsection.pages | length}}
12-
{% endfor %}
6+
{{ super() }}
137
{% endblock content %}
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
1-
Hello
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="apple-mobile-web-app-capable" content="yes">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<meta name="description" content="{{ config.description }}">
8+
<meta name="author" content="{{ config.extra.author.name }}">
9+
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet">
10+
<link href="{{ config.base_url }}/site.css" rel="stylesheet">
11+
<title>{{ config.title }}</title>
12+
</head>
13+
14+
<body>
15+
<div class="content">
16+
{% block content %}Hello{% endblock content %}
17+
</div>
18+
</body>
19+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends "index.html" %}
2+
{% block content %}
3+
{{ section.relative_path | safe }}
4+
{% for sub in section.subsections %}
5+
{% set subsection = get_section(path=sub) %}
6+
{{subsection.title}}
7+
Sub-pages: {{subsection.pages | length}}
8+
{% endfor %}
9+
{% endblock content %}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{% extends 'index.html' %}
2+
{% block content %}
3+
I'm overriden in all cases so seeing me is a bug
4+
{% endblock %}

0 commit comments

Comments
 (0)