Skip to content

Commit 6886398

Browse files
xkcodingclaude
andcommitted
feat: add view source link and CC license declaration
- Add viewSource config for raw markdown link (GitHub raw URL) - Add license config for CC BY-NC-SA 4.0 declaration - Create ViewSource.astro component with GitHub icon - Create License.astro component with official CC badge and post URL - Integrate components into PostDetails layout - Simplify BackToTopButton and move to TOC sidebar - Add tech stack info to Footer (Astro & AstroPaper) - Remove ShareLinks from post details - Update TableOfContents to support slot for BackToTopButton Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d9f9595 commit 6886398

10 files changed

Lines changed: 280 additions & 48 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Change: 新增查看原文链接和 CC 协议声明
2+
3+
## Why
4+
5+
当前博客文章页面只有"编辑"链接(指向 GitHub edit 页面),缺少以下功能:
6+
1. 读者无法快速查看文章的 Markdown 原始内容(source 模式)
7+
2. 没有明确的版权协议声明,不利于内容保护和合规转载
8+
9+
## What Changes
10+
11+
- 新增"查看原文"链接组件,跳转到 GitHub 仓库文件的 blob 页面(只读模式)
12+
- 新增 CC BY-NC-SA 4.0 协议声明组件,显示在文章正文下方
13+
-`config.ts` 中添加相关配置项(协议类型、仓库 URL 等)
14+
- 修改 `PostDetails.astro` 布局,集成新组件
15+
16+
## Impact
17+
18+
- Affected code:
19+
- `src/config.ts` - 新增配置项
20+
- `src/components/ViewSource.astro` - 新建组件
21+
- `src/components/License.astro` - 新建组件
22+
- `src/layouts/PostDetails.astro` - 集成新组件
23+
- `src/assets/icons/` - 可能需要新增图标
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## ADDED Requirements
2+
3+
### Requirement: View Source Link
4+
5+
The system SHALL provide a "View Source" link on article detail pages that opens the raw Markdown file in the GitHub repository (blob view, not edit mode).
6+
7+
#### Scenario: User clicks View Source link
8+
9+
- **WHEN** user clicks the "View Source" link on an article page
10+
- **THEN** a new tab opens with the GitHub blob URL for the article's Markdown file
11+
- **AND** the URL format is `https://github.com/{owner}/{repo}/blob/{branch}/{filePath}`
12+
13+
#### Scenario: View Source link visibility
14+
15+
- **WHEN** `SITE.viewSource.enabled` is `true`
16+
- **THEN** the View Source link is displayed on the article page
17+
- **WHEN** `SITE.viewSource.enabled` is `false`
18+
- **THEN** the View Source link is hidden
19+
20+
### Requirement: CC License Declaration
21+
22+
The system SHALL display a Creative Commons license declaration (CC BY-NC-SA 4.0) at the bottom of each article's content area.
23+
24+
#### Scenario: License display position
25+
26+
- **WHEN** an article page is rendered
27+
- **THEN** the CC license declaration appears after the article content
28+
- **AND** before the share links section
29+
30+
#### Scenario: License content elements
31+
32+
- **WHEN** the license component is rendered
33+
- **THEN** it displays:
34+
- The CC BY-NC-SA 4.0 license icon/badge
35+
- A brief license description in Chinese
36+
- A link to the full license text on Creative Commons website
37+
38+
#### Scenario: License visibility control
39+
40+
- **WHEN** `SITE.license.enabled` is `true`
41+
- **THEN** the license declaration is displayed
42+
- **WHEN** `SITE.license.enabled` is `false`
43+
- **THEN** the license declaration is hidden
44+
45+
### Requirement: Configuration Options
46+
47+
The system SHALL provide configuration options in `src/config.ts` for View Source and License features.
48+
49+
#### Scenario: viewSource configuration
50+
51+
- **WHEN** configuring the View Source feature
52+
- **THEN** the following options are available:
53+
- `enabled`: boolean to show/hide the link
54+
- `text`: display text for the link (default: "查看原文")
55+
- `url`: base URL for the repository blob path
56+
57+
#### Scenario: license configuration
58+
59+
- **WHEN** configuring the License feature
60+
- **THEN** the following options are available:
61+
- `enabled`: boolean to show/hide the license
62+
- `type`: license type identifier (e.g., "CC BY-NC-SA 4.0")
63+
- `url`: URL to the full license text
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## 1. 配置扩展
2+
3+
- [x] 1.1 在 `src/config.ts` 中添加 `viewSource` 配置项(enabled、text、url)
4+
- [x] 1.2 在 `src/config.ts` 中添加 `license` 配置项(enabled、type、url)
5+
6+
## 2. 组件开发
7+
8+
- [x] 2.1 创建 `src/components/ViewSource.astro` 组件
9+
- 接收 `post` 参数,生成 raw URL
10+
- 使用 GitHub 图标
11+
- 样式与 EditPost 保持一致
12+
- [x] 2.2 创建 `src/components/License.astro` 组件
13+
- 显示 CC BY-NC-SA 4.0 协议信息
14+
- 包含协议图标和链接
15+
- 简洁的版权声明文案
16+
17+
## 3. 布局集成
18+
19+
- [x] 3.1 修改 `src/layouts/PostDetails.astro`
20+
- 在文章正文后、分享链接前添加 License 组件
21+
- 在元信息区添加 ViewSource 链接
22+
- [x] 3.2 确保移动端和桌面端响应式显示正常
23+
24+
## 4. 验证
25+
26+
- [x] 4.1 本地构建测试,确保无类型错误
27+
- [x] 4.2 验证链接跳转正确(raw 页面)
28+
- [x] 4.3 检查暗色/亮色模式下样式正常

src/components/BackToTopButton.astro

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,44 @@
11
---
2-
import IconChevronLeft from "@/assets/icons/IconChevronLeft.svg";
32
import IconArrowNarrowUp from "@/assets/icons/IconArrowNarrowUp.svg";
43
---
54

65
<div
76
id="btt-btn-container"
8-
class:list={[
9-
"fixed end-4 bottom-8 z-50",
10-
"md:sticky md:end-auto md:float-end md:me-1",
11-
"translate-y-14 opacity-0 transition duration-500",
12-
]}
7+
class="mt-4 translate-y-4 opacity-0 transition duration-500"
138
>
149
<button
1510
data-button="back-to-top"
16-
class:list={[
17-
"group relative bg-background px-2 py-1",
18-
"size-14 rounded-full shadow-xl",
19-
"md:h-8 md:w-fit md:rounded-md md:shadow-none md:focus-visible:rounded-none",
20-
"md:bg-background/35 md:bg-clip-padding md:backdrop-blur-lg",
21-
]}
11+
class="flex items-center gap-1 rounded-md border border-foreground/10 bg-background/50 px-3 py-1.5 text-sm text-foreground/70 backdrop-blur-sm transition-colors hover:border-accent hover:text-accent"
2212
>
23-
<span
24-
id="progress-indicator"
25-
class="absolute inset-0 -z-10 block size-14 scale-110 rounded-full bg-transparent md:hidden md:h-8 md:rounded-md"
26-
></span>
27-
<IconChevronLeft class="inline-block rotate-90 md:hidden" />
28-
<span class="sr-only text-sm group-hover:text-accent md:not-sr-only">
29-
<IconArrowNarrowUp class="inline-block size-4" />
30-
返回顶部
31-
</span>
13+
<IconArrowNarrowUp class="inline-block size-4" />
14+
返回顶部
3215
</button>
3316
</div>
3417

3518
<script is:inline data-astro-rerun>
36-
/** Scrolls the document to the top when
37-
* the "Back to Top" button is clicked. */
3819
function backToTop() {
3920
const rootElement = document.documentElement;
4021
const btnContainer = document.querySelector("#btt-btn-container");
4122
const backToTopBtn = document.querySelector("[data-button='back-to-top']");
42-
const progressIndicator = document.querySelector("#progress-indicator");
4323

44-
if (!rootElement || !btnContainer || !backToTopBtn || !progressIndicator)
45-
return;
24+
if (!rootElement || !btnContainer || !backToTopBtn) return;
4625

47-
// Attach click event handler for back-to-top button
4826
backToTopBtn.addEventListener("click", () => {
49-
document.body.scrollTop = 0; // For Safari
50-
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
27+
document.body.scrollTop = 0;
28+
document.documentElement.scrollTop = 0;
5129
});
5230

53-
// Handle button visibility according to scroll position
5431
let lastVisible = null;
5532
function handleScroll() {
5633
const scrollTotal = rootElement.scrollHeight - rootElement.clientHeight;
5734
const scrollTop = rootElement.scrollTop;
58-
const scrollPercent = Math.floor((scrollTop / scrollTotal) * 100);
59-
60-
progressIndicator.style.setProperty(
61-
"background-image",
62-
`conic-gradient(var(--accent), var(--accent) ${scrollPercent}%, transparent ${scrollPercent}%)`
63-
);
64-
6535
const isVisible = scrollTop / scrollTotal > 0.3;
6636

6737
if (isVisible !== lastVisible) {
6838
btnContainer.classList.toggle("opacity-100", isVisible);
6939
btnContainer.classList.toggle("translate-y-0", isVisible);
7040
btnContainer.classList.toggle("opacity-0", !isVisible);
71-
btnContainer.classList.toggle("translate-y-14", !isVisible);
41+
btnContainer.classList.toggle("translate-y-4", !isVisible);
7242
lastVisible = isVisible;
7343
}
7444
}

src/components/Footer.astro

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,33 @@ const { noMarginTop = false, class: className, ...attrs } = Astro.props;
1717
<div
1818
class="flex flex-col items-center justify-between gap-2 border-t border-border py-4 sm:flex-row"
1919
>
20-
<div class="flex items-center gap-1 text-sm text-foreground/70">
21-
<span>&#169; {currentYear} xkcoding</span>
20+
<div
21+
class="flex flex-wrap items-center justify-center gap-x-1 gap-y-0.5 text-sm text-foreground/70 sm:justify-start"
22+
>
23+
<span>&#169; 2016-{currentYear} xkcoding</span>
2224
<span class="text-foreground/40">·</span>
2325
<span>保留所有权利</span>
26+
<span class="text-foreground/40">·</span>
27+
<span>
28+
Powered by
29+
<a
30+
href="https://astro.build/"
31+
target="_blank"
32+
rel="noopener noreferrer"
33+
class="text-accent hover:underline"
34+
>
35+
Astro
36+
</a>
37+
&
38+
<a
39+
href="https://github.com/satnaing/astro-paper"
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
class="text-accent hover:underline"
43+
>
44+
AstroPaper
45+
</a>
46+
</span>
2447
</div>
2548
<div class="flex items-center gap-2 text-xs text-foreground/50">
2649
<a

src/components/License.astro

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
import { SITE } from "@/config";
3+
4+
type Props = {
5+
class?: string;
6+
postUrl: string;
7+
};
8+
9+
const { class: className = "", postUrl } = Astro.props;
10+
11+
const showLicense = SITE.license.enabled;
12+
const licenseType = SITE.license.type;
13+
const licenseUrl = SITE.license.url;
14+
const fullUrl = `${SITE.website.replace(/\/$/, "")}${postUrl}`;
15+
---
16+
17+
{
18+
showLicense && (
19+
<div
20+
class:list={[
21+
"flex flex-wrap items-center gap-4 rounded-lg border border-foreground/10 bg-foreground/5 px-5 py-4",
22+
className,
23+
]}
24+
>
25+
{/* CC BY-NC-SA Official Badge */}
26+
<a
27+
href={licenseUrl}
28+
target="_blank"
29+
rel="noopener noreferrer license"
30+
class="shrink-0"
31+
>
32+
<img
33+
src="https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png"
34+
alt={licenseType}
35+
class="h-8"
36+
loading="lazy"
37+
/>
38+
</a>
39+
{/* License Text */}
40+
<div class="flex flex-col gap-1 text-sm">
41+
<p class="text-foreground/70">
42+
本文采用
43+
<a
44+
href={licenseUrl}
45+
target="_blank"
46+
rel="noopener noreferrer license"
47+
class="font-medium text-accent hover:underline"
48+
>
49+
{licenseType}
50+
</a>
51+
协议,转载请注明出处。
52+
</p>
53+
<p class="text-foreground/50">
54+
原文链接:
55+
<a href={fullUrl} class="break-all text-accent/80 hover:underline">
56+
{fullUrl}
57+
</a>
58+
</p>
59+
</div>
60+
</div>
61+
)
62+
}

src/components/TableOfContents.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const filteredHeadings = headings.filter(h => h.depth >= 2 && h.depth <= 4);
3030
))
3131
}
3232
</ul>
33+
<slot />
3334
</nav>
3435

3536
<script is:inline data-astro-rerun>

src/components/ViewSource.astro

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
import type { CollectionEntry } from "astro:content";
3+
import IconGitHub from "@/assets/icons/IconGitHub.svg";
4+
import { SITE } from "@/config";
5+
6+
type Props = {
7+
class?: string;
8+
post: CollectionEntry<"blog">;
9+
};
10+
11+
const { post, class: className = "" } = Astro.props;
12+
13+
const href = `${SITE.viewSource.url}${post.filePath}`;
14+
const showViewSource = SITE.viewSource.enabled && href.trim() !== "";
15+
---
16+
17+
{
18+
showViewSource && (
19+
<a
20+
href={href}
21+
target="_blank"
22+
rel="noopener noreferrer"
23+
class:list={[
24+
"flex items-center gap-1.5 opacity-80 hover:text-accent",
25+
className,
26+
]}
27+
>
28+
<IconGitHub class="inline-block" />
29+
<span>{SITE.viewSource.text}</span>
30+
</a>
31+
)
32+
}

src/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ export const SITE = {
1717
text: "内容有误?点此修正",
1818
url: "https://github.com/xkcoding/MyBlog/edit/master/",
1919
},
20+
viewSource: {
21+
enabled: true,
22+
text: "查看原文",
23+
url: "https://github.com/xkcoding/MyBlog/raw/master/",
24+
},
25+
license: {
26+
enabled: true,
27+
type: "CC BY-NC-SA 4.0",
28+
url: "https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans",
29+
},
2030
dynamicOgImage: true,
2131
dir: "ltr",
2232
lang: "zh-CN",

0 commit comments

Comments
 (0)