添加时间轴
2025-09-06  / site  / feature

该页面由AI翻译并经过人工校对,你可能想要 查看原文

创建时间轴页面

在 /source/[lang] 目录下创建 timeline/index.md 文件,并设置内容如下。

data保存站点的更新历史,startPoint是开始点。

/source/en/timeline/index.md 的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
title: Timeline
date: 2025-09-01 16:53:19
type: "timeline"
lang: en
startPoint:
title: Singularity
content: Over the course of 14 billion years since the Big Bang, the first light elements formed, neutral atoms coalesced, the earliest stars ignited, galaxies clustered, and eventually, our solar system emerged—giving rise to life on Earth. As part of this vast evolutionary saga, we too will ultimately vanish, becoming but a fleeting chapter in the grand narrative of existence. When that day comes, what part of us will endure? What legacy of our being will persist beyond our physical demise? Will it be the knowledge we have uncovered, the art we have created, the values we have upheld, or the impact we have imprinted upon the world? Will it be the dreams we dared to dream, the lives we touched, or the changes we set in motion? In the face of inevitable oblivion, it is these contributions—our ideas, our culture, our stewardship of the Earth—that may echo through the corridors of time, long after we are gone.
data:
- title: Create a site using the Chic theme.
date: 2023-10-23
link: 2025/07/hello-world
- title: Use Oranges theme and add album.
date: 2025-7-27
- title: Add 404 page and optimize search.
date: 2025-7-28
- title: Use Arch theme to support language switching; Optimize Arch to support Oranges original features; add prompt to view the original text on translated pages.
date: 2025-8-14
link: 2025/08/arch-theme-optimization
- title: Optimize the image storage directory to reduce redundant static resources in multilingual sites; add navigation menu icons and modify some styles.
date: 2025-8-31
- title: Add timeline page which supports viewing by month or year.
date: 2025-9-2
---

/source/zh-CN/timeline/index.md 的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
---
title: 时间轴
date: 2025-09-01 16:53:19
type: "timeline"
lang: zh-CN
startPoint:
title: 奇点
content: 自宇宙大爆炸以来的140亿年里,第一批轻元素形成,中性原子聚合,最早的恒星燃烧,星系聚集成团,最终,我们的太阳系诞生——地球上的生命由此而来。在这浩瀚的进化史诗中,我们终将消逝,成为存在宏大叙事中转瞬即逝的一章。当那一天来临,我们何物能永存?超越肉体消亡的永恒印记究竟是什么?是我们发掘的知识、创造的艺术、坚守的价值观,还是对世界留下的烙印?是敢于追逐的梦想、触动过的人生,抑或引发的变革浪潮?面对不可避免的湮没,正是这些贡献——我们的思想,我们的文化,我们对地球的守护——将在我们逝去很久之后,在时间的长河中回荡。
padZero: false
data:
- title: 建立站点,使用Chic主题。
date: 2023-10-23
link: 2025/07/hello-world
- title: 使用Oranges主题,增加相册功能。
date: 2025-7-27
- title: 增加404页面,优化搜索功能。
date: 2025-7-28
- title: 使用Arch主题以支持切换语言;优化Arch以支持Oranges原有功能;翻译的页面增加查看原文的提示。
date: 2025-8-14
link: 2025/08/arch-theme-optimization
- title: 优化图片保存目录以减少多语言站点下冗余的静态资源;添加导航菜单的icon,修改部分样式。
date: 2025-8-31
- title: 增加时间轴页面,支持按月或按年查看。
date: 2025-9-2
---

设置页面布局

post.ejs

更新 /themes/arch/layout 下的文件 post.ejs 并添加如下内容。

1
2
3
4
5
6
7
8
9
10
11
12
<!-- timeline -->
<% if(page.type === 'timeline') { %>
<div class="container post-details album-index">
<div class="markdown-body">
<% if (page.data && page.data.length) { %>
<% let viewByYear = __('viewByYear') %>
<% let viewByMonth = __('viewByMonth') %>
<%- partial('_partial/timeline', { data: page.data, lang: page.lang, padZero: page.padZero, viewByYear, viewByMonth,startPoint: page.startPoint, title: page.title }) %>
<% } %>
</div>
</div>
<% } %>
image.png

timeline.ejs

在 /themes/arch/layout/_patial 下创建文件 timeline.ejs 并设置以下内容。

非常感谢 https://github.com/PrintNow/TimeLine 的作者。本文件的大部分内容取自该文件。以下列出了一些差异。

  1. time重命名为date,并且不是时间戳,而是 yyyy-m-d 格式的字符串。
  2. 添加 groupBy 功能(按年或月),点击可切换分组条件。
  3. 添加 startPoint,过多的行会折叠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<div class='albums-container'>
<ul class='time-line'></ul>
</div>
<script type='text/javascript'>
let data = <%- JSON.stringify(data || []) %>;
const lang = <%- JSON.stringify(lang || 'en') %>;

const startPoint = <%- JSON.stringify(startPoint || '{}') %>;

const viewByYear = <%- JSON.stringify(viewByYear || 'View by Year') %>;
const viewByMonth = <%- JSON.stringify(viewByMonth || 'View by Month') %>;

const padZero = <%- JSON.stringify(padZero || false) %>;

// 按年月分组,组内降序,key降序
let groupByMonthFlag = true;

const eng_months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];


// 日期降序
data = data.sort((a, b) => new Date(b.date) - new Date(a.date));

let time_line = document.querySelector('.time-line');
// 添加data-lang特性
time_line.dataset.lang = lang;

renderTimeline(data);

// data:[{title: '标题', link: '链接', date: '2025-7-1'}]
function renderTimeline(data = []) {
if (!(Array.isArray(data) && data.length)) return;
let grouped2DArr = groupByMonthFlag ? groupByYearMonth(data) : groupByYear(data);
let html = '';

time_line.innerHTML = '';//清空时间线
for (let item of grouped2DArr) {
html += `
<li class='tl-header group-condition' title='${groupByMonthFlag ? viewByYear : viewByMonth}'>
<p class='group-condition'>${formatGroup(item[0], groupByMonthFlag)}</p>
</li>
<ul class='tl-body'>`;

for (let obj of item[1]) {
html += `<li>
<span>${formatRest(obj['date'], groupByMonthFlag)}</span>
${obj['link'] ? `<p class='title'><a href='/${lang}/${obj['link']}'>${obj['title']}</a></p>` : `<p class='title'>${obj['title']}</p>`}
</li>`
}
html += `</ul>`;

}
if (startPoint) {
html += `<li class='tl-header start'>
<p>${startPoint.title}</p>
</li>`;
html += `<ul class='tl-body start'>
<li>
<div class="title section">
<input class="content-check" type="checkbox" id="c1" hidden />
<div class="content">
<p class="text">${startPoint.content}</p>
<label for="c1" class="btn"></label>
</div>
</div>
</li>
</ul>`;
}
time_line.innerHTML = html;
}

time_line.addEventListener('click', toggleGroupCondition);

// 切换分组条件
function toggleGroupCondition(e) {
// 不是点击 group-condition
if (!e.target.classList.contains('group-condition')) {
return;
}
groupByMonthFlag = !groupByMonthFlag;
if (time_line.classList.contains('group-by-year')) {
time_line.classList.remove('group-by-year');
} else {
time_line.classList.add('group-by-year');
}
renderTimeline(data);
}

// 格式化分组条件
function formatGroup(dateStr, isGroupByMonth) {
let date = new Date(dateStr);

let year = date.getFullYear();
if (!isGroupByMonth) {
if (lang === 'zh-CN') {
return addLeadingZero(year, padZero) + '年';
}
return year;
}

let month = date.getMonth() + 1;

if (lang === 'zh-CN') {
year = addLeadingZero(year, padZero) + '年';
month = addLeadingZero(month, padZero) + '月';
return year + month;
}

// 英语采用day-month-year展示
return eng_months[month - 1] + ' ' + year;
}

// 格式化分组内日期
function formatRest(dateStr, isGroupByMonth) {
let date = new Date(dateStr);

let day = date.getDate();
if (isGroupByMonth) {
if (lang === 'zh-CN') {
return addLeadingZero(day, padZero) + '日';
}
return day + getOrdinalSuffix(day);
}

let month = date.getMonth() + 1;
if (lang === 'zh-CN') {
month = addLeadingZero(month, padZero) + '月';
day = addLeadingZero(day, padZero) + '日';
return month + day;
}

// 英语采用day-month-year展示
month = eng_months[month - 1];
day = day + getOrdinalSuffix(day);
return day + ' ' + month;
}

function groupByYear(data) {
// 按年分组,组内降序
const groupObj = data.reduce((acc, item) => {
const year = new Date(item.date).getFullYear();
if (!acc[year]) acc[year] = [];
acc[year].push(item);
return acc;
}, {});
// key降序
return Object.entries(groupObj).sort((a, b) => b[0] - a[0]);
}

function groupByYearMonth(data) {
// 按月分组,组内降序
const groupObj = data.reduce((acc, item) => {
const date = new Date(item.date);
const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
if (!acc[yearMonth]) acc[yearMonth] = [];
acc[yearMonth].push(item);
return acc;
}, {});
// key降序
return Object.entries(groupObj).sort((a, b) => b[0].localeCompare(a[0]));
}

// 添加前导零
function addLeadingZero(num, padZero) {
return padZero && num < 10 ? '0' + num : num;
}

// 英文序数词
function getOrdinalSuffix(day) {
if (day >= 11 && day <= 13) {
return 'th';
}
switch (day % 10) {
case 1: return 'st';
case 2: return 'nd';
case 3: return 'rd';
default: return 'th';
}
}

</script>
<link rel='stylesheet' href='/css/timeline.css' />

设置页面样式

timeline.css

在 /themes/arch/source/css 下创建文件 timeline.css 并设置以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
.time-line li {
list-style-type: none;
}

.time-line .tl-header {
cursor: pointer;
background-color: #23b7e5;
color: white;
text-align: center;
display: inline-block;
padding: 0 14px;
border-radius: 8px;
}
.time-line .tl-header p {
font-size: 14px;
line-height: 8px;
}

/*时间垂直线*/
.time-line .tl-body {
position: relative;
border-left: 4px solid #23b7e5;
margin-left: 40px;
min-height: 88px;
padding-top: 1px;
padding-bottom: 1px;
}

/*时间线左侧,日 样式*/
.time-line .tl-body span {
position: absolute;
left: -50px;
top: 16px;
font-weight: 400;
font-size: 14px;
}

.time-line .tl-body li {
display: block;
position: relative;
left: -24px;
margin: 8px 0;
}
.time-line .tl-body li:before {
position: absolute;
content: "";
top: 21px;
left: -15px;
width: 6px;
height: 6px;
background: #fff;
border: 2px solid #23b7e5;
border-radius: 50%;
}

@media (max-width: 770px) {
.time-line .tl-body li:before {
left: -11px;
}
}

.time-line.group-by-year .tl-header:not(.start) {
padding: 0 28px;
}

.time-line.group-by-year[data-lang="zh-CN"] .tl-header:not(.start) {
padding: 0 21px;
}

.time-line[data-lang="zh-CN"]:not(.group-by-year) .tl-body:not(.start) {
margin-left: 48px;
}

.time-line.group-by-year .tl-body:not(.start) span {
left: -76px;
}

/*设置时间线内容样式*/
.time-line .tl-body li .title {
background-color: #23b7e5;
color: white;
display: inline-block;
margin: 8px 0;
margin-left: 10px;
padding: 8px 16px;
border-radius: 6px;
font-weight: 400;
}
.time-line .tl-body li .title:before {
content: "";
position: absolute;
left: -8px;
border: 10px solid rgba(0, 0, 0, 0);
border-right-color: #23b7e5;
}

.time-line .tl-body li a {
text-decoration: none;
color: white;
border-bottom: none;
}

.time-line .tl-body li a:hover {
border-bottom: 1px solid #fff;
}

/*设置时间线开始样式*/
.tl-header.start {
margin-top: 100px;
padding: 0 8px;
position: relative;
left: -4px;
cursor: default;
}

.time-line[data-lang="zh-CN"] .tl-header.start {
padding: 0 30px;
}

.time-line[data-lang="zh-CN"]:not(.group-by-year) .tl-header.start {
left: 4px;
}

.time-line[data-lang="zh-CN"]:not(.group-by-year) .tl-body.start {
margin-left: 94px;
}

.tl-header.start::before {
content: "";
position: absolute;
top: -96px;
height: 92px;
border-left: 4px dotted #23b7e5;
}

.tl-body.start {
border: none;
margin-left: 86px;
margin-top: -50px;
}

.tl-body.start li::before {
content: none !important;
}
.tl-body.start .title {
margin-left: 50px;
}

.tl-body.start .title ::before {
top: 24px !important;
}

.tl-header.start > .title {
font-weight: 500;
}

.tl-body.start .section {
display: flex !important;
}

.tl-body.start .content {
max-height: 68px;
overflow: hidden;
border-radius: 4px;
padding: 0;
}

.tl-body.start .content .text {
box-sizing: border-box;
white-space: pre-wrap;
width: 100%;
float: right;
line-height: 1.5;
margin: 0;
margin-left: -100px;
-webkit-mask: linear-gradient(red 30px, transparent 70px);
}

.tl-body.start .content::before {
content: "";
width: 100px;
height: 100%;
float: left;
}

.tl-body.start .content .btn {
float: right;
width: 100px;
text-align: center;
position: relative;
left: calc(50% - 50px);
transform: translateY(-100%);
cursor: pointer;
}

.tl-body.start .content .btn::after {
content: "";
position: relative;
top: -2px;
display: block;
height: 16px;
background-color: #fff;
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E %3Cpath d='M143 352.3L7 216.3c-9.4-9.4-9.4-24.6 0-33.9l22.6-22.6c9.4-9.4 24.6-9.4 33.9 0l96.4 96.4 96.4-96.4c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9l-136 136c-9.2 9.4-24.4 9.4-33.8 0z'%3E%3C/path%3E %3C/svg%3E")
center/ 24px 24px no-repeat;
}

.tl-body.start .content .btn::before {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 16px;
}

.tl-body.start .content-check:checked + .content {
max-height: fit-content;
padding-bottom: 16px;
}

.tl-body.start .content-check:checked + .content .btn {
left: auto;
right: calc(50% - 50px);
}

.tl-body.start .content-check:checked + .content .btn::after {
transform: scaleY(-1) translateY(-18px);
}

.tl-body.start .content-check:checked + .content .text {
-webkit-mask: none;
}

main.styl

更新/themes/arch/source/css下的main.styl并添加下面的内容。

1
2
3
4
5
6
7
8
9
10
11
.time-line .tl-header, .time-line .title {
background-color: $theme-color !important;
}

.time-line .tl-body, .time-line li:before {
border-color: $theme-color !important;
}

.time-line .title:before {
border-right-color: $theme-color !important;
}
image-1.png

添加翻译

更新 /themes/arch/languages 下的 en.yml 和 zh-CN.yml 文件,并按如下方式设置内容。

/themes/arch/languages/en.yml 的内容。

1
2
3
Timeline: Timeline
viewByYear: View by Year
viewByMonth: View by Month

/themes/arch/languages/zh-CN.yml 的内容。

1
2
3
Timeline: 时间轴
viewByYear: 按年查看
viewByMonth: 按月查看

添加导航栏

更新 _config.arch.yml 中的navbar并添加如下内容。

1
2
3
4
5
6
navbar:
-
name: Timeline
enable: true
path: /timeline/
key: timeline
image-2.png

截图

screenshot.gif