Preface
Goal: Refactoring special page, Index, Archive, Category, and Tag.
Hexo has this four special page sections: Index, Archive, Category, Tag. The last three has very parts in common. So we can redefine as:
-
1: Blog Index
-
2: Section List
Source Code
You can download the source code of this article here.
Extract and run on CLI:
$ npm install
1: Blog Index
Special Layout: Index
Remember our last blog index. Let me refresh our setup.
Configuration
We need to setup permalink and stuff. Add these lines below our configuration.
-
- _config.yml
- gitlab.com/…/_config.yml
# Home page setting
index_generator:
path: '/blog'
order_by: -date
Layout: EJS Index: Redesigned
We can rewrite blog template. This time using my own custom class meta-item blog-item.
- themes/tutor-04/layout/index.ejs
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h1 class="title is-4"><%= config.author %></h1>
<h2 class="subtitle is-4"><%= config.subtitle %></h2>
</section>
<div class="post-list">
<% page.posts.each(function(post){ %>
<section class="section" id="archive">
<div class="meta-item blog-item">
<a href="<%- url_for(post.path) %>"><%= post.title %></a>
</div>
</section>
<% }) %>
</div>
</main>
Render: Browser
Open in your favorite browser.
Layout: EJS Index: Refactored
Things get complex when we design the inner parts. This why refactoring takes place.
-
- themes/tutor-04/layout/index.ejs
- gitlab.com/…/layout/index.ejs
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h1 class="title is-4"><%= config.author %></h1>
<h2 class="subtitle is-4"><%= config.subtitle %></h2>
</section>
<div class="post-list">
<% page.posts.each(function(post){ %>
<section class="section" id="archive">
<%- partial('summary/blog-item', {post: post}) %>
</section>
<% }) %>
</div>
</main>
Layout: EJS Blog Item
For each item, processed in its own template.
-
- themes/tutor-04/layout/summary/blog-item.ejs
- gitlab.com/…/layout/summary/blog-item.ejs
<div class="meta-item blog-item">
<strong><a class="meta_link"
href="<%- url_for(post.path) %>"><%= post.title %>
</strong></a>
<div class="meta">
<div class="meta_time is-pulled-left">
<i class="fa fa-calendar"></i>
<time datetime="<%= date(post.date, "YYYY-MM-DD[T]HH:mm:ss.SSS") %>">
<%= date(post.date, "MMM DD, YYYY") %></time>
</div>
<div class="is-pulled-right" id="meta_tags">
<% post.tags.forEach(function(tag){ %>
<a href="<%- url_for(tag.path) %>">
<span class="tag is-dark">
<span class="fa fa-tag"></span>
<%= tag.name %></span></a>
<% }) %>
</div>
</div>
<div class="is-clearfix"></div>
<div>
<% if(post.excerpt) { %>
<p><%- post.excerpt %></p>
<% } %>
</div>
<div class="read-more is-light">
<a href="<%- url_for(post.path) %>"
class="button is-dark is-small">Read More </a>
</div>
</div>
Basically, just moving the complex part somewhere else.
Render: Browser
Open in your favorite browser.
2: Simple Section
Special Layout: Archive, Category, Tag
What can be applied to one layout, can be applied to another. We are going to start with archive layout.
Layout: EJS Archive
Consider slight modification of our previous layout.
-
- themes/tutor-04/layout/archive.ejs
- gitlab.com/…/layout/archive.ejs
<%
var section_title = __('archive');
if (is_month()){
section_title += ': ' + page.year + '/' + page.month;
} else if (is_year()){
section_title += ': ' + page.year;
}
%>
<%-
partial('summary/post-list-simple', {
section_title: section_title
})
%>
.
Layout: EJS Post List: Simple
-
- themes/tutor-04/layout/summary/post-list-simple.ejs
- gitlab.com/…/layout/summary/post-list-simple.ejs
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h4 class="title is-4"><%= section_title %></h4>
</section>
<section class="section" id="archive">
<% page.posts.each(function(post){ %>
<div class="archive-item">
<div class="is-pulled-left"><a href="<%- url_for(post.path) %>">
<%= post.title %>
</a></div>
<div class="is-pulled-right has-text-right"><time>
<%= date(post.date, "DD MMM") %>
<span class="fa fa-calendar"></span>
</time></div>
<div class="is-clearfix"></div>
</div>
<% }) %>
</section>
</main>
Render: Browser
Open in your favorite browser. You should see, the modified version of archive page.
The Loop
How Does It Works?
The simplified range loop, is as below:
<% page.posts.each(function(post){ %>
...
<%= post.title %>
...
<% }) %>
3: Grouping Query: By Year
How about grouping the list above by year? Thi would be complicated in other templating engine. Luckily we are using embedded javascript.
Data Structure
Suppose we have an array of posts:
.
├── [post 01]
│ ├── title
│ ├── name
│ ├── ...
│ └── date
└── [post 01]
├── title
├── name
├── ...
└── date
First we have to insert year as field in every item. So we have something like this:
.
├── [post 01]
│ ├── title
│ ├── name
│ ├── ...
│ ├── date
│ └── year
└── [post 01]
├── title
├── name
├── ...
├── date
└── year
We can achieve this by using mapping in javascript.
// grouping by date
const posts = page.posts.map(post => ({
...post, year: date(post.date, "YYYY")
}));
As we already have year field, we can group this data structure by year.
const postsByYear = groupBy(posts, 'year');
While the groupBy function, is provided in mozilla site:
Javascript: EJS Post List: By Year
The complete javascript source is as below:
// Data Model
// https://developer.mozilla.org/.../Array/reduce
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
// grouping by date
const posts = page.posts.map(post => ({
...post, year: date(post.date, "YYYY")
}));
const postsByYear = groupBy(posts, 'year');
Layout: EJS Post List: By Year
Pour down the javascript above, between the tag below <% … _%>:
-
- themes/tutor-04/layout/summary/post-list-by-year.ejs
- gitlab.com/…/layout/summary/post-list-by-year.ejs
<%
...
_%>
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h4 class="title is-4"><%= section_title %></h4>
</section>
<%
const postsSorted = Object.keys(postsByYear).sort(date, -1);
postsSorted.forEach(function (year){
%>
<section class="section box">
<div class ="anchor-target archive-year"
id="<%= year %>"><%= year %></div>
<div class="archive-list archive-p3">
<% postsByYear[year].forEach(function(post){ %>
<div class="archive-item">
<div class="is-pulled-left"><a href="<%- url_for(post.path) %>">
<%= post.title %>
</a></div>
<div class="is-pulled-right has-text-right"><time>
<%= date(post.date, "DD MMM") %>
<span class="fas fa-calendar"></span>
</time></div>
<div class="is-clearfix"></div>
</div>
<% }) %>
</div>
</section>
<% }) %>
</main>
Render: Browser
Open in your favorite browser. You should see, the archive page grouped by year.
The Loop
How Does It Works?
The simplified range loop, is as below:
<%
const postsSorted = Object.keys(postsByYear).sort(date, -1);
postsSorted.forEach(function (year){
%>
...
<%= year %>
...
<% postsByYear[year].forEach(function(post){ %>
...
<%= post.title %>
...
<% }) %>
...
<% }) %>
While postsByYear is:
const postsByYear = groupBy(posts, 'year');
I hope that, this explanation is clear.
Update
For unknown reason. Hexo break break my logic.
This .sort(date, -1)
is no longer working.
The solution is to use .sort(date, -1).reverse()
.
4: Grouping Query: By Month
How about grouping the list above by month? How complex this would be?
Data Structure
Again, suppose we have an array of posts:
.
├── [post 01]
│ ├── title
│ ├── name
│ ├── ...
│ └── date
└── [post 01]
├── title
├── name
├── ...
└── date
First we have to insert both month and year as field in every item. Wait… why both year and month? Because we have two loops. The outer loop would process by year, and the inner one wouyld process by month.
We have should have something like this:
.
├── [post 01]
│ ├── title
│ ├── name
│ ├── ...
│ ├── date
│ ├── year
│ └── month
└── [post 01]
├── title
├── name
├── ...
├── date
├── year
└── month
We can achieve this by using mapping in javascript. Actually this is almost exactly the same as our previous example. I guess, this is not so complicated, as I thought it was.
const posts = page.posts.map(post => ({
...post,
year: date(post.date, "YYYY"),
month: date(post.date, "MMMM")
}));
As we already have year field, we can group this data structure by year.
const postsByYear = groupBy(posts, 'year');
Wait..? Where is the group by month? Well…. this will be processed in inner loop later.
Javascript: EJS Post List: By Year
The complete javascript soource is as below:
// Data Model
// https://developer.mozilla.org/.../Array/reduce
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
// grouping by date
const posts = page.posts.map(post => ({
...post,
year: date(post.date, "YYYY"),
month: date(post.date, "MMMM")
}));
const postsByYear = groupBy(posts, 'year');
// additional
const yearToday = date(new Date(), 'YYYY');
Layout: EJS Post List: By Month
Pour down the javascript above, between the tag below <% … _%>:
-
- themes/tutor-04/layout/summary/post-list-by-month.ejs
- gitlab.com/…/layout/summary/post-list-by-month.ejs
<%
...
_%>
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h4 class="title is-4"><%= section_title %></h4>
</section>
<%
const postsSorted = Object.keys(postsByYear).sort(date, -1);
postsSorted.forEach(function (year){
%>
<section class="section box">
<div class ="anchor-target archive-year" id="<%= year %>">
<% if (year == yearToday) {%>
This year's posts (<%= year %>)
<% } else { %>
<%= year %>
<% } %>
</div>
<%
const postsByMonth = groupBy(postsByYear[year], 'month');
Object.keys(postsByMonth).forEach(function (month){
%>
<div class="archive-p4">
<div class ="archive-month"
id="<%= year %>-<%= month %>">
<%= month %> <%= year %></div>
<div class="archive-list archive-p3">
<% postsByMonth[month].forEach(function(post){ %>
<div class="archive-item">
<div class="is-pulled-left"><a href="<%- url_for(post.path) %>">
<%= post.title %>
</a></div>
<div class="is-pulled-right has-text-right"><time>
<%= date(post.date, "DD MMM") %>
<span class="fas fa-calendar"></span>
</time></div>
<div class="is-clearfix"></div>
</div>
<% }) %>
</div>
</div>
<% }) %>
</section>
<% }) %>
</main>
Render: Browser
Open in your favorite browser. You should see, the archive page grouped by month.
The Loop
How Does It Works?
The simplified range loop, is as below:
<%
const postsSorted = Object.keys(postsByYear).sort(date, -1);
postsSorted.forEach(function (year){
%>
...
<%= year %>
<%
const postsByMonth = groupBy(postsByYear[year], 'month');
Object.keys(postsByMonth).forEach(function (month){
%>
...
<%= month %>
...
<% postsByMonth[month].forEach(function(post){ %>
...
<%= post.title %>
...
<% }) %>
...
<% }) %>
...
<% }) %>
Update
For unknown reason. Hexo break break my logic.
This .sort(date, -1)
is no longer working.
The solution is to use .sort(date, -1).reverse()
.
5: Responsive
Doing responsive is just a matter of changine view, without altering the javascript data model.
Layout: EJS Post List: Responsive
Pour down the javascript above, between the tag below <% … _%>:
-
- themes/tutor-04/layout/summary/post-list-responsive.ejs
- gitlab.com/…/layout/summary/post-list-responsive.ejs
<%
...
_%>
<main role="main"
class="column is-full box-deco has-background-white">
<section class="section">
<h4 class="title is-4"><%= section_title %></h4>
</section>
<%
const postsSorted = Object.keys(postsByYear).sort(date, -1);
postsSorted.forEach(function (year){
%>
<section class="section">
<div class ="anchor-target archive-year" id="<%= year %>">
<% if (year == yearToday) {%>
This year's posts (<%= year %>)
<% } else { %>
<%= year %>
<% } %>
</div>
<div class="columns is-multiline ">
<%
const postsByMonth = groupBy(postsByYear[year], 'month');
Object.keys(postsByMonth).forEach(function (month){
%>
<div class="column is-full-mobile
is-half-tablet is-one-third-widescreen">
<section class="panel is-light">
<div class="panel-header" id="<%= year %>-<%= month %>">
<p><%= month %> <%= year %></p>
<span class="fa fa-archive"></span>
</div>
<div class="panel-body has-background-white">
<div class="archive-list archive-p3">
<% postsByMonth[month].forEach(function(post){ %>
<div class="archive-item">
<div class="is-pulled-left"><a href="<%- url_for(post.path) %>">
<%= post.title %>
</a></div>
<div class="is-pulled-right has-text-right"><time>
<%= date(post.date, "DD MMM") %>
<span class="fas fa-calendar"></span>
</time></div>
<div class="is-clearfix"></div>
</div>
<% }) %>
</div>
</div>
</section>
</div>
<% }) %>
</div>
</section>
<% }) %>
</main>
How Does It Works?
These bulma classes below do all the hardworks:
-
is-full-mobile
-
is-half-tablet
-
is-one-third-widescreen
<section class="columns is-multiline" id="archive">
<% terms.each(function(item){ %>
<div class="column
is-full-mobile
is-half-tablet
is-one-third-widescreen">
...
</div>
<% }) %>
</section>
Now we can use them for
-
Archive section page,
-
Category section page, and
-
Tag section page.
Consider have a look at difference for each screen size.
Layout: EJS Archive
-
- themes/tutor-04/layout/archive.ejs
- gitlab.com/…/layout/archive.ejs
<%
var section_title = __('archive');
if (is_month()){
section_title += ': ' + page.year + '/' + page.month;
} else if (is_year()){
section_title += ': ' + page.year;
}
%>
<%-
partial('summary/post-list-responsive', {
section_title: section_title
})
%>
Open in your favorite browser.
.
Layout: EJS Category
-
- themes/tutor-04/layout/category.ejs
- gitlab.com/…/layout/category.ejs
<%-
partial('summary/post-list-responsive', {
section_title: __('category') + ': ' + page.category
})
%>
Open in your favorite browser.
.
Layout: EJS Tag
-
- themes/tutor-04/layout/tag.ejs
- gitlab.com/…/layout/tag.ejs
<%-
partial('summary/post-list-responsive', {
section_title: __('tag') + ': ' + page.tag
})
%>
Open in your favorite browser.
.
I think that’s all about this article.
What is Next ?
There are, some interesting topic about Custom Output in Hexo. Consider continue reading [ Hexo - Custom Output ].
Thank you for reading.