jekyll-theme-zer0 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +130 -18
- data/_data/authors.yml +52 -0
- data/_data/navigation/posts.yml +13 -14
- data/_includes/components/author-card.html +177 -0
- data/_includes/components/js-cdn.html +27 -22
- data/_includes/components/post-card.html +176 -0
- data/_includes/core/branding.html +24 -12
- data/_includes/core/head.html +8 -8
- data/_includes/navigation/sidebar-folders.html +63 -103
- data/_layouts/blog.html +424 -232
- data/_layouts/category.html +247 -0
- data/_layouts/journals.html +272 -23
- data/_layouts/tag.html +111 -0
- metadata +11 -6
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
<!--
|
|
5
|
+
===================================================================
|
|
6
|
+
CATEGORY LAYOUT - Blog category archive page
|
|
7
|
+
===================================================================
|
|
8
|
+
|
|
9
|
+
File: category.html
|
|
10
|
+
Path: _layouts/category.html
|
|
11
|
+
Inherits: default.html (which inherits root.html)
|
|
12
|
+
Purpose: Display posts filtered by category with responsive card grid
|
|
13
|
+
|
|
14
|
+
Template Logic:
|
|
15
|
+
- Filters site.posts by page.category front matter variable
|
|
16
|
+
- Displays category header with description and post count
|
|
17
|
+
- Uses post-card.html component for consistent card rendering
|
|
18
|
+
- Implements responsive Bootstrap 5 grid layout
|
|
19
|
+
- Supports optional category icon and description
|
|
20
|
+
|
|
21
|
+
Front Matter Variables:
|
|
22
|
+
- page.category: Category name to filter posts (required)
|
|
23
|
+
- page.title: Display title for the page
|
|
24
|
+
- page.description: Category description text
|
|
25
|
+
- page.icon: Bootstrap Icon class for category (optional)
|
|
26
|
+
|
|
27
|
+
Dependencies:
|
|
28
|
+
- _includes/components/post-card.html: Reusable post card component
|
|
29
|
+
- Bootstrap 5 grid and card components
|
|
30
|
+
- Bootstrap Icons for category icons
|
|
31
|
+
===================================================================
|
|
32
|
+
-->
|
|
33
|
+
|
|
34
|
+
<!-- ========================== -->
|
|
35
|
+
<!-- CATEGORY HEADER SECTION -->
|
|
36
|
+
<!-- ========================== -->
|
|
37
|
+
<header class="category-header mb-5">
|
|
38
|
+
<div class="d-flex align-items-center mb-3">
|
|
39
|
+
{% if page.icon %}
|
|
40
|
+
<i class="bi bi-{{ page.icon }} fs-1 me-3 text-primary"></i>
|
|
41
|
+
{% endif %}
|
|
42
|
+
<div>
|
|
43
|
+
<h1 class="display-5 mb-0">{{ page.title }}</h1>
|
|
44
|
+
{% assign category_posts = site.posts | where_exp: "post", "post.categories contains page.category" %}
|
|
45
|
+
<p class="text-muted mb-0">
|
|
46
|
+
<i class="bi bi-collection me-1"></i>
|
|
47
|
+
{{ category_posts.size }} {{ category_posts.size | pluralize: "article", "articles" }}
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{% if page.description %}
|
|
53
|
+
<p class="lead text-muted">{{ page.description }}</p>
|
|
54
|
+
{% endif %}
|
|
55
|
+
|
|
56
|
+
<!-- Category description from page content -->
|
|
57
|
+
{% if content != blank %}
|
|
58
|
+
<div class="category-description mb-4">
|
|
59
|
+
{{ content }}
|
|
60
|
+
</div>
|
|
61
|
+
{% endif %}
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<!-- ========================== -->
|
|
65
|
+
<!-- CATEGORY FILTER BADGES -->
|
|
66
|
+
<!-- ========================== -->
|
|
67
|
+
<!-- Quick filter for subcategories or related tags -->
|
|
68
|
+
{% assign all_tags = "" | split: "" %}
|
|
69
|
+
{% for post in category_posts %}
|
|
70
|
+
{% for tag in post.tags %}
|
|
71
|
+
{% unless all_tags contains tag %}
|
|
72
|
+
{% assign all_tags = all_tags | push: tag %}
|
|
73
|
+
{% endunless %}
|
|
74
|
+
{% endfor %}
|
|
75
|
+
{% endfor %}
|
|
76
|
+
|
|
77
|
+
{% if all_tags.size > 0 %}
|
|
78
|
+
<div class="category-filters mb-4">
|
|
79
|
+
<span class="text-muted me-2">Filter by tag:</span>
|
|
80
|
+
{% for tag in all_tags limit: 8 %}
|
|
81
|
+
<a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}"
|
|
82
|
+
class="badge bg-secondary text-decoration-none me-1 mb-1">
|
|
83
|
+
{{ tag }}
|
|
84
|
+
</a>
|
|
85
|
+
{% endfor %}
|
|
86
|
+
{% if all_tags.size > 8 %}
|
|
87
|
+
<a href="{{ site.baseurl }}/tags/" class="badge bg-outline-secondary text-decoration-none">
|
|
88
|
+
+{{ all_tags.size | minus: 8 }} more
|
|
89
|
+
</a>
|
|
90
|
+
{% endif %}
|
|
91
|
+
</div>
|
|
92
|
+
{% endif %}
|
|
93
|
+
|
|
94
|
+
<!-- ========================== -->
|
|
95
|
+
<!-- FEATURED POSTS SECTION -->
|
|
96
|
+
<!-- ========================== -->
|
|
97
|
+
{% assign featured_in_category = category_posts | where: "featured", true %}
|
|
98
|
+
{% if featured_in_category.size > 0 %}
|
|
99
|
+
<section class="featured-posts mb-5">
|
|
100
|
+
<h2 class="h4 mb-3">
|
|
101
|
+
<i class="bi bi-star-fill text-warning me-2"></i>Featured in {{ page.category }}
|
|
102
|
+
</h2>
|
|
103
|
+
<div class="row row-cols-1 row-cols-md-2 g-4 mb-4">
|
|
104
|
+
{% for fpost in featured_in_category limit: 2 %}
|
|
105
|
+
<div class="col">
|
|
106
|
+
<div class="card h-100 border-0 shadow-sm">
|
|
107
|
+
<div class="position-relative">
|
|
108
|
+
{% if fpost.featured %}
|
|
109
|
+
<span class="badge bg-warning text-dark position-absolute top-0 end-0 m-2 z-1">
|
|
110
|
+
<i class="bi bi-star-fill me-1"></i>Featured
|
|
111
|
+
</span>
|
|
112
|
+
{% endif %}
|
|
113
|
+
<a href="{{ fpost.url | relative_url }}" class="text-decoration-none">
|
|
114
|
+
{% if fpost.preview %}
|
|
115
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ fpost.preview }}"
|
|
116
|
+
class="card-img-top" alt="{{ fpost.title }}" loading="lazy">
|
|
117
|
+
{% else %}
|
|
118
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
|
|
119
|
+
class="card-img-top" alt="Preview" loading="lazy">
|
|
120
|
+
{% endif %}
|
|
121
|
+
</a>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="card-body">
|
|
124
|
+
<h5 class="card-title">
|
|
125
|
+
<a href="{{ fpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
|
|
126
|
+
{{ fpost.title }}
|
|
127
|
+
</a>
|
|
128
|
+
</h5>
|
|
129
|
+
<p class="card-text text-muted small">{{ fpost.excerpt | strip_html | truncate: 120 }}</p>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="card-footer bg-transparent">
|
|
132
|
+
<small class="text-muted">
|
|
133
|
+
<i class="bi bi-calendar me-1"></i>{{ fpost.date | date: "%b %d, %Y" }}
|
|
134
|
+
</small>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
{% endfor %}
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
{% endif %}
|
|
142
|
+
|
|
143
|
+
<!-- ========================== -->
|
|
144
|
+
<!-- ALL POSTS GRID -->
|
|
145
|
+
<!-- ========================== -->
|
|
146
|
+
<section class="all-posts">
|
|
147
|
+
<h2 class="h4 mb-3 pb-2 border-bottom">
|
|
148
|
+
<i class="bi bi-newspaper me-2"></i>All {{ page.category }} Articles
|
|
149
|
+
</h2>
|
|
150
|
+
|
|
151
|
+
{% if category_posts.size > 0 %}
|
|
152
|
+
<!-- Responsive card grid: 1 column mobile, 2 tablet, 3 desktop -->
|
|
153
|
+
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
|
154
|
+
{% for cpost in category_posts %}
|
|
155
|
+
<div class="col">
|
|
156
|
+
<div class="card h-100 border-0 shadow-sm">
|
|
157
|
+
<div class="position-relative">
|
|
158
|
+
{% if cpost.breaking %}
|
|
159
|
+
<span class="badge bg-danger position-absolute top-0 start-0 m-2 z-1">
|
|
160
|
+
<i class="bi bi-lightning-fill me-1"></i>Breaking
|
|
161
|
+
</span>
|
|
162
|
+
{% endif %}
|
|
163
|
+
{% if cpost.featured %}
|
|
164
|
+
<span class="badge bg-warning text-dark position-absolute top-0 end-0 m-2 z-1">
|
|
165
|
+
<i class="bi bi-star-fill me-1"></i>Featured
|
|
166
|
+
</span>
|
|
167
|
+
{% endif %}
|
|
168
|
+
<a href="{{ cpost.url | relative_url }}" class="text-decoration-none">
|
|
169
|
+
{% if cpost.preview %}
|
|
170
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ cpost.preview }}"
|
|
171
|
+
class="card-img-top" alt="{{ cpost.title }}" loading="lazy">
|
|
172
|
+
{% else %}
|
|
173
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
|
|
174
|
+
class="card-img-top" alt="Preview" loading="lazy">
|
|
175
|
+
{% endif %}
|
|
176
|
+
</a>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="card-body d-flex flex-column">
|
|
179
|
+
<h5 class="card-title mb-2">
|
|
180
|
+
<a href="{{ cpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
|
|
181
|
+
{{ cpost.title | truncate: 60 }}
|
|
182
|
+
</a>
|
|
183
|
+
</h5>
|
|
184
|
+
<p class="card-text text-muted small flex-grow-1">
|
|
185
|
+
{{ cpost.excerpt | strip_html | truncate: 100 }}
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="card-footer bg-transparent border-top-0">
|
|
189
|
+
<div class="d-flex justify-content-between align-items-center small text-muted">
|
|
190
|
+
<span>
|
|
191
|
+
<i class="bi bi-calendar me-1"></i>{{ cpost.date | date: "%b %d, %Y" }}
|
|
192
|
+
</span>
|
|
193
|
+
{% if cpost.author %}
|
|
194
|
+
<span>
|
|
195
|
+
<i class="bi bi-person me-1"></i>{{ cpost.author | truncate: 15 }}
|
|
196
|
+
</span>
|
|
197
|
+
{% endif %}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
{% endfor %}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- ========================== -->
|
|
206
|
+
<!-- PAGINATION (if many posts) -->
|
|
207
|
+
<!-- ========================== -->
|
|
208
|
+
{% if category_posts.size > 12 %}
|
|
209
|
+
<nav aria-label="Category pagination" class="mt-5">
|
|
210
|
+
<p class="text-center text-muted">
|
|
211
|
+
Showing all {{ category_posts.size }} articles.
|
|
212
|
+
<a href="#" class="text-decoration-none">Back to top <i class="bi bi-arrow-up"></i></a>
|
|
213
|
+
</p>
|
|
214
|
+
</nav>
|
|
215
|
+
{% endif %}
|
|
216
|
+
|
|
217
|
+
{% else %}
|
|
218
|
+
<!-- Empty state -->
|
|
219
|
+
<div class="text-center py-5">
|
|
220
|
+
<i class="bi bi-inbox fs-1 text-muted"></i>
|
|
221
|
+
<p class="text-muted mt-3">No articles in this category yet.</p>
|
|
222
|
+
<a href="{{ site.baseurl }}/posts/" class="btn btn-outline-primary">
|
|
223
|
+
Browse all posts
|
|
224
|
+
</a>
|
|
225
|
+
</div>
|
|
226
|
+
{% endif %}
|
|
227
|
+
</section>
|
|
228
|
+
|
|
229
|
+
<!-- ========================== -->
|
|
230
|
+
<!-- RELATED CATEGORIES -->
|
|
231
|
+
<!-- ========================== -->
|
|
232
|
+
<aside class="related-categories mt-5 pt-4 border-top">
|
|
233
|
+
<h3 class="h5 mb-3">Explore Other Categories</h3>
|
|
234
|
+
<div class="d-flex flex-wrap gap-2">
|
|
235
|
+
{% for nav_item in site.data.navigation.posts %}
|
|
236
|
+
{% unless nav_item.title == page.category %}
|
|
237
|
+
<a href="{{ nav_item.url | relative_url }}"
|
|
238
|
+
class="btn btn-outline-secondary btn-sm">
|
|
239
|
+
{% if nav_item.icon %}
|
|
240
|
+
<i class="bi bi-{{ nav_item.icon }} me-1"></i>
|
|
241
|
+
{% endif %}
|
|
242
|
+
{{ nav_item.title }}
|
|
243
|
+
</a>
|
|
244
|
+
{% endunless %}
|
|
245
|
+
{% endfor %}
|
|
246
|
+
</div>
|
|
247
|
+
</aside>
|
data/_layouts/journals.html
CHANGED
|
@@ -2,32 +2,281 @@
|
|
|
2
2
|
layout: default
|
|
3
3
|
---
|
|
4
4
|
<!--
|
|
5
|
-
|
|
5
|
+
===================================================================
|
|
6
|
+
JOURNALS LAYOUT - Blog Post and Article Display
|
|
7
|
+
===================================================================
|
|
8
|
+
|
|
9
|
+
File: journals.html
|
|
10
|
+
Path: _layouts/journals.html
|
|
11
|
+
Inherits: default.html (which inherits root.html)
|
|
12
|
+
Purpose: Display blog posts with rich metadata, navigation, and comments
|
|
13
|
+
|
|
14
|
+
Template Logic:
|
|
15
|
+
- Displays article content with semantic HTML5 markup
|
|
16
|
+
- Shows post metadata (author, date, reading time, tags)
|
|
17
|
+
- Provides previous/next post navigation
|
|
18
|
+
- Integrates Giscus comment system (when enabled)
|
|
19
|
+
- Uses Schema.org BlogPosting markup for SEO
|
|
20
|
+
|
|
21
|
+
Dependencies:
|
|
22
|
+
- content/giscus.html: GitHub Discussions comment system
|
|
23
|
+
- site.giscus configuration for comment system
|
|
24
|
+
- Bootstrap 5 pagination components
|
|
25
|
+
===================================================================
|
|
6
26
|
-->
|
|
7
27
|
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
28
|
+
<article id="main" class="post h-entry" role="main" itemscope itemtype="https://schema.org/BlogPosting">
|
|
29
|
+
|
|
30
|
+
<!-- ================================ -->
|
|
31
|
+
<!-- ARTICLE HEADER -->
|
|
32
|
+
<!-- ================================ -->
|
|
33
|
+
<header class="post-header mb-4">
|
|
34
|
+
<h1 id="page-title" class="post-title p-name" itemprop="headline">
|
|
35
|
+
{{ page.title }}
|
|
36
|
+
</h1>
|
|
37
|
+
|
|
38
|
+
<!-- Post Metadata -->
|
|
39
|
+
<div class="post-meta text-muted mb-3">
|
|
40
|
+
<!-- Author -->
|
|
41
|
+
{% if page.author %}
|
|
42
|
+
<span class="post-author" itemprop="author" itemscope itemtype="https://schema.org/Person">
|
|
43
|
+
<i class="bi bi-person me-1"></i>
|
|
44
|
+
<span itemprop="name">{{ page.author }}</span>
|
|
45
|
+
</span>
|
|
46
|
+
<span class="mx-2">•</span>
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
<!-- Publication Date -->
|
|
50
|
+
{% if page.date %}
|
|
51
|
+
<time class="post-date dt-published" datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
|
|
52
|
+
<i class="bi bi-calendar me-1"></i>
|
|
53
|
+
{{ page.date | date: "%B %d, %Y" }}
|
|
54
|
+
</time>
|
|
55
|
+
<span class="mx-2">•</span>
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
<!-- Reading Time -->
|
|
59
|
+
{% assign words = content | number_of_words %}
|
|
60
|
+
{% assign minutes = words | divided_by: 200 %}
|
|
61
|
+
{% if minutes < 1 %}{% assign minutes = 1 %}{% endif %}
|
|
62
|
+
<span class="reading-time">
|
|
63
|
+
<i class="bi bi-clock me-1"></i>
|
|
64
|
+
{{ minutes }} min read
|
|
65
|
+
</span>
|
|
66
|
+
|
|
67
|
+
<!-- Last Modified -->
|
|
68
|
+
{% if page.lastmod %}
|
|
69
|
+
<span class="mx-2">•</span>
|
|
70
|
+
<span class="post-updated" itemprop="dateModified" content="{{ page.lastmod | date_to_xmlschema }}">
|
|
71
|
+
<i class="bi bi-pencil me-1"></i>
|
|
72
|
+
Updated: {{ page.lastmod | date: "%B %d, %Y" }}
|
|
73
|
+
</span>
|
|
74
|
+
{% endif %}
|
|
12
75
|
</div>
|
|
13
76
|
|
|
14
|
-
<!--
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
77
|
+
<!-- Tags -->
|
|
78
|
+
{% if page.tags and page.tags.size > 0 %}
|
|
79
|
+
<div class="post-tags mb-3">
|
|
80
|
+
{% for tag in page.tags %}
|
|
81
|
+
<a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}" class="badge bg-secondary text-decoration-none me-1">
|
|
82
|
+
<i class="bi bi-tag me-1"></i>{{ tag }}
|
|
83
|
+
</a>
|
|
84
|
+
{% endfor %}
|
|
85
|
+
</div>
|
|
86
|
+
{% endif %}
|
|
87
|
+
</header>
|
|
88
|
+
|
|
89
|
+
<!-- ================================ -->
|
|
90
|
+
<!-- ARTICLE CONTENT -->
|
|
91
|
+
<!-- ================================ -->
|
|
92
|
+
<div class="post-content e-content" itemprop="articleBody">
|
|
93
|
+
{{ content }}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- ================================ -->
|
|
97
|
+
<!-- AUTHOR BIO SECTION -->
|
|
98
|
+
<!-- ================================ -->
|
|
99
|
+
{% if page.author %}
|
|
100
|
+
{% assign author_key = page.author %}
|
|
101
|
+
{% assign author_data = site.data.authors[author_key] %}
|
|
102
|
+
{% unless author_data %}
|
|
103
|
+
{% assign author_data = site.data.authors.default %}
|
|
104
|
+
{% assign author_name = page.author %}
|
|
105
|
+
{% else %}
|
|
106
|
+
{% assign author_name = author_data.name %}
|
|
107
|
+
{% endunless %}
|
|
108
|
+
|
|
109
|
+
<aside class="author-section my-5 pt-4 border-top">
|
|
110
|
+
<h3 class="h5 mb-3">
|
|
111
|
+
<i class="bi bi-person-badge me-2"></i>About the Author
|
|
112
|
+
</h3>
|
|
113
|
+
<div class="author-card card border-0 shadow-sm">
|
|
114
|
+
<div class="card-body">
|
|
115
|
+
<div class="d-flex align-items-start">
|
|
116
|
+
{% if author_data.avatar %}
|
|
117
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}{{ author_data.avatar }}"
|
|
118
|
+
alt="{{ author_name }}"
|
|
119
|
+
class="rounded-circle me-3"
|
|
120
|
+
width="80" height="80"
|
|
121
|
+
style="object-fit: cover;">
|
|
122
|
+
{% else %}
|
|
123
|
+
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-3"
|
|
124
|
+
style="width: 80px; height: 80px;">
|
|
125
|
+
<i class="bi bi-person fs-1"></i>
|
|
126
|
+
</div>
|
|
127
|
+
{% endif %}
|
|
128
|
+
<div class="flex-grow-1">
|
|
129
|
+
<h5 class="card-title mb-1">{{ author_name }}</h5>
|
|
130
|
+
{% if author_data.role %}
|
|
131
|
+
<p class="text-primary mb-2">{{ author_data.role }}</p>
|
|
132
|
+
{% endif %}
|
|
133
|
+
{% if author_data.bio %}
|
|
134
|
+
<p class="card-text text-muted">{{ author_data.bio }}</p>
|
|
135
|
+
{% endif %}
|
|
136
|
+
<div class="author-social d-flex gap-2 mt-2">
|
|
137
|
+
{% if author_data.github %}
|
|
138
|
+
<a href="https://github.com/{{ author_data.github }}"
|
|
139
|
+
class="btn btn-sm btn-outline-secondary"
|
|
140
|
+
target="_blank" rel="noopener" title="GitHub">
|
|
141
|
+
<i class="bi bi-github"></i>
|
|
142
|
+
</a>
|
|
143
|
+
{% endif %}
|
|
144
|
+
{% if author_data.twitter %}
|
|
145
|
+
<a href="https://twitter.com/{{ author_data.twitter }}"
|
|
146
|
+
class="btn btn-sm btn-outline-secondary"
|
|
147
|
+
target="_blank" rel="noopener" title="Twitter">
|
|
148
|
+
<i class="bi bi-twitter"></i>
|
|
149
|
+
</a>
|
|
22
150
|
{% endif %}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
151
|
+
{% if author_data.website %}
|
|
152
|
+
<a href="{{ author_data.website }}"
|
|
153
|
+
class="btn btn-sm btn-outline-secondary"
|
|
154
|
+
target="_blank" rel="noopener" title="Website">
|
|
155
|
+
<i class="bi bi-globe"></i>
|
|
156
|
+
</a>
|
|
29
157
|
{% endif %}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</aside>
|
|
164
|
+
{% endif %}
|
|
165
|
+
|
|
166
|
+
<!-- ================================ -->
|
|
167
|
+
<!-- RELATED POSTS SECTION -->
|
|
168
|
+
<!-- ================================ -->
|
|
169
|
+
{% if page.tags.size > 0 %}
|
|
170
|
+
{% assign related_posts = "" | split: "" %}
|
|
171
|
+
{% for tag in page.tags %}
|
|
172
|
+
{% assign tagged = site.posts | where_exp: "post", "post.tags contains tag" | where_exp: "post", "post.url != page.url" %}
|
|
173
|
+
{% for post in tagged %}
|
|
174
|
+
{% unless related_posts contains post %}
|
|
175
|
+
{% assign related_posts = related_posts | push: post %}
|
|
176
|
+
{% endunless %}
|
|
177
|
+
{% endfor %}
|
|
178
|
+
{% endfor %}
|
|
179
|
+
{% assign related_posts = related_posts | slice: 0, 3 %}
|
|
180
|
+
|
|
181
|
+
{% if related_posts.size > 0 %}
|
|
182
|
+
<aside class="related-posts my-5 pt-4 border-top">
|
|
183
|
+
<h3 class="h5 mb-3">
|
|
184
|
+
<i class="bi bi-journal-text me-2"></i>Related Posts
|
|
185
|
+
</h3>
|
|
186
|
+
<div class="row row-cols-1 row-cols-md-3 g-3">
|
|
187
|
+
{% for rpost in related_posts %}
|
|
188
|
+
<div class="col">
|
|
189
|
+
<div class="card h-100 border-0 shadow-sm">
|
|
190
|
+
<a href="{{ rpost.url | relative_url }}" class="text-decoration-none">
|
|
191
|
+
{% if rpost.preview %}
|
|
192
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ rpost.preview }}"
|
|
193
|
+
class="card-img-top" alt="{{ rpost.title }}" loading="lazy">
|
|
194
|
+
{% else %}
|
|
195
|
+
<img src="{{ site.baseurl }}/{{ site.public_folder }}/{{ site.teaser }}"
|
|
196
|
+
class="card-img-top" alt="Preview" loading="lazy">
|
|
197
|
+
{% endif %}
|
|
198
|
+
</a>
|
|
199
|
+
<div class="card-body">
|
|
200
|
+
<h6 class="card-title mb-1">
|
|
201
|
+
<a href="{{ rpost.url | relative_url }}" class="text-decoration-none text-body-emphasis">
|
|
202
|
+
{{ rpost.title | truncate: 60 }}
|
|
203
|
+
</a>
|
|
204
|
+
</h6>
|
|
205
|
+
<small class="text-muted">
|
|
206
|
+
<i class="bi bi-calendar me-1"></i>{{ rpost.date | date: "%b %d, %Y" }}
|
|
207
|
+
</small>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
{% endfor %}
|
|
212
|
+
</div>
|
|
213
|
+
</aside>
|
|
214
|
+
{% endif %}
|
|
215
|
+
{% endif %}
|
|
216
|
+
|
|
217
|
+
<!-- ================================ -->
|
|
218
|
+
<!-- POST NAVIGATION -->
|
|
219
|
+
<!-- ================================ -->
|
|
220
|
+
<nav class="post-navigation my-5 pt-4 border-top" aria-label="Post navigation">
|
|
221
|
+
<div class="row g-3">
|
|
222
|
+
<!-- Previous Post -->
|
|
223
|
+
<div class="col-md-6">
|
|
224
|
+
{% if page.previous %}
|
|
225
|
+
<a href="{{ page.previous.url | relative_url }}" class="card h-100 text-decoration-none border-0 shadow-sm">
|
|
226
|
+
<div class="card-body">
|
|
227
|
+
<small class="text-muted d-block mb-1">
|
|
228
|
+
<i class="bi bi-chevron-left me-1"></i>Previous
|
|
229
|
+
</small>
|
|
230
|
+
<span class="text-body-emphasis">{{ page.previous.title | truncate: 50 }}</span>
|
|
231
|
+
</div>
|
|
232
|
+
</a>
|
|
233
|
+
{% else %}
|
|
234
|
+
<div class="card h-100 border-0 bg-body-tertiary opacity-50">
|
|
235
|
+
<div class="card-body">
|
|
236
|
+
<small class="text-muted d-block mb-1">
|
|
237
|
+
<i class="bi bi-chevron-left me-1"></i>Previous
|
|
238
|
+
</small>
|
|
239
|
+
<span class="text-muted">No previous post</span>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
{% endif %}
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- Next Post -->
|
|
246
|
+
<div class="col-md-6">
|
|
247
|
+
{% if page.next %}
|
|
248
|
+
<a href="{{ page.next.url | relative_url }}" class="card h-100 text-decoration-none border-0 shadow-sm">
|
|
249
|
+
<div class="card-body text-end">
|
|
250
|
+
<small class="text-muted d-block mb-1">
|
|
251
|
+
Next<i class="bi bi-chevron-right ms-1"></i>
|
|
252
|
+
</small>
|
|
253
|
+
<span class="text-body-emphasis">{{ page.next.title | truncate: 50 }}</span>
|
|
254
|
+
</div>
|
|
255
|
+
</a>
|
|
256
|
+
{% else %}
|
|
257
|
+
<div class="card h-100 border-0 bg-body-tertiary opacity-50">
|
|
258
|
+
<div class="card-body text-end">
|
|
259
|
+
<small class="text-muted d-block mb-1">
|
|
260
|
+
Next<i class="bi bi-chevron-right ms-1"></i>
|
|
261
|
+
</small>
|
|
262
|
+
<span class="text-muted">No next post</span>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
{% endif %}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</nav>
|
|
269
|
+
|
|
270
|
+
<!-- ================================ -->
|
|
271
|
+
<!-- COMMENT SYSTEM -->
|
|
272
|
+
<!-- ================================ -->
|
|
273
|
+
{% if page.comments != false and site.giscus %}
|
|
274
|
+
<section class="post-comments mt-5 pt-4 border-top" id="comments">
|
|
275
|
+
<h2 class="h4 mb-4">
|
|
276
|
+
<i class="bi bi-chat-dots me-2"></i>Comments
|
|
277
|
+
</h2>
|
|
278
|
+
{% include content/giscus.html %}
|
|
279
|
+
</section>
|
|
280
|
+
{% endif %}
|
|
281
|
+
|
|
282
|
+
</article>
|
data/_layouts/tag.html
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
---
|
|
4
|
+
<!--
|
|
5
|
+
===================================================================
|
|
6
|
+
TAG LAYOUT - Tag archive page displaying posts by tag
|
|
7
|
+
===================================================================
|
|
8
|
+
|
|
9
|
+
File: tag.html
|
|
10
|
+
Path: _layouts/tag.html
|
|
11
|
+
Inherits: default.html (which inherits root.html)
|
|
12
|
+
Purpose: Display posts filtered by a specific tag
|
|
13
|
+
|
|
14
|
+
Template Logic:
|
|
15
|
+
- Filters site.posts by page.tag front matter variable
|
|
16
|
+
- Displays tag header with post count
|
|
17
|
+
- Uses post-card.html component for consistent rendering
|
|
18
|
+
- Shows related tags for discovery
|
|
19
|
+
|
|
20
|
+
Front Matter Variables:
|
|
21
|
+
- page.tag: Tag name to filter posts (required)
|
|
22
|
+
- page.title: Display title for the page
|
|
23
|
+
- page.description: Tag description text (optional)
|
|
24
|
+
|
|
25
|
+
Dependencies:
|
|
26
|
+
- _includes/components/post-card.html: Reusable post card component
|
|
27
|
+
- Bootstrap 5 grid and badge components
|
|
28
|
+
===================================================================
|
|
29
|
+
-->
|
|
30
|
+
|
|
31
|
+
<!-- ========================== -->
|
|
32
|
+
<!-- TAG HEADER SECTION -->
|
|
33
|
+
<!-- ========================== -->
|
|
34
|
+
<header class="tag-header mb-5">
|
|
35
|
+
<nav aria-label="breadcrumb" class="mb-3">
|
|
36
|
+
<ol class="breadcrumb">
|
|
37
|
+
<li class="breadcrumb-item"><a href="{{ site.baseurl }}/">Home</a></li>
|
|
38
|
+
<li class="breadcrumb-item"><a href="{{ site.baseurl }}/tags/">Tags</a></li>
|
|
39
|
+
<li class="breadcrumb-item active" aria-current="page">{{ page.tag }}</li>
|
|
40
|
+
</ol>
|
|
41
|
+
</nav>
|
|
42
|
+
|
|
43
|
+
<div class="d-flex align-items-center mb-3">
|
|
44
|
+
<i class="bi bi-tag-fill fs-1 me-3 text-primary"></i>
|
|
45
|
+
<div>
|
|
46
|
+
<h1 class="display-5 mb-0">{{ page.title | default: page.tag }}</h1>
|
|
47
|
+
{% assign tag_posts = site.posts | where_exp: "post", "post.tags contains page.tag" %}
|
|
48
|
+
<p class="text-muted mb-0">
|
|
49
|
+
<i class="bi bi-file-earmark-text me-1"></i>
|
|
50
|
+
{{ tag_posts.size }} {{ tag_posts.size | pluralize: "post", "posts" }} tagged
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{% if page.description %}
|
|
56
|
+
<p class="lead text-muted">{{ page.description }}</p>
|
|
57
|
+
{% endif %}
|
|
58
|
+
|
|
59
|
+
{{ content }}
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<!-- ========================== -->
|
|
63
|
+
<!-- TAGGED POSTS GRID -->
|
|
64
|
+
<!-- ========================== -->
|
|
65
|
+
<section class="tagged-posts">
|
|
66
|
+
{% if tag_posts.size > 0 %}
|
|
67
|
+
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
|
68
|
+
{% for post in tag_posts %}
|
|
69
|
+
{% include components/post-card.html post=post %}
|
|
70
|
+
{% endfor %}
|
|
71
|
+
</div>
|
|
72
|
+
{% else %}
|
|
73
|
+
<!-- Empty state -->
|
|
74
|
+
<div class="text-center py-5">
|
|
75
|
+
<i class="bi bi-tag fs-1 text-muted"></i>
|
|
76
|
+
<p class="text-muted mt-3">No posts with this tag yet.</p>
|
|
77
|
+
<a href="{{ site.baseurl }}/tags/" class="btn btn-outline-primary">
|
|
78
|
+
Browse all tags
|
|
79
|
+
</a>
|
|
80
|
+
</div>
|
|
81
|
+
{% endif %}
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<!-- ========================== -->
|
|
85
|
+
<!-- RELATED TAGS -->
|
|
86
|
+
<!-- ========================== -->
|
|
87
|
+
{% comment %} Find tags that appear with this tag {% endcomment %}
|
|
88
|
+
{% assign related_tags = "" | split: "" %}
|
|
89
|
+
{% for post in tag_posts %}
|
|
90
|
+
{% for tag in post.tags %}
|
|
91
|
+
{% unless tag == page.tag or related_tags contains tag %}
|
|
92
|
+
{% assign related_tags = related_tags | push: tag %}
|
|
93
|
+
{% endunless %}
|
|
94
|
+
{% endfor %}
|
|
95
|
+
{% endfor %}
|
|
96
|
+
|
|
97
|
+
{% if related_tags.size > 0 %}
|
|
98
|
+
<aside class="related-tags mt-5 pt-4 border-top">
|
|
99
|
+
<h3 class="h5 mb-3">
|
|
100
|
+
<i class="bi bi-tags me-2"></i>Related Tags
|
|
101
|
+
</h3>
|
|
102
|
+
<div class="d-flex flex-wrap gap-2">
|
|
103
|
+
{% for tag in related_tags limit: 12 %}
|
|
104
|
+
<a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}"
|
|
105
|
+
class="badge bg-secondary text-decoration-none fs-6">
|
|
106
|
+
{{ tag }}
|
|
107
|
+
</a>
|
|
108
|
+
{% endfor %}
|
|
109
|
+
</div>
|
|
110
|
+
</aside>
|
|
111
|
+
{% endif %}
|