feat(docs): add sidebar resizing and update Claude Code workflow

- Add sidebar width resizing functionality with persistence and bounds checking
- Update Claude Code documentation to reflect current command changes (remove deprecated /commit and /review, add /diff and plugin workflow)
- Remove Husky pre-commit hook boilerplate to simplify setup
- Update Vue component type annotations from TypeScript to plain JavaScript for consistency
- Regenerate sitemap with updated timestamps
This commit is contained in:
sanbuphy
2026-03-23 17:36:13 +08:00
parent c50b4377fe
commit e2796ea75d
6 changed files with 233 additions and 66 deletions
-3
View File
@@ -1,6 +1,3 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "🔍 Pre-commit checks started..."
echo ""
+146 -5
View File
@@ -3,7 +3,7 @@ import DefaultTheme from 'vitepress/theme'
import { useData, useRoute, withBase } from 'vitepress'
import TextType from './components/TextType.vue'
import GitHubStars from './components/GitHubStars.vue'
import { onMounted, ref, watch, computed } from 'vue'
import { onMounted, onBeforeUnmount, ref, watch, computed } from 'vue'
import ReadingProgress from './components/ReadingProgress.vue'
import { Setting } from '@element-plus/icons-vue'
import easyVibePaths from './data/easyVibePaths.json'
@@ -88,12 +88,99 @@ const resetLineHeight = () => {
// 目录栏(左侧 VPSidebar)收起/展开功能
// ============================================
const SIDEBAR_COLLAPSED_KEY = 'ev-sidebar-collapsed'
const SIDEBAR_WIDTH_KEY = 'ev-sidebar-width'
const DEFAULT_SIDEBAR_WIDTH = 272
const MIN_SIDEBAR_WIDTH = 160
const MAX_SIDEBAR_WIDTH = 560
const sidebarCollapsed = ref(false)
const sidebarWidth = ref(DEFAULT_SIDEBAR_WIDTH)
const sidebarResizing = ref(false)
let sidebarResizeLeft = 0
const toggleSidebar = () => {
sidebarCollapsed.value = !sidebarCollapsed.value
}
const getSidebarWidthBounds = () => {
if (typeof window === 'undefined') {
return {
min: MIN_SIDEBAR_WIDTH,
max: MAX_SIDEBAR_WIDTH
}
}
const viewportMax = window.innerWidth - 240
return {
min: MIN_SIDEBAR_WIDTH,
max: Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, viewportMax))
}
}
const clampSidebarWidth = (value) => {
const numeric = Number(value)
if (!Number.isFinite(numeric)) return DEFAULT_SIDEBAR_WIDTH
const bounds = getSidebarWidthBounds()
return Math.min(bounds.max, Math.max(bounds.min, numeric))
}
const applySidebarWidth = (width) => {
if (typeof document === 'undefined') return
document.documentElement.style.setProperty('--vp-sidebar-width', `${width}px`)
}
const setSidebarWidth = (value, shouldPersist = true) => {
const normalized = clampSidebarWidth(value)
sidebarWidth.value = normalized
applySidebarWidth(normalized)
if (shouldPersist) {
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(normalized))
}
}
const getSidebarLeftBoundary = () => {
const sidebar = document.querySelector('.VPSidebar')
if (sidebar) {
return sidebar.getBoundingClientRect().left
}
return 0
}
const updateSidebarWidthFromPointer = (clientX) => {
const nextWidth = clientX - sidebarResizeLeft
setSidebarWidth(nextWidth, false)
}
const handleSidebarResizeMove = (event) => {
if (!sidebarResizing.value) return
updateSidebarWidthFromPointer(event.clientX)
}
const stopSidebarResize = () => {
if (!sidebarResizing.value) return
sidebarResizing.value = false
document.body.classList.remove('ev-sidebar-resizing')
localStorage.setItem(SIDEBAR_WIDTH_KEY, String(sidebarWidth.value))
window.removeEventListener('pointermove', handleSidebarResizeMove)
window.removeEventListener('pointerup', stopSidebarResize)
window.removeEventListener('pointercancel', stopSidebarResize)
}
const startSidebarResize = (event) => {
if (typeof window === 'undefined') return
if (window.innerWidth < 960 || sidebarCollapsed.value) return
event.preventDefault()
sidebarResizeLeft = getSidebarLeftBoundary()
sidebarResizing.value = true
document.body.classList.add('ev-sidebar-resizing')
updateSidebarWidthFromPointer(event.clientX)
window.addEventListener('pointermove', handleSidebarResizeMove)
window.addEventListener('pointerup', stopSidebarResize)
window.addEventListener('pointercancel', stopSidebarResize)
}
const handleViewportResize = () => {
setSidebarWidth(sidebarWidth.value, false)
}
const isHomePage = computed(() => frontmatter.value.layout === 'home')
const isWelcomePage = computed(() =>
route.path === '/welcome/' ||
@@ -119,9 +206,23 @@ onMounted(() => {
document.body.classList.add('ev-sidebar-collapsed')
}
const savedSidebarWidth = localStorage.getItem(SIDEBAR_WIDTH_KEY)
if (savedSidebarWidth) {
setSidebarWidth(savedSidebarWidth, false)
} else {
setSidebarWidth(DEFAULT_SIDEBAR_WIDTH, false)
}
window.addEventListener('resize', handleViewportResize)
initOutlineAutoScroll()
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleViewportResize)
stopSidebarResize()
})
// ============================================
// Outline 侧边栏自动滚动跟随功能
// 当页面滚动时,自动滚动 outline 让当前激活项保持在可视区域
@@ -484,8 +585,15 @@ watch(sidebarCollapsed, (collapsed) => {
<div
v-if="!isHomePage && !isWelcomePage"
class="ev-sidebar-hover-area"
:class="{ collapsed: sidebarCollapsed }"
:class="{ collapsed: sidebarCollapsed, resizing: sidebarResizing }"
>
<div
v-if="!sidebarCollapsed"
class="ev-sidebar-resizer"
role="separator"
aria-orientation="vertical"
@pointerdown="startSidebarResize"
/>
<button
class="ev-sidebar-toggle-btn"
:class="{ collapsed: sidebarCollapsed }"
@@ -695,7 +803,8 @@ watch(sidebarCollapsed, (collapsed) => {
display: none;
position: fixed;
top: 0;
left: calc(var(--vp-sidebar-width, 272px) - 16px);
--ev-sidebar-divider-offset: 16px;
left: calc(var(--vp-sidebar-width, 272px) - var(--ev-sidebar-divider-offset));
width: 24px;
height: 100vh;
z-index: 30;
@@ -703,6 +812,22 @@ watch(sidebarCollapsed, (collapsed) => {
.ev-sidebar-hover-area.collapsed {
left: 0;
}
.ev-sidebar-resizer {
position: absolute;
left: var(--ev-sidebar-divider-offset);
top: 0;
width: 2px;
height: 100%;
background: var(--vp-c-divider);
opacity: 0;
cursor: col-resize;
transition: opacity 0.2s ease, background-color 0.2s ease;
}
.ev-sidebar-hover-area:hover .ev-sidebar-resizer,
.ev-sidebar-hover-area.resizing .ev-sidebar-resizer {
opacity: 1;
background: var(--vp-c-brand-1);
}
/* 分界线上的收起按钮 */
.ev-sidebar-toggle-btn {
@@ -710,7 +835,7 @@ watch(sidebarCollapsed, (collapsed) => {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 6px;
left: calc(var(--ev-sidebar-divider-offset) - 4px);
width: 18px;
height: 36px;
border: 1px solid var(--vp-c-divider);
@@ -740,6 +865,9 @@ watch(sidebarCollapsed, (collapsed) => {
opacity: 0.7;
animation: none;
}
.ev-sidebar-hover-area.resizing .ev-sidebar-toggle-btn {
opacity: 1;
}
/* 桌面端才显示按钮 */
@media (min-width: 960px) {
@@ -754,7 +882,7 @@ watch(sidebarCollapsed, (collapsed) => {
/* @1440px 时分界线按钮跟随侧边栏实际宽度 */
@media (min-width: 1440px) {
.ev-sidebar-hover-area:not(.collapsed) {
left: calc((100% - (var(--vp-layout-max-width, 1440px) - 64px)) / 2 + var(--vp-sidebar-width, 272px) - 32px - 16px);
left: calc((100% - (var(--vp-layout-max-width, 1440px) - 64px)) / 2 + var(--vp-sidebar-width, 272px) - var(--ev-sidebar-divider-offset));
}
}
@@ -804,4 +932,17 @@ watch(sidebarCollapsed, (collapsed) => {
.VPNavBar.has-sidebar .divider {
transition: padding-left 0.3s ease, transform 0.3s ease;
}
.ev-sidebar-resizing,
.ev-sidebar-resizing * {
cursor: col-resize !important;
user-select: none;
}
.ev-sidebar-resizing .VPSidebar,
.ev-sidebar-resizing .VPContent.has-sidebar,
.ev-sidebar-resizing .VPNavBar.has-sidebar .content,
.ev-sidebar-resizing .VPNavBar.has-sidebar .divider {
transition: none !important;
}
</style>
@@ -49,7 +49,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import iconChatGPT from './icons/chatgpt.svg?raw'
@@ -71,9 +71,9 @@ const aiProviders = [
const isOpen = ref(false)
const copied = ref(false)
const downloaded = ref(false)
const dropdownContainer = ref<HTMLElement | null>(null)
const dropdownContainer = ref(null)
const isRendered = ref(false)
const dropdownMenu = ref<HTMLElement | null>(null)
const dropdownMenu = ref(null)
function toggleDropdown() {
if (isOpen.value) {
@@ -120,7 +120,7 @@ function viewAsMarkdown() {
isOpen.value = false
}
function openInAI(provider: (typeof aiProviders)[0]) {
function openInAI(provider) {
const markdownUrl = resolveMarkdownPageURL(currentURL)
const prompt = `Read from ${markdownUrl} so I can ask questions about it.`
window.open(provider.url + encodeURIComponent(prompt), '_blank')
@@ -141,8 +141,8 @@ function downloadMarkdown() {
.catch((e) => console.error('❌ Error:', e))
}
function handleClickOutside(event: MouseEvent) {
if (dropdownContainer.value && !dropdownContainer.value.contains(event.target as Node)) {
function handleClickOutside(event) {
if (dropdownContainer.value && !dropdownContainer.value.contains(event.target)) {
isOpen.value = false
}
}
+5 -5
View File
@@ -594,7 +594,7 @@
</url>
<url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary/</loc>
<lastmod>2026-03-12T13:45:38+08:00</lastmod>
<lastmod>2026-03-18T07:57:16-05:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary/"/>
@@ -812,7 +812,7 @@
</url>
<url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/1.1-introduction-to-ai-ide/</loc>
<lastmod>2026-03-16T12:42:29+08:00</lastmod>
<lastmod>2026-03-18T07:57:16-05:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/1.1-introduction-to-ai-ide/"/>
@@ -828,7 +828,7 @@
</url>
<url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/1.3-integrating-ai-capabilities/</loc>
<lastmod>2026-03-16T12:42:29+08:00</lastmod>
<lastmod>2026-03-18T07:57:16-05:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-1/1.3-integrating-ai-capabilities/"/>
@@ -949,7 +949,7 @@
</url>
<url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/</loc>
<lastmod>2026-02-27T18:26:49+08:00</lastmod>
<lastmod>2026-03-18T07:59:13-05:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-2/backend/2.6-modern-cli/"/>
@@ -1070,7 +1070,7 @@
</url>
<url>
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/</loc>
<lastmod>2026-03-03T13:14:17+08:00</lastmod>
<lastmod>2026-03-18T17:22:35+08:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/"/>
@@ -366,7 +366,7 @@ https://docs.claude.com/en/docs/claude-code/slash-commands
| claude -c | 继续最近的一次会话 | `claude -c` |
| claude -r | 恢复上一段会话 | `claude -r` |
| /resume | 在当前聊天中切换回上一段会话 | `claude -c``/resume` |
| claude commit | 协助创建 Git 提交信息并提交代码 | `claude commit` |
| /plugin | 管理插件,可安装提交与审查类扩展能力 | `/plugin` |
| /init | 用 CLAUDE.md 初始化项目说明 | `/init` |
| /clear | 清空当前会话上下文,防止信息过载 | `/clear` |
| /compact | 压缩会话历史,减少上下文 token 占用 | `/compact` |
+75 -46
View File
@@ -451,47 +451,61 @@ Claude Code 的上下文窗口是有限的(通常 200K Token)。长对话会
现在我们已经完成了用户模块,接下来做订单模块
```
### 技巧 7/commit 自动提交 —— Git 工作流自动化
### 技巧 7用 Claude Code 辅助 Git 提交
`/commit` 让 Git 提交变得 effortless。Claude 会自动查看变更,生成符合规范的提交信息
在 Claude Code 里,推荐的提交流程是:先让 Claude 帮你查看 diff、整理提交信息,再由你执行标准的 Git 命令完成提交。这样既清晰,也方便你在提交前再次确认改动内容
**使用方式:**
官方文档参考:
- [Built-in commands](https://code.claude.com/docs/en/commands)
- [Discover plugins](https://code.claude.com/docs/en/discover-plugins)
**推荐工作流:**
```bash
/commit
# 1. 查看当前改动
/diff
!git status
# 2. 让 Claude 总结变更并生成提交信息
请基于当前 git diff,按照 Conventional Commits 规范生成一个 commit message
并用中文解释为什么这样分类
# 3. 你确认后,再执行标准 Git 提交
!git add -A
!git commit -m "feat(docs): update Claude Code workflow guidance"
```
**Claude 会做什么**
**这种方式的好处**
1. **查看变更**:运行 `git diff``git status`
2. **分析变更内容**:理解修改的目的和影响
3. **生成提交信息**:按照 Conventional Commits 规范
4. **执行提交**:运行 `git commit`
1. **更贴近当前官方能力**:不依赖已经移除的内置命令
2. **更透明**:你能先检查 diff 和 commit message,再决定是否提交
3. **更通用**:换到别的 AI IDE 或纯 Git 环境时,工作流依然成立
**示例输出**
**如果你想保留"一条命令提交"的体验**
```
检测到以下变更:
- src/components/UserCard.tsx - 新增用户卡片组件
- src/types/user.ts - 添加 User 类型定义
- tests/UserCard.test.tsx - 添加单元测试
建议的提交信息:
feat(components): add UserCard component with type definitions and tests
是否执行提交?(y/n/e - 编辑信息)
```
**进阶用法:**
Claude Code 现在推荐通过插件补回这类能力。例如官方插件市场示例里的 `commit-commands` 插件,会提供 `/commit-commands:commit` 这类命令。
```bash
# 自动提交,不询问
/commit --yes
# 1. 添加示例插件市场
/plugin marketplace add anthropics/claude-code
# 生成提交信息但不执行
/commit --dry-run
# 2. 安装提交工作流插件
/plugin install commit-commands@anthropics-claude-code
# 3. 重新加载插件
/reload-plugins
# 4. 使用插件命令提交
/commit-commands:commit
```
**补充说明:**
- `/commit-commands:commit` 是插件提供的命令,不是 Claude Code 当前默认内置命令
- 如果你只是想在提交前检查改动,优先使用 `/diff`,或直接让 Claude 解读 `git diff`
- 官方也已将 `/review` 标记为 deprecated;如果你需要类似能力,建议改用插件或自然语言审查工作流
### 技巧 8Shift+Tab 自动接受 —— 提高流畅度
默认情况下,Claude 修改代码前会询问你的确认。这在学习阶段很有帮助,但熟悉后可能会觉得繁琐。`Shift+Tab` 开启自动接受模式,让工作流更流畅。
@@ -1101,8 +1115,8 @@ Slash 命令是 Claude Code 的内置功能,以 `/` 开头。它们提供标
| `/plan` | 进入规划模式 | 复杂任务前先制定计划 |
| `/clear` | 清除对话历史 | 上下文混乱时重新开始 |
| `/compact` | 压缩上下文 | 长对话后节省 Token |
| `/commit` | 创建 Git 提交 | 快速提交变更 |
| `/review` | 审查未提交的变更 | 提交前检查代码 |
| `/diff` | 打开交互式 diff 视图 | 查看当前未提交改动 |
| `/plugin` | 管理插件 | 安装提交、审查等扩展能力 |
| `/context` | 查看上下文使用 | 优化 Token 消耗 |
| `/cost` | 查看本次会话费用 | 关注使用成本 |
| `/config` | 打开配置面板 | 修改设置 |
@@ -1115,9 +1129,11 @@ Slash 命令是 Claude Code 的内置功能,以 `/` 开头。它们提供标
# 完整开发工作流
/plan # 1. 制定计划
# ... 执行开发 ...
/review # 2. 查变更
/commit # 3. 提交代码
/cost # 4. 查看成本
/diff # 2. 查变更
请基于当前 diff 生成 commit message
!git add -A # 3. 暂存改动
!git commit -m "..." # 4. 提交代码
/cost # 5. 查看成本
```
### 符号系统
@@ -1219,12 +1235,15 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
**创建提交:**
```bash
# 自动提交
/commit
# 查看变更
/diff
# 提交前审查
/review
/commit
# 让 Claude 生成提交信息
请基于当前 git diff 生成一个 Conventional Commit message
# 手动提交
!git add -A
!git commit -m "..."
```
**分支操作:**
@@ -1234,7 +1253,9 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
!git checkout -b feature/user-authentication
# 完成开发后
/commit
请根据当前改动生成提交信息
!git add -A
!git commit -m "..."
!git push -u origin feature/user-authentication
```
@@ -1250,11 +1271,13 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
# 3. 运行测试
!npm test
# 4. 查变更
/review
# 4. 查变更
/diff
# 5. 提交代码
/commit
# 5. 生成并确认提交信息
请基于当前 git diff 生成一个 Conventional Commit message
!git add -A
!git commit -m "..."
# 6. 推送到远程
!git push -u origin feature/payment-integration
@@ -1391,7 +1414,9 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
!npm test
# 5. 提交修复
/commit
请根据当前 diff 生成修复类提交信息
!git add -A
!git commit -m "fix: ..."
```
**场景 2:代码审查工作流**
@@ -1411,7 +1436,8 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
优化 UserList 组件的性能
# 5. 最终审查
/review
/diff
请审查当前改动,指出潜在风险和可改进点
```
**场景 3:新功能开发工作流**
@@ -1434,10 +1460,13 @@ Claude Code 深度集成了 Git,让你可以在不离开终端的情况下完
!npm test
# 6. 代码审查
/review
/diff
请基于当前 diff 做一次代码审查
# 7. 提交代码
/commit
请生成本次功能开发的 commit message
!git add -A
!git commit -m "feat: ..."
!git push
```