Skip to content

Commit 092d7b9

Browse files
committed
feat: render taxonomy pages without shared frame
- Preserve taxonomy content, comments, and pagination on detail routes - Assign tag blur tiers by relative post counts - Reuse page titles with taxonomy icons and adjust sidebar spacing
1 parent bab660c commit 092d7b9

10 files changed

Lines changed: 97 additions & 29 deletions

File tree

docs/content/docs/en/page_templates/tags.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ title: Tags
66

77
`blur` style:
88

9+
The blur intensity is distributed according to relative tag popularity. Tags
10+
with the same number of posts use the same intensity, while the most-used tags
11+
remain clear.
12+
913
![Screenshot 2023-03-24 at 4.36.53 PM](https://assets.ohevan.com/img/1213873f86e23a1c55bfe25f069242fc.png)
1014

1115
`cloud` style:

docs/content/docs/zh/page_templates/tags.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ title: 标签
66

77
`blur` 样式:
88

9+
模糊程度根据标签的相对使用频率动态分配。文章数量相同的标签使用相同的模糊程度,使用最多的标签保持清晰。
10+
911
![Screenshot 2023-03-24 at 4.36.53 PM](https://assets.ohevan.com/img/1213873f86e23a1c55bfe25f069242fc.png)
1012

1113
`cloud` 样式:

layout/components/page-title.ejs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
<% const bordered = typeof showBorder === 'boolean' && showBorder; %>
2-
<h1 class="<%= bordered ? 'text-4xl mt-4 mb-8 font-medium font-display tracking-tight border-b border-rd-gray-alpha-400 pb-2' : 'mt-1.5 mb-9 text-5xl leading-none font-bold text-rd-gray-1000' %>"><%- title %></h1>
2+
<% const titleIconClass = typeof iconClass === 'string' ? iconClass : ''; %>
3+
<h1 class="<%= bordered ? 'text-4xl mt-4 mb-8 font-medium font-display tracking-tight border-b border-rd-gray-alpha-400 pb-2' : 'mt-1.5 mb-9 text-5xl leading-none font-medium text-rd-gray-1000' %>"><% if (titleIconClass) { %><i class="<%= titleIconClass %>" aria-hidden="true"></i>&nbsp;<% } %><%- title %></h1>

layout/pages/category.ejs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
<h1 class="mb-10 border-b border-rd-gray-alpha-400 pb-5 text-4xl font-semibold text-rd-gray-1000 sm:text-5xl [&_i]:!text-inherit">
2-
<i class="fa-solid fa-folder" aria-hidden="true"></i>&nbsp;<%= page.category %>
3-
</h1>
4-
<%- partial('components/posts-list', {posts: page.posts}) %>
1+
<div class="mb-4 pt-5 sm:mb-6 sm:pt-0 md:mb-8">
2+
<%- partial('components/page-title', {
3+
title: escape_html(page.category),
4+
showBorder: true,
5+
iconClass: 'fa-regular fa-folder text-rd-gray-800'
6+
}) %>
7+
<%- partial('components/posts-list', {posts: page.posts}) %>
8+
9+
<% if (page.content) { %>
10+
<div class="markdown-body text-rd-gray-1000"><%- page.content %></div>
11+
<% } %>
12+
13+
<% if (page.comment === true || page.comments === true) { %>
14+
<%- partial('components/comments/index') %>
15+
<% } %>
16+
</div>
17+
18+
<%- partial('components/paginator') %>

layout/pages/home/sidebar.ejs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
const hasSidebarLinks = theme.home.sidebar.links !== null;
33
const hasSidebarAnnouncement = theme.home.sidebar.announcement !== null;
44
const hasSidebarExtra = hasSidebarLinks || hasSidebarAnnouncement;
5-
const sidebarItems = theme.home.sidebar.first_item === "info"
6-
? ["profile", ...(hasSidebarExtra ? ["links"] : [])]
7-
: [...(hasSidebarExtra ? ["links"] : []), "profile"];
5+
const sidebarItems = ["profile", ...(hasSidebarExtra ? ["extra"] : [])];
6+
7+
if (theme.home.sidebar.first_item !== "info") sidebarItems.reverse();
88
%>
99
<aside class="mx-10 hidden h-auto w-60 md:block">
10-
<div class="sticky top-[calc(var(--current-navbar-height)_+_1rem)]">
11-
<% sidebarItems.forEach((item, index) => { %>
12-
<% const separated = index > 0; %>
10+
<div class="sticky top-[calc(var(--current-navbar-height)_+_1rem)] flex flex-col gap-6">
11+
<% for (const item of sidebarItems) { %>
1312
<% if (item === "profile") { %>
14-
<div data-sidebar-panel class="box-border rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 p-5 <%= separated ? 'mt-6' : '' %>">
13+
<div data-sidebar-panel class="box-border rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 p-4">
1514
<div class="flex justify-center">
1615
<%- image_tag(theme.defaults.avatar, {class: "h-20 w-20 rounded-2xl border border-rd-gray-alpha-400 p-px"}) %>
1716
</div>
@@ -24,11 +23,11 @@
2423
<%- partial('components/statistics') %>
2524
</div>
2625
<% } else { %>
27-
<div data-sidebar-panel class="box-border overflow-hidden rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 p-4 <%= separated ? 'mt-6' : '' %>">
28-
<div data-sidebar-header class="-mx-4 -mt-4 mb-2.5 border-b border-rd-gray-alpha-400 bg-rd-gray-100 p-4">
26+
<div data-sidebar-panel class="box-border overflow-hidden rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100">
27+
<div data-sidebar-header class="border-b border-rd-gray-alpha-400 bg-rd-gray-100 p-4">
2928
<div class="text-center font-title text-xl font-medium text-rd-gray-1000"><%= theme.info.title || config.title %></div>
3029
<% if (hasSidebarAnnouncement && hasSidebarLinks) { %>
31-
<div class="mt-2.5 mb-1 text-center text-sm text-rd-gray-900"><%- theme.home.sidebar.announcement %></div>
30+
<div class="mb-1 text-center text-sm text-rd-gray-900"><%- theme.home.sidebar.announcement %></div>
3231
<% } %>
3332
</div>
3433
<% if (hasSidebarLinks) { %>
@@ -46,6 +45,6 @@
4645
<% } %>
4746
</div>
4847
<% } %>
49-
<% }); %>
48+
<% } %>
5049
</div>
5150
</aside>

layout/pages/router.ejs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
<%- partial('pages/post/index') %>
1010
<% } else { %>
1111
<div id="main-content" class="relative box-border h-full w-9/10 max-w-content transition-[max-width,width] duration-200 ease-in-out sm:w-6/7 md:w-4/5">
12-
<% if (pageKind === 'archive') { %>
13-
<%- partial('pages/archive') %>
14-
<% } else if (pageKind === 'category') { %>
15-
<%- partial('components/page-panel', {pageBody: partial('pages/category')}) %>
16-
<% } else if (pageKind === 'tag') { %>
17-
<%- partial('components/page-panel', {pageBody: partial('pages/tag')}) %>
12+
<% if (pageKind === 'archive') { %>
13+
<%- partial('pages/archive') %>
14+
<% } else if (pageKind === 'category') { %>
15+
<%- partial('pages/category') %>
16+
<% } else if (pageKind === 'tag') { %>
17+
<%- partial('pages/tag') %>
1818
<% } else if (pageKind === 'categories') { %>
1919
<%- partial('pages/categories') %>
2020
<% } else if (pageKind === 'tags') { %>

layout/pages/tag.ejs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
<h1 class="mb-10 border-b border-rd-gray-alpha-400 pb-5 text-4xl font-semibold text-rd-gray-1000 sm:text-5xl [&_i]:!text-inherit">
2-
<i class="fa-solid fa-tag" aria-hidden="true"></i>&nbsp;<%= page.tag %>
3-
</h1>
4-
<%- partial('components/posts-list', {posts: page.posts}) %>
1+
<div class="mb-4 pt-5 sm:mb-6 sm:pt-0 md:mb-8">
2+
<%- partial('components/page-title', {
3+
title: escape_html(page.tag),
4+
showBorder: true,
5+
iconClass: 'fa-regular fa-hashtag text-rd-gray-800'
6+
}) %>
7+
<%- partial('components/posts-list', {posts: page.posts}) %>
8+
9+
<% if (page.content) { %>
10+
<div class="markdown-body text-rd-gray-1000"><%- page.content %></div>
11+
<% } %>
12+
13+
<% if (page.comment === true || page.comments === true) { %>
14+
<%- partial('components/comments/index') %>
15+
<% } %>
16+
</div>
17+
18+
<%- partial('components/paginator') %>

layout/pages/tags.ejs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@
55
<% if (theme.page_templates.tags_style === 'cloud') { %>
66
<div class="tags"><%- tagcloud({min_font: 1, max_font: 5, unit: 'rem', amount: 100}) %></div>
77
<% } else { %>
8+
<%
9+
const blurClasses = ['blur-sm', 'blur-xs', 'opacity-70', 'blur-none'];
10+
const tagCounts = [...new Set(site.tags.map((tag) => tag.posts.length))].sort((a, b) => a - b);
11+
const blurClassByCount = new Map(tagCounts.map((count, index) => {
12+
const tier = tagCounts.length === 1
13+
? blurClasses.length - 1
14+
: Math.round(index * (blurClasses.length - 1) / (tagCounts.length - 1));
15+
return [count, blurClasses[tier]];
16+
}));
17+
%>
818
<ul class="group/tags flex list-none flex-wrap items-center justify-center p-0 leading-10">
919
<% site.tags.forEach((tag) => { %>
1020
<%
1121
const count = tag.posts.length;
12-
const blurClass = count === 1 ? 'blur-sm' : count === 2 ? 'blur-xs' : count <= 4 ? 'opacity-70' : 'blur-none';
22+
const blurClass = blurClassByCount.get(count);
1323
%>
1424
<li>
1525
<a class="relative m-2.5 block flex-1 rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 px-6 py-3.5 text-2xl text-rd-gray-1000 transition-[filter,color,background-color,transform] duration-300 group-hover/tags:blur-none group-hover/tags:opacity-100 hover:bg-primary hover:!text-rd-primary-text active:translate-y-0.5 [&_i]:mr-1 [&_i]:opacity-30 [&_i]:!text-inherit <%= blurClass %>" href="<%- url_for('/' + tag.path) %>">

openspec/specs/page-rendering/spec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The common page shell SHALL render route content through one EJS router whose br
4848

4949
#### Scenario: Raw route renders
5050

51-
- **WHEN** home, post, archive, categories, tags, masonry, bookmarks, or not-found content requires route-specific composition
51+
- **WHEN** home, post, archive, category, tag, categories, tags, masonry, bookmarks, or not-found content requires route-specific composition
5252
- **THEN** the router renders its literal route partial without an unnecessary page-panel wrapper
5353

5454
### Requirement: EJS ownership follows rendered responsibility
@@ -79,7 +79,7 @@ Required Hexo layout entries SHALL remain at the layout root. Reusable rendered
7979

8080
The explicit render flow SHALL preserve page content, page titles, optional comments, pagination, Swup boundaries, behavior IDs and data attributes, conditional assets, and route-scoped collections. Archive and taxonomy routes MUST render the collection supplied for the current route rather than silently replacing it with the complete site collection.
8181

82-
The categories and tags indexes MUST render without the shared page frame or a comments region.
82+
Category and tag detail routes and their indexes MUST render without the shared page frame. The detail routes MUST preserve optional comments and pagination, while the indexes MUST NOT render a comments region.
8383

8484
#### Scenario: Paginated or filtered archive renders
8585

tests/theme-generation.test.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,32 @@ test("theme build and generation configurations", async (t) => {
123123
"group/tags",
124124
borderedPageTitle,
125125
]);
126+
const tagTiers = new Map();
127+
const tagPattern = /class="[^"]* (blur-sm|blur-xs|opacity-70|blur-none)" href="\/tags\/[^"]+\/">[\s\S]*?<span[^>]*>(\d+)<\/span>/g;
128+
for (const [, blurClass, count] of tags.matchAll(tagPattern)) {
129+
const classes = tagTiers.get(Number(count)) || new Set();
130+
classes.add(blurClass);
131+
tagTiers.set(Number(count), classes);
132+
}
133+
const tagCounts = [...tagTiers.keys()].sort((a, b) => a - b);
134+
assert.ok(tagCounts.length > 1);
135+
tagTiers.forEach((classes) => assert.equal(classes.size, 1));
136+
assert.deepEqual([...tagTiers.get(tagCounts[0])], ["blur-sm"]);
137+
assert.deepEqual([...tagTiers.get(tagCounts.at(-1))], ["blur-none"]);
126138
assert.ok(!tags.includes('class="box-border mb-8 rounded-2xl border'));
127139
assert.ok(!tags.includes('id="comments"'));
140+
141+
const tag = readOutput("tags/test/index.html");
142+
includes(tag, [
143+
borderedPageTitle,
144+
'class="fa-regular fa-hashtag text-rd-gray-900"',
145+
'class="paginator',
146+
]);
147+
assert.ok(!tag.includes('class="box-border mb-4 rounded-none border-0'));
148+
149+
const category = readOutput("categories/Demo/index.html");
150+
includes(category, [borderedPageTitle, 'class="fa-solid fa-folder"', 'class="paginator']);
151+
assert.ok(!category.includes('class="box-border mb-4 rounded-none border-0'));
128152
});
129153

130154
await t.test("legacy and unknown templates use ordinary page rendering", () => {

0 commit comments

Comments
 (0)