Skip to content

Commit 280e6c6

Browse files
committed
新增书架区
1 parent 7cac416 commit 280e6c6

10 files changed

Lines changed: 379 additions & 17 deletions

File tree

packages/pure/components/user/MdxRepl.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { width } = Astro.props
77
---
88

99
<div class='mdx-repl overflow-hidden rounded-xl border'>
10-
<div class='mdx-repl-container flex flex-col items-center justify-center p-4'>
10+
<div class='mdx-repl-container not-prose flex flex-col items-center justify-center p-4'>
1111
<slot />
1212
</div>
1313
<div class='bg-muted'>

public/bookshelf.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{}]

public/clouds/mountain.jpg

-4.4 MB
Binary file not shown.

src/assets/cats/1-h-3.jpg

-1.81 MB
Binary file not shown.

src/assets/cats/config/data.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Import all cat images
22
import _1h1 from '@/assets/cats/1-h-1.jpg';
33
import _1h2 from '@/assets/cats/1-h-2.jpg';
4-
import _1h3 from '@/assets/cats/1-h-3.jpg';
54
import _1h from '@/assets/cats/1-h.jpg';
65
import _1m1 from '@/assets/cats/1-m-1.jpg';
76
import _1m2 from '@/assets/cats/1-m-2.png';
@@ -93,31 +92,21 @@ export const photos: Photo[] = [
9392
{
9493
subjects: ['小花'],
9594
image: _1h1,
96-
tags: ['三花', '田园猫', '可爱', 'Queen'],
9795
},
9896
{
9997
subjects: ['小花'],
10098
image: _1h2,
10199
capturedAt: '2025-04-03',
102-
tags: ['三花', '田园猫', '可爱', 'Queen'],
103-
},
104-
{
105-
subjects: ['小花'],
106-
image: _1h3,
107-
capturedAt: '2025-04-03',
108-
caption: '小花的第三张照片'
109100
},
110101
{
111102
subjects: ['小花'],
112103
image: _1h,
113104
capturedAt: '2025-04-03',
114-
tags: ['花猫'],
115105
},
116106
{
117107
subjects: ['月亮'],
118108
image: _1m1,
119109
capturedAt: '2025-04-03',
120-
tags: ['金吉拉','可爱']
121110
},
122111
{
123112
subjects: ['月亮'],
@@ -142,7 +131,6 @@ export const photos: Photo[] = [
142131
subjects: ['大姐'],
143132
image: _211,
144133
capturedAt: '2025-04-03',
145-
tags: ['大姐猫']
146134
},
147135
{
148136
subjects: ['大头'],
@@ -159,7 +147,6 @@ export const photos: Photo[] = [
159147
subjects: ['老三'],
160148
image: _223,
161149
capturedAt: '2025-04-03',
162-
tags: ['老三猫']
163150
},
164151
{
165152
subjects: ['老四'],
@@ -181,19 +168,16 @@ export const photos: Photo[] = [
181168
subjects: ['老四'],
182169
image: _224,
183170
capturedAt: '2025-04-03',
184-
tags: ['老四猫', '主图']
185171
},
186172
{
187173
subjects: ['老六'],
188174
image: _2261,
189175
capturedAt: '2025-04-03',
190-
tags: ['老六猫', '建筑']
191176
},
192177
{
193178
subjects: ['老六'],
194179
image: _226,
195180
capturedAt: '2025-04-03',
196-
caption: '老六的主要照片'
197181
},
198182
{
199183
subjects: ['小六'],

src/assets/shelf/no_so.png

1.45 MB
Loading
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
import { Icon } from '@/custom/components/user';
3+
import type { IconName } from 'packages/pure/libs/icons';
4+
5+
interface Props {
6+
item: {
7+
title: string;
8+
type: 'book' | 'paper' | 'article';
9+
status: 'read' | 'reading' | 'to-read';
10+
description?: string;
11+
rating?: number;
12+
identifier?: string;
13+
sourceUrl?: string;
14+
links?: string[];
15+
tags?: string[];
16+
};
17+
postsMap: Map<string, { title: string; }>;
18+
index: number;
19+
}
20+
21+
const { item, postsMap, index } = Astro.props;
22+
23+
const typeIcons: { [key: string]: IconName } = {
24+
book: 'book',
25+
paper: 'document',
26+
article: 'link'
27+
};
28+
29+
const identifierIcon = item.type === 'book' ? 'hashtag' : 'doi';
30+
31+
const linkedPosts = item.links?.map(slug => ({
32+
slug,
33+
title: postsMap.get(slug)?.title || slug
34+
})) || [];
35+
36+
let sourceHostname = '';
37+
if (item.type === 'article' && item.sourceUrl) {
38+
try {
39+
sourceHostname = new URL(item.sourceUrl).hostname;
40+
} catch (e) {
41+
// Invalid URL, ignore.
42+
}
43+
}
44+
---
45+
46+
<div
47+
class="reading-card group relative flex flex-col overflow-hidden rounded-lg border bg-background/60 p-4 transition-all duration-300 hover:border-primary/50 hover:shadow-lg hover:bg-background/90"
48+
>
49+
<!-- Index Number -->
50+
<span class="absolute top-2 right-2 flex h-5 w-5 items-center justify-center rounded-full bg-muted/60 text-xs font-bold text-muted-foreground transition-colors duration-300 group-hover:bg-muted/90">
51+
{index}
52+
</span>
53+
54+
<div class="relative z-10 flex flex-col h-full">
55+
<!-- Card Header: Type Icon + Title -->
56+
<div class="flex items-start gap-2">
57+
<Icon name={typeIcons[item.type]} class="size-5 flex-shrink-0 text-muted-foreground" />
58+
<h3 class="text-base font-semibold text-foreground">{item.title}</h3>
59+
</div>
60+
61+
<!-- Attributes Section: Badges -->
62+
<div class="mt-2 flex flex-wrap items-center gap-2">
63+
{item.rating && (
64+
<div class="flex items-center gap-1 rounded-full bg-muted/80 px-2 py-0.5 text-xs font-medium text-muted-foreground">
65+
<Icon name="star" class="size-3.5 text-yellow-400" />
66+
<span>{item.rating.toFixed(1)}</span>
67+
</div>
68+
)}
69+
{item.identifier && (
70+
<div class="flex items-center gap-1 rounded-full bg-muted/80 px-2 py-0.5 text-xs font-medium text-muted-foreground">
71+
<Icon name={identifierIcon} class="size-3.5" />
72+
<span>{item.identifier}</span>
73+
</div>
74+
)}
75+
</div>
76+
77+
<!-- Description -->
78+
{item.description && (
79+
<p class="mt-3 border-l-2 border-border pl-3 text-sm text-muted-foreground">
80+
{item.description}
81+
</p>
82+
)}
83+
84+
<!-- Tags -->
85+
{item.tags && item.tags.length > 0 && (
86+
<div class="mt-3 flex flex-wrap gap-1.5">
87+
{item.tags.map(tag => (
88+
<span class="rounded bg-primary/10 px-1.5 py-0.5 text-xs font-medium text-primary/80">
89+
{tag}
90+
</span>
91+
))}
92+
</div>
93+
)}
94+
95+
<!-- Spacer to push content to bottom -->
96+
<div class="flex-grow"></div>
97+
98+
<!-- Linked Notes -->
99+
{linkedPosts.length > 0 && (
100+
<div class="mt-3 border-t pt-3">
101+
<h4 class="mb-1.5 text-xs font-semibold text-muted-foreground">相关笔记:</h4>
102+
<ul class="flex flex-col gap-1">
103+
{linkedPosts.map((post) => (
104+
<li>
105+
<a
106+
href={`/blog/${post.slug}`}
107+
class="text-sm text-primary/80 hover:text-primary hover:underline underline-offset-2"
108+
>
109+
{post.title}
110+
</a>
111+
</li>
112+
))}
113+
</ul>
114+
</div>
115+
)}
116+
117+
<!-- Source URL (New Position & Style) -->
118+
{sourceHostname && (
119+
<div class="mt-3 border-t pt-3">
120+
<a href={item.sourceUrl} target="_blank" rel="nofollow noopener noreferrer" class="flex items-center gap-2 text-sm font-medium text-primary hover:underline">
121+
<img src={`https://favicon.im/${sourceHostname}`} alt={`Favicon for ${sourceHostname}`} class="size-4" />
122+
<span>阅读原文</span>
123+
</a>
124+
</div>
125+
)}
126+
</div>
127+
</div>

src/content/blog/shelf-config.mdx

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: 书架功能配置指南
3+
description: '如何配置和使用书架页面'
4+
publishDate: 2025-10-15 22:00:00
5+
slug: shelf-config
6+
tags: ['建站', '功能说明']
7+
---
8+
9+
import { MdxRepl, Aside } from '@/custom/components/user';
10+
import { TabItem, Tabs } from 'astro-pure/user';
11+
import ReadingCard from '@/components/reading/ReadingCard.astro';
12+
13+
"书架"页面是一个独立的看板, 用于展示您的阅读轨迹. 其所有内容都通过一个单独的 JSON 文件进行管理, 不依赖于任何文章的 frontmatter.
14+
15+
## 1. 核心配置文件
16+
17+
您需要维护的唯一文件是位于项目根目录下 `public/` 文件夹中的 `bookshelf.json`
18+
19+
## 2. 数据结构详解
20+
21+
该 JSON 文件是一个包含多个“阅读条目”对象的数组. 每个对象代表一张卡片, 其结构如下:
22+
23+
<Aside type="note" title="字段说明">
24+
| 字段 | 类型 | 是否必需 | 说明 |
25+
| :--- | :--- | :--- | :--- |
26+
| `title` | `string` | 必需 | 书籍, 论文或文章的标题. |
27+
| `type` | `string` | 必需 | `'book' \| 'paper' \| 'article'`, 决定了卡片上显示的图标. |
28+
| `status` | `string` | 必需 | `'to-read' \| 'reading' \| 'read'`, 决定了卡片出现在看板的哪一列. |
29+
| `description` | `string` | 可选 | 一句简短的描述, 会显示在标题下方. |
30+
| `rating` | `number` | 可选 | 评分, 1-5 的数字, 会以星星形式展示. |
31+
| `identifier` | `string` | 可选 | 唯一标识符. 对于书籍是 ISBN, 对于论文是 DOI. |
32+
| `sourceUrl` | `string` | 可选 | **仅用于 `article` 类型**, 指向原文的链接. |
33+
| `tags` | `string[]` | 可选 | 标签数组, 会在卡片底部显示为彩色徽章. |
34+
| `links` | `string[]` | 可选 | 关联的博客笔记. 只填写笔记的 `slug`. |
35+
</Aside>
36+
37+
## 3. 工作流程
38+
39+
假设您读完一本书,并为它写了一篇读后感, 如何将它们添加到书架呢?
40+
41+
1. **撰写笔记**: 像往常一样, 在 `src/content/blog/` 目录下创建您的 `.mdx` 笔记文件. 完成文章后, 记下您在 frontmatter 中为它设置的 `slug`.
42+
43+
2. **打开配置文件**: 打开 `public/bookshelf.json` 文件.
44+
45+
3. **添加新条目**: 在 JSON 数组中添加一个新的对象, 代表您读完的这本书. 填写 `title`, `type`, `status` 等信息.
46+
47+
4. **关联笔记**: 在该对象的 `links` 数组中, 添加您刚刚记下的笔记 `slug` 字符串.
48+
49+
5. **保存**: 保存文件. 网站下次构建时, 书架页面就会自动更新.
50+
51+
## 4. 卡片效果预览
52+
53+
下面是不同类型卡片的渲染效果和对应的 JSON 数据结构。
54+
55+
### 书籍 (Book) 示例
56+
57+
<MdxRepl>
58+
<ReadingCard
59+
item={{
60+
title: "人类简史",
61+
type: "book",
62+
status: "read",
63+
description: "从石器时代到人工智能, 一部宏大的人类历史.",
64+
rating: 5,
65+
identifier: "978-7-5086-6074-3",
66+
tags: ["历史", "人类学"],
67+
links: ["sapiens-note-1"]
68+
}}
69+
postsMap={new Map()}
70+
index={1}
71+
/>
72+
<Fragment slot="desc">
73+
````json
74+
{
75+
"title": "人类简史",
76+
"type": "book",
77+
"status": "read",
78+
"description": "从石器时代到人工智能, 一部宏大的人类历史.",
79+
"rating": 5,
80+
"identifier": "978-7-5086-6074-3",
81+
"tags": ["历史", "人类学"],
82+
"links": ["sapiens-note-1"]
83+
}
84+
````
85+
</Fragment>
86+
</MdxRepl>
87+
88+
### 论文 (Paper) 示例
89+
90+
<MdxRepl>
91+
<ReadingCard
92+
item={{
93+
title: "Attention Is All You Need",
94+
type: "paper",
95+
status: "read",
96+
description: "Transformer 架构的开山之作, 奠定了现代大语言模型的基础.",
97+
rating: 5,
98+
identifier: "10.48550/arXiv.1706.03762",
99+
tags: ["NLP", "Transformer"],
100+
links: ["transformer-explained"]
101+
}}
102+
postsMap={new Map([['transformer-explained', {title: 'Transformer 架构解析'}]])}
103+
index={2}
104+
/>
105+
<Fragment slot="desc">
106+
````json
107+
{
108+
"title": "Attention Is All You Need",
109+
"type": "paper",
110+
"status": "read",
111+
"description": "Transformer 架构的开山之作, 奠定了现代大语言模型的基础.",
112+
"rating": 5,
113+
"identifier": "10.48550/arXiv.1706.03762",
114+
"tags": ["NLP", "Transformer"],
115+
"links": ["transformer-explained"]
116+
}
117+
````
118+
</Fragment>
119+
</MdxRepl>
120+
121+
### 文章 (Article) 示例
122+
123+
<MdxRepl>
124+
<ReadingCard
125+
item={{
126+
title: "Working with TCP Sockets",
127+
type: "article",
128+
status: "reading",
129+
description: "一篇关于 TCP Socket 编程的优秀文章.",
130+
sourceUrl: "https://www.google.com",
131+
tags: ["Network", "Socket"]
132+
}}
133+
postsMap={new Map()}
134+
index={3}
135+
/>
136+
<Fragment slot="desc">
137+
````json
138+
{
139+
"title": "Working with TCP Sockets",
140+
"type": "article",
141+
"status": "reading",
142+
"description": "一篇关于 TCP Socket 编程的优秀文章.",
143+
"sourceUrl": "https://www.google.com",
144+
"tags": ["Network", "Socket"]
145+
}
146+
````
147+
</Fragment>
148+
</MdxRepl>

0 commit comments

Comments
 (0)