Skip to content

Commit 578b9c5

Browse files
committed
feat: 添加页面标题导航功能 - 标题居中显示,左上角添加返回LeetCode Hot 100链接
1 parent 33344ea commit 578b9c5

File tree

7 files changed

+338
-3
lines changed

7 files changed

+338
-3
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Design Document
2+
3+
## Overview
4+
5+
本设计文档描述页面标题和导航功能的实现方案。主要修改 Header 组件,将现有的左对齐标题改为居中显示,并在左侧添加返回 LeetCode Hot 100 的链接。设计保持与现有代码风格一致,使用 CSS Module 进行样式管理。
6+
7+
## Architecture
8+
9+
### 组件结构
10+
11+
```
12+
Header
13+
├── BackLink (左侧) - 返回 LeetCode Hot 100 链接
14+
├── TitleSection (中间) - 题目标题,可点击跳转
15+
└── RightSection (右侧) - 视频按钮、GitHub 链接(保持不变)
16+
```
17+
18+
### 布局方案
19+
20+
使用 CSS Grid 三列布局实现左中右对齐:
21+
- 左列:返回链接,左对齐
22+
- 中列:题目标题,居中
23+
- 右列:现有功能区,右对齐
24+
25+
## Components and Interfaces
26+
27+
### Header 组件接口更新
28+
29+
```typescript
30+
// 更新 HeaderProps 接口
31+
export interface HeaderProps {
32+
title: string // 题目标题(如 "1. 两数之和")
33+
leetcodeUrl: string // LeetCode 题目链接
34+
githubUrl: string // GitHub 仓库链接
35+
backUrl?: string // 返回链接 URL(可选,默认为 Hot 100 页面)
36+
backText?: string // 返回链接文本(可选,默认为 "返回 LeetCode Hot 100")
37+
}
38+
```
39+
40+
### 默认值
41+
42+
```typescript
43+
const DEFAULT_BACK_URL = 'https://fuck-algorithm.github.io/leetcode-hot-100/'
44+
const DEFAULT_BACK_TEXT = '← 返回 LeetCode Hot 100'
45+
```
46+
47+
## Data Models
48+
49+
本功能不涉及新的数据模型,仅扩展现有的 `HeaderProps` 接口。
50+
51+
## Correctness Properties
52+
53+
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
54+
55+
### Property 1: External links open in new tab
56+
*For any* external link (LeetCode problem link or back link) rendered by the Header component, the link element SHALL have `target="_blank"` attribute set, ensuring all external navigation opens in a new browser tab.
57+
**Validates: Requirements 1.3, 2.3, 3.1, 3.2**
58+
59+
### Property 2: External links have security attributes
60+
*For any* external link rendered by the Header component with `target="_blank"`, the link element SHALL have `rel="noopener noreferrer"` attribute set to prevent security vulnerabilities.
61+
**Validates: Requirements 3.3**
62+
63+
### Property 3: Title text preservation
64+
*For any* valid problem title string passed to the Header component, the rendered title text SHALL contain the original title string unchanged, preserving the problem number and Chinese title format.
65+
**Validates: Requirements 1.1**
66+
67+
## Error Handling
68+
69+
| 场景 | 处理方式 |
70+
|------|----------|
71+
| backUrl 为空 | 使用默认 URL |
72+
| backText 为空 | 使用默认文本 |
73+
| leetcodeUrl 无效 | 仍然渲染链接,由浏览器处理 |
74+
75+
## Testing Strategy
76+
77+
### 单元测试
78+
79+
1. 验证 Header 组件正确渲染所有元素
80+
2. 验证默认值正确应用
81+
3. 验证自定义 backUrl 和 backText 正确显示
82+
83+
### Property-Based Testing
84+
85+
使用 `fast-check` 库进行属性测试:
86+
87+
1. **链接属性测试**:验证所有外部链接都包含正确的 `target``rel` 属性
88+
2. **标题渲染测试**:验证任意有效标题字符串都能正确渲染
89+
90+
### 测试框架
91+
92+
- 测试框架:Vitest
93+
- 属性测试库:fast-check
94+
- 测试文件位置:`src/components/__tests__/Header.property.test.ts`
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Requirements Document
2+
3+
## Introduction
4+
5+
本功能旨在优化页面标题展示和导航体验,使页面标题与 LeetCode 题目标题保持一致(包含题号、中文标题),并提供便捷的导航链接。标题居中显示,左上角提供返回 LeetCode Hot 100 的链接,所有外部链接均在新页面打开。
6+
7+
## Glossary
8+
9+
- **Page_Title_System**: 页面标题展示系统,负责显示题目标题和导航链接
10+
- **Problem_Title**: LeetCode 题目标题,包含题号和中文名称(如 "1. 两数之和")
11+
- **Back_Link**: 返回链接,位于页面左上角,点击后在新页面打开 LeetCode Hot 100 页面
12+
- **LeetCode_Link**: 题目链接,点击后在新页面打开对应的 LeetCode 题目页面
13+
14+
## Requirements
15+
16+
### Requirement 1
17+
18+
**User Story:** As a user, I want the page title to match the LeetCode problem title format, so that I can easily identify which problem I am studying.
19+
20+
#### Acceptance Criteria
21+
22+
1. WHEN the page loads THEN the Page_Title_System SHALL display the Problem_Title containing the problem number and Chinese title (e.g., "1. 两数之和")
23+
2. WHEN the Problem_Title is displayed THEN the Page_Title_System SHALL center the title horizontally at the top of the page
24+
3. WHEN a user clicks on the Problem_Title THEN the Page_Title_System SHALL open the corresponding LeetCode problem page in a new browser tab
25+
26+
### Requirement 2
27+
28+
**User Story:** As a user, I want a back link to LeetCode Hot 100, so that I can easily navigate to the problem list.
29+
30+
#### Acceptance Criteria
31+
32+
1. WHEN the page loads THEN the Page_Title_System SHALL display the Back_Link in the top-left corner of the page
33+
2. WHEN the Back_Link is displayed THEN the Page_Title_System SHALL show text similar to "返回 LeetCode Hot 100"
34+
3. WHEN a user clicks on the Back_Link THEN the Page_Title_System SHALL open https://fuck-algorithm.github.io/leetcode-hot-100/ in a new browser tab
35+
36+
### Requirement 3
37+
38+
**User Story:** As a user, I want all external links to open in new tabs, so that I don't lose my current visualization progress.
39+
40+
#### Acceptance Criteria
41+
42+
1. WHEN a user clicks on the LeetCode_Link THEN the Page_Title_System SHALL open the link in a new browser tab using target="_blank"
43+
2. WHEN a user clicks on the Back_Link THEN the Page_Title_System SHALL open the link in a new browser tab using target="_blank"
44+
3. WHEN external links are rendered THEN the Page_Title_System SHALL include rel="noopener noreferrer" attribute for security
45+
46+
### Requirement 4
47+
48+
**User Story:** As a user, I want a clear visual hierarchy in the header, so that I can easily distinguish between navigation and title elements.
49+
50+
#### Acceptance Criteria
51+
52+
1. WHEN the header is displayed THEN the Page_Title_System SHALL position the Back_Link on the left side of the header
53+
2. WHEN the header is displayed THEN the Page_Title_System SHALL position the Problem_Title in the center of the header
54+
3. WHEN the header is displayed THEN the Page_Title_System SHALL maintain the existing right-side elements (video button, GitHub link)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Implementation Plan
2+
3+
- [x] 1. Update TypeScript interfaces
4+
- [x] 1.1 Add backUrl and backText optional properties to HeaderProps interface
5+
- Add `backUrl?: string` for custom back link URL
6+
- Add `backText?: string` for custom back link text
7+
- _Requirements: 2.1, 2.2, 2.3_
8+
9+
- [x] 2. Update Header component
10+
- [x] 2.1 Add default constants for back link
11+
- Define DEFAULT_BACK_URL = 'https://fuck-algorithm.github.io/leetcode-hot-100/'
12+
- Define DEFAULT_BACK_TEXT = '← 返回 LeetCode Hot 100'
13+
- _Requirements: 2.2, 2.3_
14+
- [x] 2.2 Implement back link element in Header component
15+
- Add anchor element with backUrl (or default) as href
16+
- Set target="_blank" and rel="noopener noreferrer"
17+
- Display backText (or default) as link text
18+
- _Requirements: 2.1, 2.2, 2.3, 3.2, 3.3_
19+
- [x] 2.3 Restructure Header layout for three-column design
20+
- Wrap back link in left section
21+
- Keep title in center section
22+
- Keep existing right section (video button, GitHub link)
23+
- _Requirements: 4.1, 4.2, 4.3_
24+
- [x] 2.4 Write property test for external links target attribute
25+
- **Property 1: External links open in new tab**
26+
- **Validates: Requirements 1.3, 2.3, 3.1, 3.2**
27+
- [x] 2.5 Write property test for external links security attributes
28+
- **Property 2: External links have security attributes**
29+
- **Validates: Requirements 3.3**
30+
- [x] 2.6 Write property test for title text preservation
31+
- **Property 3: Title text preservation**
32+
- **Validates: Requirements 1.1**
33+
34+
- [x] 3. Update Header CSS styles
35+
- [x] 3.1 Implement CSS Grid three-column layout
36+
- Change header display to grid with three columns
37+
- Left column: auto width, left-aligned
38+
- Center column: 1fr, center-aligned
39+
- Right column: auto width, right-aligned
40+
- _Requirements: 4.1, 4.2, 4.3_
41+
- [x] 3.2 Add styles for back link
42+
- Style back link with appropriate colors and hover effects
43+
- Ensure visual consistency with existing header design
44+
- _Requirements: 2.1, 2.2_
45+
- [x] 3.3 Adjust title styles for center alignment
46+
- Update title styles to work with grid center column
47+
- Maintain existing hover effects and link styling
48+
- _Requirements: 1.2_
49+
50+
- [x] 4. Checkpoint - Ensure all tests pass
51+
- Ensure all tests pass, ask the user if questions arise.

src/components/Header.module.css

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
.header {
2-
display: flex;
3-
justify-content: space-between;
2+
display: grid;
3+
grid-template-columns: auto 1fr auto;
44
align-items: center;
55
padding: 12px 20px;
66
background: #1a1a2e;
77
border-bottom: 1px solid #2d2d44;
88
}
99

10+
.backLink {
11+
font-size: 0.875rem;
12+
color: #888;
13+
text-decoration: none;
14+
transition: color 0.2s;
15+
white-space: nowrap;
16+
}
17+
18+
.backLink:hover {
19+
color: #4fc3f7;
20+
}
21+
1022
.title {
1123
font-size: 1.25rem;
1224
font-weight: 600;
1325
color: #fff;
1426
text-decoration: none;
1527
transition: color 0.2s;
28+
text-align: center;
29+
justify-self: center;
1630
}
1731

1832
.title:hover {

src/components/Header.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type { HeaderProps } from '../types'
33
import { getGitHubStars } from '../utils/indexedDB'
44
import styles from './Header.module.css'
55

6-
export function Header({ title, leetcodeUrl, githubUrl }: HeaderProps) {
6+
const DEFAULT_BACK_URL = 'https://fuck-algorithm.github.io/leetcode-hot-100/'
7+
const DEFAULT_BACK_TEXT = '← 返回 LeetCode Hot 100'
8+
9+
export function Header({ title, leetcodeUrl, githubUrl, backUrl, backText }: HeaderProps) {
710
const [stars, setStars] = useState<number>(0)
811
const [showVideo, setShowVideo] = useState(false)
912

@@ -24,6 +27,14 @@ export function Header({ title, leetcodeUrl, githubUrl }: HeaderProps) {
2427
return (
2528
<>
2629
<header className={styles.header}>
30+
<a
31+
href={backUrl || DEFAULT_BACK_URL}
32+
target="_blank"
33+
rel="noopener noreferrer"
34+
className={styles.backLink}
35+
>
36+
{backText || DEFAULT_BACK_TEXT}
37+
</a>
2738
<a
2839
href={leetcodeUrl}
2940
target="_blank"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { describe, it, expect } from 'vitest'
2+
import * as fc from 'fast-check'
3+
import { render, screen, cleanup } from '@testing-library/react'
4+
import { Header } from '../Header'
5+
6+
// Arbitrary for valid URLs
7+
const urlArb = fc.webUrl()
8+
9+
// Arbitrary for valid title strings (problem number + Chinese title)
10+
const titleArb = fc
11+
.tuple(fc.integer({ min: 1, max: 3000 }), fc.string({ minLength: 1, maxLength: 20 }))
12+
.map(([num, name]) => `${num}. ${name}`)
13+
14+
/**
15+
* **Feature: page-title-navigation, Property 1: External links open in new tab**
16+
* **Validates: Requirements 1.3, 2.3, 3.1, 3.2**
17+
*
18+
* For any external link (LeetCode problem link or back link) rendered by the Header component,
19+
* the link element SHALL have `target="_blank"` attribute set.
20+
*/
21+
describe('Property 1: External links open in new tab', () => {
22+
it('all external links should have target="_blank"', () => {
23+
fc.assert(
24+
fc.property(titleArb, urlArb, urlArb, urlArb, (title, leetcodeUrl, githubUrl, backUrl) => {
25+
cleanup()
26+
const { container } = render(
27+
<Header
28+
title={title}
29+
leetcodeUrl={leetcodeUrl}
30+
githubUrl={githubUrl}
31+
backUrl={backUrl}
32+
/>
33+
)
34+
35+
// Get all anchor elements
36+
const links = container.querySelectorAll('a')
37+
38+
// All links should have target="_blank"
39+
links.forEach((link) => {
40+
expect(link.getAttribute('target')).toBe('_blank')
41+
})
42+
cleanup()
43+
}),
44+
{ numRuns: 100 }
45+
)
46+
})
47+
})
48+
49+
/**
50+
* **Feature: page-title-navigation, Property 2: External links have security attributes**
51+
* **Validates: Requirements 3.3**
52+
*
53+
* For any external link rendered by the Header component with `target="_blank"`,
54+
* the link element SHALL have `rel="noopener noreferrer"` attribute set.
55+
*/
56+
describe('Property 2: External links have security attributes', () => {
57+
it('all external links with target="_blank" should have rel="noopener noreferrer"', () => {
58+
fc.assert(
59+
fc.property(titleArb, urlArb, urlArb, urlArb, (title, leetcodeUrl, githubUrl, backUrl) => {
60+
cleanup()
61+
const { container } = render(
62+
<Header
63+
title={title}
64+
leetcodeUrl={leetcodeUrl}
65+
githubUrl={githubUrl}
66+
backUrl={backUrl}
67+
/>
68+
)
69+
70+
// Get all anchor elements with target="_blank"
71+
const links = container.querySelectorAll('a[target="_blank"]')
72+
73+
// All such links should have rel="noopener noreferrer"
74+
links.forEach((link) => {
75+
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
76+
})
77+
cleanup()
78+
}),
79+
{ numRuns: 100 }
80+
)
81+
})
82+
})
83+
84+
/**
85+
* **Feature: page-title-navigation, Property 3: Title text preservation**
86+
* **Validates: Requirements 1.1**
87+
*
88+
* For any valid problem title string passed to the Header component,
89+
* the rendered title text SHALL contain the original title string unchanged.
90+
*/
91+
describe('Property 3: Title text preservation', () => {
92+
it('title text should be rendered unchanged', () => {
93+
fc.assert(
94+
fc.property(titleArb, urlArb, urlArb, (title, leetcodeUrl, githubUrl) => {
95+
cleanup()
96+
const { container } = render(
97+
<Header title={title} leetcodeUrl={leetcodeUrl} githubUrl={githubUrl} />
98+
)
99+
100+
// Find the title link element and check its text content
101+
const titleLink = container.querySelector('a[class*="title"]')
102+
expect(titleLink).not.toBeNull()
103+
expect(titleLink?.textContent).toBe(title)
104+
cleanup()
105+
}),
106+
{ numRuns: 100 }
107+
)
108+
})
109+
})

src/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ export interface HeaderProps {
109109
title: string
110110
leetcodeUrl: string
111111
githubUrl: string
112+
backUrl?: string
113+
backText?: string
112114
}
113115

114116
export interface DataInputProps {

0 commit comments

Comments
 (0)