Skip to content

Commit cd403bd

Browse files
committed
feat: improve category hierarchy display
- Render complete category paths in post and home metadata - Apply home category limits per path and support zero for all paths - Expand mode-aware gray scales for borders and high-contrast surfaces
1 parent 4c760db commit cd403bd

14 files changed

Lines changed: 206 additions & 54 deletions

File tree

docs/content/docs/en/developer/index.mdx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,21 @@ appropriate entry.
153153
page backgrounds.
154154
- `--rd-gray-100`, `--rd-gray-200`, and `--rd-gray-300` are for default, hover,
155155
and active component backgrounds, respectively.
156-
- `--rd-gray-alpha-400` is for structural borders and dividers.
156+
- `--rd-gray-400`, `--rd-gray-500`, and `--rd-gray-600` are for default, hover,
157+
and active component borders, respectively.
158+
- `--rd-gray-700` and `--rd-gray-800` are for default and hover high-contrast
159+
component backgrounds.
157160
- `--rd-gray-900` is for secondary text and icons; `--rd-gray-1000` is for
158161
primary text and icons.
162+
- `--rd-gray-alpha-100` through `--rd-gray-alpha-1000` mirror the solid gray
163+
roles when transparency is required; `--rd-gray-alpha-400` remains the
164+
standard structural border and divider.
159165
- `--rd-primary-text` is for text and icons on primary-colored fills.
160166
- `--rd-shadow` remains the complete mode-aware shadow treatment.
161167

162-
`--rd-gray-500` through `--rd-gray-800` are intentionally undefined and must
163-
not be invented without a demonstrated component role. Do not add component
164-
aliases such as `card` or `foreground`; components should select the numbered
165-
variable that matches their role directly. Tailwind mappings backed by
166-
color-mode or configuration-generated runtime variables belong in
167-
`@theme inline`.
168+
Do not add component aliases such as `card` or `foreground`; components should
169+
select the numbered variable that matches their role directly. Tailwind
170+
mappings backed by color-mode or configuration-generated runtime variables
171+
belong in `@theme inline`.
168172

169173
For more information, see the [official Tailwind CSS documentation](https://tailwindcss.com/docs).

docs/content/docs/en/home/home.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,11 @@ home:
165165

166166
| Type | Optional values | Default value |
167167
| :----: | :-------------: | :-----------: |
168-
| Number | 1, 2, 3, ... | 3 |
168+
| Number | 0, 1, 2, ... | 3 |
169+
170+
Categories are displayed as complete hierarchy paths, such as
171+
`Parent › Child`. The limit counts paths rather than individual hierarchy
172+
segments; use `0` to display every path.
169173

170174
## Tags
171175

docs/content/docs/zh/developer/index.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ pnpm release:notes:check
104104

105105
- `--rd-background-100``--rd-background-200` 分别用于主要和交替页面背景。
106106
- `--rd-gray-100``--rd-gray-200``--rd-gray-300` 分别用于组件的默认、悬停和激活背景。
107-
- `--rd-gray-alpha-400` 用于结构边框和分隔线。
107+
- `--rd-gray-400``--rd-gray-500``--rd-gray-600` 分别用于组件的默认、悬停和激活边框。
108+
- `--rd-gray-700``--rd-gray-800` 分别用于高对比度组件的默认和悬停背景。
108109
- `--rd-gray-900` 用于次要文字和图标,`--rd-gray-1000` 用于主要文字和图标。
110+
- `--rd-gray-alpha-100``--rd-gray-alpha-1000` 在需要透明度时与同编号的纯色灰阶用途一致;`--rd-gray-alpha-400` 仍用于标准结构边框和分隔线。
109111
- `--rd-primary-text` 用于主题色填充上的文字和图标。
110112
- `--rd-shadow` 继续表示完整的明暗模式阴影效果。
111113

112-
`--rd-gray-500``--rd-gray-800` 有意保持未定义;没有经过实际组件证明的职责时,不得自行补充。不要新增 `card``foreground` 等组件别名,组件应直接选择与职责匹配的编号变量。引用明暗模式变量或配置生成变量的 Tailwind 运行时映射必须放在 `@theme inline` 中。
114+
不要新增 `card``foreground` 等组件别名,组件应直接选择与职责匹配的编号变量。引用明暗模式变量或配置生成变量的 Tailwind 运行时映射必须放在 `@theme inline` 中。
113115

114116
详细的 Tailwind CSS 文档请参考 [Tailwind CSS 官方文档](https://tailwindcss.com/docs)

docs/content/docs/zh/home/home.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ home:
164164

165165
| 类型 | 可选值 | 默认值 |
166166
| :--: | :----------: | :----: |
167-
| 数字 | 1, 2, 3, ... | 3 |
167+
| 数字 | 0, 1, 2, ... | 3 |
168+
169+
分类会按 `父分类 › 子分类` 这样的完整层级路径显示。此限制计算的是路径数量,而不是路径中的单个分类层级;设置为 `0` 时显示全部路径。
168170

169171
## 标签
170172

layout/pages/categories.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<% if (category.children.length) { %>
1414
<div class="flex flex-col mx-6 border border-t-0 border-rd-gray-alpha-400 bg-rd-background-200 rounded-b-xl">
1515
<div class="flex items-center gap-1 text-sm text-rd-gray-800 border-b border-dotted border-rd-gray-alpha-400 px-4 py-1">
16-
<i class="fa-regular fa-diagram-subtask text-xs"></i>
16+
<i class="fa-regular fa-diagram-subtask text-xs text-rd-gray-700"></i>
1717
<span><%- __('subcategories') %></span>
1818
</div>
1919
<ul class="relative flex flex-col py-2 px-4 pt-1">

layout/pages/home/post-card.ejs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!-- using ring to avoid border wrapping around post thumbnail -->
2-
<li data-home-card class="relative flex box-border flex-col mb-6 overflow-hidden rounded-2xl ring-inset ring ring-rd-gray-alpha-400 bg-rd-background-100 sm:mb-8 md:mb-10 group">
2+
<li data-home-card class="relative flex box-border flex-col mb-6 overflow-hidden rounded-2xl ring-inset ring ring-rd-gray-alpha-400 sm:mb-8 md:mb-10 group">
33
<% if (post.sticky) { %>
44
<div data-sticky-label class="absolute top-3 right-3 z-10 cursor-default rounded-full border border-rd-gray-alpha-400 bg-rd-background-100/15 px-2.5 py-0.5 text-xs text-rd-gray-900 hover:text-rd-gray-1000">
55
<i class="fa-regular fa-thumbtack mr-1 text-xs" aria-hidden="true"></i><%- __('sticky') %>
@@ -44,9 +44,9 @@
4444
<% } %>
4545
</div>
4646

47-
<div class="flex items-center justify-between text-sm text-rd-gray-900">
48-
<div class="tracking-normal [&_a]:text-rd-gray-900 [&_a:hover]:text-primary">
49-
<span class="mr-2.5"><i class="fa-solid fa-calendars" aria-hidden="true"></i>&nbsp;
47+
<div class="flex items-center justify-between gap-4 text-sm text-rd-gray-900">
48+
<div class="flex min-w-0 flex-wrap items-center gap-x-2.5 gap-y-1 tracking-normal [&_a]:text-rd-gray-900 [&_a:hover]:text-primary">
49+
<span class="flex items-center"><i class="fa-solid fa-calendars" aria-hidden="true"></i>&nbsp;
5050
<time data-home-article-date data-date="<%= post.date %>" datetime="<%= date_xml(post.date) %>">
5151
<% if (theme.home.article_date_format == 'auto' || theme.home.article_date_format == 'relative') { %>
5252
<%= date(post.date, config.date_format) %>
@@ -56,29 +56,32 @@
5656
</time>
5757
</span>
5858
<% if (post.categories.length && theme.home.categories.enable === true) { %>
59-
<span class="mr-2.5 hidden sm:inline">
60-
<i class="fa-solid fa-folders" aria-hidden="true"></i>&nbsp;
61-
<ul class="inline">
62-
<% let previousParentId = null; %>
63-
<% post.categories.forEach((category) => { %>
64-
<% if (previousParentId !== category.parent) { %>
65-
<% if (previousParentId !== null) { %><li class="inline">></li><% } %>
66-
<li class="inline"><a href="<%- url_for(category.path) %>"><%= category.name %></a>&nbsp;</li>
67-
<% } else { %>
68-
<li class="inline">| <a href="<%- url_for(category.path) %>"><%= category.name %></a>&nbsp;</li>
69-
<% } %>
70-
<% previousParentId = category.parent; %>
59+
<%
60+
const categoryPaths = getCategoryPaths(post.categories);
61+
const categoryLimit = Number(theme.home.categories.limit);
62+
const visibleCategoryPaths = categoryLimit > 0 ? categoryPaths.slice(0, categoryLimit) : categoryPaths;
63+
%>
64+
<div class="hidden min-w-0 items-center gap-1.5 sm:flex">
65+
<i class="fa-regular fa-folders shrink-0" aria-hidden="true"></i>
66+
<ul class="flex min-w-0 flex-wrap gap-1.5" aria-label="<%= __('categories') %>">
67+
<% visibleCategoryPaths.forEach((categoryPath) => { %>
68+
<li data-home-category-path class="flex max-w-full items-center rounded-md border border-rd-gray-alpha-300 bg-rd-gray-100/30 px-2 py-0.5 text-xs">
69+
<% categoryPath.forEach((category, index) => { %>
70+
<% if (index > 0) { %><i class="fa-regular fa-angle-right mx-1 shrink-0 text-xs text-rd-gray-800" aria-hidden="true"></i><% } %>
71+
<a class="min-w-0 truncate" href="<%- url_for(category.path) %>"><%= category.name %></a>
72+
<% }); %>
73+
</li>
7174
<% }); %>
7275
</ul>
73-
</span>
76+
</div>
7477
<% } %>
7578
<% if (post.tags.length && theme.home.tags.enable === true) { %>
76-
<span class="mr-2.5 hidden last:mr-0 md:inline">
77-
<i class="fa-solid fa-tags" aria-hidden="true"></i>&nbsp;
78-
<ul class="inline">
79+
<span class="hidden items-center gap-1.5 md:flex">
80+
<i class="fa-solid fa-tags" aria-hidden="true"></i>
81+
<ul class="flex flex-wrap gap-1.5">
7982
<% post.tags.forEach((tag, i) => { %>
8083
<% if (theme.home.tags.limit === 0 || i + 1 <= theme.home.tags.limit) { %>
81-
<li class="inline"><%= i === 0 ? '' : '| ' %><a href="<%- url_for(tag.path) %>"><%= tag.name %></a>&nbsp;</li>
84+
<li class="flex"><a class="rounded-full border border-rd-gray-alpha-300 bg-rd-gray-100/30 px-2 py-0.5 text-xs text-rd-gray-900 hover:bg-rd-gray-alpha-300 hover:text-primary" href="<%- url_for(tag.path) %>"><%= tag.name %></a></li>
8285
<% } %>
8386
<% }); %>
8487
</ul>
@@ -87,7 +90,7 @@
8790
</div>
8891

8992
<a
90-
class="group/read-more flex items-center gap-2"
93+
class="group/read-more flex shrink-0 items-center gap-2 whitespace-nowrap"
9194
href="<%- url_for(post.path) %>"
9295
>
9396
<%- __('read_more') %>

layout/pages/post/meta.ejs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,20 @@
1818
<% } %>
1919
2020
<% if (articleObject?.categories?.length) { %>
21-
<span class="inline <%= metaItemClass %>">
22-
<i class="fa-regular fa-folders" aria-hidden="true"></i>&nbsp;
23-
<ul class="inline">
24-
<% let previousParentId = null; %>
25-
<% articleObject.categories.forEach((category) => { %>
26-
<% if (previousParentId !== category.parent) { %>
27-
<% if (previousParentId !== null) { %><li class="inline">></li><% } %>
28-
<li class="inline"><a href="<%- url_for(category.path) %>"><%= category.name %></a>&nbsp;</li>
29-
<% } else { %>
30-
<li class="inline">| <a href="<%- url_for(category.path) %>"><%= category.name %></a>&nbsp;</li>
31-
<% } %>
32-
<% previousParentId = category.parent; %>
21+
<div class="inline-flex flex-wrap items-center gap-1 <%= metaItemClass %>">
22+
<i class="fa-regular fa-folders shrink-0" aria-hidden="true"></i>
23+
<ul class="inline-flex flex-wrap items-center gap-x-1.5 gap-y-1" aria-label="<%= __('categories') %>">
24+
<% getCategoryPaths(articleObject.categories).forEach((categoryPath, pathIndex) => { %>
25+
<li data-post-category-path class="inline-flex items-center">
26+
<% if (pathIndex > 0) { %><span class="mr-1.5 text-rd-gray-800" aria-hidden="true">·</span><% } %>
27+
<% categoryPath.forEach((category, index) => { %>
28+
<% if (index > 0) { %><i class="fa-regular fa-angle-right mx-1 text-xs text-rd-gray-800" aria-hidden="true"></i><% } %>
29+
<a href="<%- url_for(category.path) %>"><%= category.name %></a>
30+
<% }); %>
31+
</li>
3332
<% }); %>
3433
</ul>
35-
</span>
34+
</div>
3635
<% } %>
3736
<% if (articleObject?.tags?.length) { %>
3837
<span class="hidden md:inline <%= metaItemClass %>">

openspec/specs/theme-visual-system/spec.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Define the theme's structural borders, static depth, and interaction-effect cont
88

99
### Requirement: Numbered RD colors have fixed roles
1010

11-
Redefine SHALL provide a compact mode-aware numbered color foundation consisting of `--rd-background-100`, `--rd-background-200`, `--rd-gray-100`, `--rd-gray-200`, `--rd-gray-300`, `--rd-gray-alpha-400`, `--rd-gray-900`, and `--rd-gray-1000`. The background levels SHALL represent primary and alternate page backgrounds, gray levels 100 through 300 SHALL represent increasingly visible neutral component backgrounds, gray-alpha 400 SHALL represent the standard structural border, and gray levels 900 and 1000 SHALL represent secondary and primary text and icons.
11+
Redefine SHALL provide a mode-aware numbered color foundation consisting of `--rd-background-100`, `--rd-background-200`, solid `--rd-gray-100` through `--rd-gray-1000`, and translucent `--rd-gray-alpha-100` through `--rd-gray-alpha-1000`. The background levels SHALL represent primary and alternate page backgrounds; gray levels 100 through 300 SHALL represent increasingly visible neutral component backgrounds; gray levels 400 through 600 SHALL represent increasingly visible component borders; gray levels 700 and 800 SHALL represent default and hover high-contrast component backgrounds; and gray levels 900 and 1000 SHALL represent secondary and primary text and icons. Each gray-alpha level SHALL mirror the corresponding solid gray role when transparency is required, with gray-alpha 400 representing the standard structural border.
1212

1313
#### Scenario: Contributor chooses a page background
1414

@@ -21,6 +21,21 @@ Redefine SHALL provide a compact mode-aware numbered color foundation consisting
2121
- **WHEN** a neutral control or nested component needs default, hover, or active differentiation
2222
- **THEN** it uses the applicable `--rd-gray-100`, `--rd-gray-200`, or `--rd-gray-300` level
2323

24+
#### Scenario: Contributor chooses a component border
25+
26+
- **WHEN** a neutral component border needs default, hover, or active differentiation
27+
- **THEN** it uses the applicable `--rd-gray-400`, `--rd-gray-500`, or `--rd-gray-600` level
28+
29+
#### Scenario: Contributor chooses a high-contrast component background
30+
31+
- **WHEN** a neutral high-contrast component needs default or hover differentiation
32+
- **THEN** it uses `--rd-gray-700` or `--rd-gray-800`, respectively
33+
34+
#### Scenario: Contributor needs a translucent gray
35+
36+
- **WHEN** a numbered gray role must allow the underlying surface to show through
37+
- **THEN** it uses the corresponding `--rd-gray-alpha-*` level
38+
2439
#### Scenario: Contributor chooses text color
2540

2641
- **WHEN** primary or secondary text or an icon renders on a supported neutral background

scripts/helpers/page-helpers.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,34 @@ hexo.extend.helper.register("getCategoryTree", function (categories) {
6565
return roots;
6666
});
6767

68+
hexo.extend.helper.register("getCategoryPaths", function (categories) {
69+
const items = [];
70+
categories?.forEach((category) => items.push(category));
71+
72+
const nodes = items.map((category) => ({
73+
id: String(category._id),
74+
parentId: category.parent ? String(category.parent) : null,
75+
name: category.name,
76+
path: category.path,
77+
}));
78+
const nodesById = new Map(nodes.map((node) => [node.id, node]));
79+
const parentIds = new Set(
80+
nodes.map((node) => node.parentId).filter((id) => nodesById.has(id)),
81+
);
82+
83+
return nodes.filter((node) => !parentIds.has(node.id)).map((leaf) => {
84+
const categoryPath = [];
85+
let current = leaf;
86+
87+
while (current) {
88+
categoryPath.unshift({ name: current.name, path: current.path });
89+
current = current.parentId ? nodesById.get(current.parentId) : null;
90+
}
91+
92+
return categoryPath;
93+
});
94+
});
95+
6896
hexo.extend.helper.register("resolvePageKind", function (page) {
6997
if (this.is_home()) return "home";
7098
if (this.is_post()) return "post";

styles/base/variables.css

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,23 @@
2626
--rd-gray-100: #f7f7f8;
2727
--rd-gray-200: #f2f3f4;
2828
--rd-gray-300: #eef0f2;
29-
--rd-gray-alpha-400: rgb(0 0 0 / 10%);
30-
--rd-gray-800: #778084;
29+
--rd-gray-400: #e4e7e9;
30+
--rd-gray-500: #d8dcde;
31+
--rd-gray-600: #c7cccf;
32+
--rd-gray-700: #a4abad;
33+
--rd-gray-800: #7d8689;
3134
--rd-gray-900: #5c6669;
3235
--rd-gray-1000: #343a3c;
36+
--rd-gray-alpha-100: rgb(0 0 0 / 3%);
37+
--rd-gray-alpha-200: rgb(0 0 0 / 5%);
38+
--rd-gray-alpha-300: rgb(0 0 0 / 6%);
39+
--rd-gray-alpha-400: rgb(0 0 0 / 10%);
40+
--rd-gray-alpha-500: rgb(0 0 0 / 14%);
41+
--rd-gray-alpha-600: rgb(0 0 0 / 20%);
42+
--rd-gray-alpha-700: rgb(0 0 0 / 34%);
43+
--rd-gray-alpha-800: rgb(0 0 0 / 48%);
44+
--rd-gray-alpha-900: rgb(0 0 0 / 61%);
45+
--rd-gray-alpha-1000: rgb(0 0 0 / 78%);
3346
--scrollbar-color: #c1c1c1;
3447
--scrollbar-color-hover: #a1a1a1;
3548
--scroll-bar-bg-color: #fafafa;
@@ -52,10 +65,23 @@
5265
--rd-gray-100: #27282c;
5366
--rd-gray-200: #2a2c30;
5467
--rd-gray-300: #2d2f34;
55-
--rd-gray-alpha-400: rgb(255 255 255 / 8%);
56-
--rd-gray-800: #778084;
68+
--rd-gray-400: #35373c;
69+
--rd-gray-500: #41434a;
70+
--rd-gray-600: #50525b;
71+
--rd-gray-700: #656772;
72+
--rd-gray-800: #7c7d89;
5773
--rd-gray-900: #9595a2;
5874
--rd-gray-1000: #cbcbd1;
75+
--rd-gray-alpha-100: rgb(255 255 255 / 3%);
76+
--rd-gray-alpha-200: rgb(255 255 255 / 5%);
77+
--rd-gray-alpha-300: rgb(255 255 255 / 6%);
78+
--rd-gray-alpha-400: rgb(255 255 255 / 8%);
79+
--rd-gray-alpha-500: rgb(255 255 255 / 16%);
80+
--rd-gray-alpha-600: rgb(255 255 255 / 23%);
81+
--rd-gray-alpha-700: rgb(255 255 255 / 33%);
82+
--rd-gray-alpha-800: rgb(255 255 255 / 43%);
83+
--rd-gray-alpha-900: rgb(255 255 255 / 54%);
84+
--rd-gray-alpha-1000: rgb(255 255 255 / 77%);
5985
--scrollbar-color: #898989;
6086
--scrollbar-color-hover: #a1a1a1;
6187
--scroll-bar-bg-color: #2a2c30;
@@ -69,6 +95,6 @@
6995
--note-type-title-bg: #4e3a1e;
7096
--note-black-title-bg: #1e1e1e;
7197
--note-purple-title-bg: #4e1e4e;
72-
--rd-shadow: 0 6px 24px rgb(0 0 0 / 25%);
98+
--rd-shadow: 0 6px 24px rgb(0 0 0 / 15%);
7399
}
74100
}

0 commit comments

Comments
 (0)