Skip to content

Commit e519650

Browse files
sanbuphyclaude
andcommitted
Add 3 guided tour steps: notes/bookmarks, settings, QR code
- Add data-tour attributes to Sidebar for notes-saved, settings, walkinglabs - Insert tour steps between "5 Learning Paths" and "Runnable Notebooks" - Covers: note/bookmark management, theme/font settings, community QR code Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ee4ab2c commit e519650

2 files changed

Lines changed: 117 additions & 16 deletions

File tree

web/src/App.jsx

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Welcome from './components/Welcome.jsx'
88
import GuidedTour from './components/GuidedTour.jsx'
99
import SettingsPanel from './components/SettingsPanel.jsx'
1010
import ChangelogModal from './components/ChangelogModal.jsx'
11+
import WalkingLabsModal from './components/WalkingLabsModal.jsx'
1112
import { SettingsProvider } from './context/SettingsContext.jsx'
1213
import useSettings from './hooks/useSettings.js'
1314
import useTheme from './hooks/useTheme.js'
@@ -97,6 +98,7 @@ function AppContent() {
9798
const [tourStepIndex, setTourStepIndex] = useState(0)
9899
const [settingsOpen, setSettingsOpen] = useState(false)
99100
const [changelogOpen, setChangelogOpen] = useState(false)
101+
const [walkingLabsOpen, setWalkingLabsOpen] = useState(false)
100102

101103
const { settings, updateSettings } = useSettings()
102104
const { resolvedTheme, toggleTheme } = useTheme(settings.theme)
@@ -120,7 +122,7 @@ function AppContent() {
120122
const nextId = getInitialNotebookId()
121123
if (nextId === NOTES_SENTINEL) return
122124
setCurrentId((prev) => {
123-
if (prev === NOTES_SENTINEL) return prev
125+
if (prev === nextId) return prev
124126
return nextId
125127
})
126128
if (nextId && window.location.hash !== `#${nextId}`) {
@@ -190,6 +192,21 @@ function AppContent() {
190192
title: '5 大学习路径',
191193
body: '这里是整套课程的地图:基础、训练、推理、前沿、评测与部署。每张卡片概括一个阶段要解决的问题,点击后会定位到对应学习路径,方便你从全局选择下一步。',
192194
},
195+
{
196+
target: '[data-tour="notes-saved"]',
197+
title: '笔记与收藏',
198+
body: '阅读时可以随时收藏 Notebook、选中文字添加笔记或高亮标记。所有内容在这里统一管理,支持导出备份和导入恢复,换浏览器也不会丢失。',
199+
},
200+
{
201+
target: '[data-tour="settings"]',
202+
title: '个性化设置',
203+
body: '点击齿轮图标打开设置面板,可以切换浅色/深色/跟随系统主题,调整正文字号大小。设置会自动保存到本地,下次打开时保持不变。',
204+
},
205+
{
206+
target: '[data-tour="walkinglabs"]',
207+
title: '扫码加入社群',
208+
body: '点击左上角的「...」按钮,会展开 WalkingLabs 社群二维码。扫码可以加入学习交流群,和其他读者一起讨论问题、分享学习心得。',
209+
},
193210
{
194211
target: '[data-tour="notebooks"]',
195212
title: '精选可运行 Notebook',
@@ -202,10 +219,20 @@ function AppContent() {
202219
title: '一键运行',
203220
body: '顶部按钮可以把当前 Notebook 打开到 ModelScope、百度星河社区或 Colab,在线运行代码,无需本地配置。',
204221
},
222+
{
223+
target: '.viewer-title-row',
224+
title: '收藏与笔记',
225+
body: '点击标题旁的星标可收藏当前 Notebook。所有收藏和笔记在左侧边栏「笔记与收藏」中统一管理,支持导出和导入备份。',
226+
},
227+
{
228+
target: '.viewer-body',
229+
title: '选中文字即可操作',
230+
body: '阅读时选中任意文字,会弹出工具栏:复制内容、添加笔记(引用原文 + 你的想法)、黄色高亮标记重点,还能生成精美的分享卡片。',
231+
},
205232
{
206233
target: '.toc',
207234
title: '右侧大纲导航',
208-
body: '右侧大纲对应每个学习环节,点击可快速跳转。推荐阅读顺序:先看直觉理解,再看手算验证,然后运行代码,最后观察输出。',
235+
body: '右侧大纲对应每个学习环节,点击可快速跳转。大纲上的蓝色圆点表示该章节有笔记。推荐阅读顺序:先看直觉理解,再看手算验证,然后运行代码,最后观察输出。',
209236
},
210237
{
211238
target: '.code_cell',
@@ -239,6 +266,21 @@ function AppContent() {
239266
title: '5 Learning Paths',
240267
body: 'This is the course map: Foundation, Training, Inference, Frontiers, and Eval & Deploy. Each card summarizes one stage, and clicking a card takes you to that learning path so you can choose the next step from the full curriculum.',
241268
},
269+
{
270+
target: '[data-tour="notes-saved"]',
271+
title: 'Notes & Bookmarks',
272+
body: 'Bookmark any notebook, highlight text, or add notes while reading. Everything is managed here — export and import backups so you never lose your work across browsers.',
273+
},
274+
{
275+
target: '[data-tour="settings"]',
276+
title: 'Personalization',
277+
body: 'Click the gear icon to open settings. Switch between light, dark, and system themes, or adjust the reading font size. Preferences are saved automatically.',
278+
},
279+
{
280+
target: '[data-tour="walkinglabs"]',
281+
title: 'Join the Community',
282+
body: 'Click the "..." button in the top-left corner to reveal the WalkingLabs community QR code. Scan to join the discussion group and connect with other learners.',
283+
},
242284
{
243285
target: '[data-tour="notebooks"]',
244286
title: 'Runnable Notebooks',
@@ -251,10 +293,20 @@ function AppContent() {
251293
title: 'One-Click Run',
252294
body: 'The top buttons open the current notebook in ModelScope, Baidu Xinghe, or Google Colab. Run code online without any local setup.',
253295
},
296+
{
297+
target: '.viewer-title-row',
298+
title: 'Bookmarks & Notes',
299+
body: 'Click the star next to the title to bookmark. All bookmarks and notes are managed in the sidebar under "Notes & Saved", with export and import support.',
300+
},
301+
{
302+
target: '.viewer-body',
303+
title: 'Select Text to Annotate',
304+
body: 'Select any text while reading to bring up a toolbar: copy content, add a note (with quote + your thoughts), highlight key points in yellow, or generate a shareable card image.',
305+
},
254306
{
255307
target: '.toc',
256308
title: 'Table of Contents',
257-
body: 'The right sidebar outlines each section. Click to jump. Recommended order: read the intuition first, then hand calculation, then run code, then observe outputs.',
309+
body: 'The right sidebar outlines each section. Blue dots mark sections with notes. Recommended order: read the intuition first, then hand calculation, then run code, then observe outputs.',
258310
},
259311
{
260312
target: '.code_cell',
@@ -335,8 +387,10 @@ function AppContent() {
335387
onOpenNotes={handleOpenNotes}
336388
onOpenSettings={() => setSettingsOpen(true)}
337389
onOpenChangelog={() => setChangelogOpen(true)}
390+
onOpenWalkingLabs={() => setWalkingLabsOpen(true)}
338391
bookmarks={nbm.bookmarks}
339392
notes={nbm.notes}
393+
notebooksWithNotes={nbm.notebooksWithNotes}
340394
isOpen={sidebarOpen}
341395
onClose={() => setSidebarOpen(false)}
342396
/>
@@ -347,6 +401,7 @@ function AppContent() {
347401
catalog={catalog}
348402
bookmarks={nbm.bookmarks}
349403
notes={nbm.notes}
404+
notebooksWithNotes={nbm.notebooksWithNotes}
350405
getSectionNotes={nbm.getSectionNotes}
351406
exportData={nbm.exportData}
352407
importFile={nbm.importFile}
@@ -398,6 +453,12 @@ function AppContent() {
398453
lang={lang}
399454
commits={CHANGELOG_COMMITS}
400455
/>
456+
457+
<WalkingLabsModal
458+
isOpen={walkingLabsOpen}
459+
onClose={() => setWalkingLabsOpen(false)}
460+
lang={lang}
461+
/>
401462
</div>
402463
)
403464
}

web/src/components/Sidebar.jsx

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
Github,
77
GraduationCap,
88
History,
9+
Ellipsis,
910
Settings,
11+
Star,
12+
StickyNote,
1013
Sun,
1114
Moon,
1215
X,
@@ -52,7 +55,24 @@ function buildSidebarSections(catalog) {
5255
.filter(Boolean)
5356
}
5457

55-
export default function Sidebar({ catalog, currentId, lang, onLanguageChange, onSelect, onHome, onStartTour, onOpenNotes, onOpenSettings, onOpenChangelog, bookmarks = {}, notes = {}, isOpen, onClose }) {
58+
export default function Sidebar({
59+
catalog,
60+
currentId,
61+
lang,
62+
onLanguageChange,
63+
onSelect,
64+
onHome,
65+
onStartTour,
66+
onOpenNotes,
67+
onOpenSettings,
68+
onOpenChangelog,
69+
onOpenWalkingLabs,
70+
bookmarks = {},
71+
notes = {},
72+
notebooksWithNotes = new Set(),
73+
isOpen,
74+
onClose,
75+
}) {
5676
const { resolvedTheme, toggleTheme } = useSettingsContext()
5777
const [searchQuery, setSearchQuery] = useState('')
5878
const [filterMode, setFilterMode] = useState('all')
@@ -82,7 +102,7 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
82102
...section,
83103
lessons: section.lessons.filter(lesson => {
84104
if (filterMode === 'bookmarked') return !!bookmarks[lesson.id]
85-
if (filterMode === 'noted') return Object.keys(notes).some(k => k.startsWith(lesson.id + '::'))
105+
if (filterMode === 'noted') return notebooksWithNotes.has(lesson.id)
86106
if (!searchQuery) return true
87107
return lesson.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
88108
lesson.num.includes(searchQuery)
@@ -91,7 +111,7 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
91111

92112
const inFilterMode = filterMode !== 'all'
93113
const hasBookmarks = Object.keys(bookmarks).length > 0
94-
const hasNotes = Object.keys(notes).length > 0
114+
const hasNotes = notebooksWithNotes.size > 0
95115

96116
return (
97117
<aside className={`w-64 h-screen max-h-screen border-r flex flex-col justify-between shrink-0 md:sticky md:top-0 z-30 transition-transform duration-300 ${
@@ -111,9 +131,26 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
111131
<span className="brand-line brand-line-sub">Notebook</span>
112132
</span>
113133
</button>
114-
<button onClick={onClose} className="md:hidden text-[var(--text-muted)] hover:text-[var(--text-secondary)] p-1 rounded-lg">
115-
<X className="w-5 h-5" />
116-
</button>
134+
<div className="flex items-center gap-1">
135+
<button
136+
onClick={() => onOpenWalkingLabs?.()}
137+
data-tour="walkinglabs"
138+
className="sidebar-header-icon"
139+
title={lang === 'zh' ? '更多' : 'More'}
140+
aria-label={
141+
lang === 'zh' ? '打开 WalkingLabs 介绍' : 'Open WalkingLabs intro'
142+
}
143+
>
144+
<Ellipsis className="w-5 h-5" />
145+
</button>
146+
<button
147+
onClick={onClose}
148+
className="md:hidden text-[var(--text-muted)] hover:text-[var(--text-secondary)] p-1 rounded-lg"
149+
aria-label={lang === 'zh' ? '关闭侧栏' : 'Close sidebar'}
150+
>
151+
<X className="w-5 h-5" />
152+
</button>
153+
</div>
117154
</div>
118155

119156
{/* Language toggle */}
@@ -146,17 +183,19 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
146183
<button
147184
onClick={() => setFilterMode('all')}
148185
className={`sidebar-filter-tab ${filterMode === 'all' ? 'active' : ''}`}
149-
>{lang === 'zh' ? '全部' : 'All'}</button>
186+
>{lang === 'zh' ? '课程' : 'Courses'}</button>
150187
<button
151188
onClick={() => setFilterMode('bookmarked')}
152-
className={`sidebar-filter-tab ${filterMode === 'bookmarked' ? 'active' : ''}`}
189+
className={`sidebar-filter-tab sidebar-filter-tab-icon ${filterMode === 'bookmarked' ? 'active' : ''}`}
153190
disabled={!hasBookmarks}
154-
>{lang === 'zh' ? '已收藏' : 'Saved'}</button>
191+
title={lang === 'zh' ? '已收藏' : 'Saved'}
192+
><BookMarked className="w-3.5 h-3.5" /></button>
155193
<button
156194
onClick={() => setFilterMode('noted')}
157-
className={`sidebar-filter-tab ${filterMode === 'noted' ? 'active' : ''}`}
195+
className={`sidebar-filter-tab sidebar-filter-tab-icon ${filterMode === 'noted' ? 'active' : ''}`}
158196
disabled={!hasNotes}
159-
>{lang === 'zh' ? '有笔记' : 'Noted'}</button>
197+
title={lang === 'zh' ? '有笔记' : 'Noted'}
198+
><StickyNote className="w-3.5 h-3.5" /></button>
160199
</div>
161200

162201
{/* Lessons list */}
@@ -183,7 +222,7 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
183222
{section.lessons.map((lesson) => {
184223
const isSelected = currentId === lesson.id
185224
const isBm = !!bookmarks[lesson.id]
186-
const hasNote = Object.keys(notes).some(k => k.startsWith(lesson.id + '::'))
225+
const hasNote = notebooksWithNotes.has(lesson.id)
187226
return (
188227
<button
189228
key={lesson.id}
@@ -206,7 +245,6 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
206245
</div>
207246
<div className="flex items-center gap-1 shrink-0 ml-1">
208247
{isBm && <span className="sidebar-item-star" title={lang === 'zh' ? '已收藏' : 'Bookmarked'}>&#9733;</span>}
209-
{hasNote && <span className="sidebar-item-note-dot" title={lang === 'zh' ? '有笔记' : 'Has notes'}>&#183;</span>}
210248
</div>
211249
</button>
212250
)
@@ -229,6 +267,7 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
229267
</button>
230268
<button
231269
onClick={() => onOpenNotes?.()}
270+
data-tour="notes-saved"
232271
className="w-full text-left flex items-center gap-2.5 px-2.5 py-1.5 rounded-lg text-xs font-semibold text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] transition-colors"
233272
>
234273
<BookMarked className="w-3.5 h-3.5" />
@@ -256,6 +295,7 @@ export default function Sidebar({ catalog, currentId, lang, onLanguageChange, on
256295
</button>
257296
<button
258297
onClick={() => onOpenSettings?.()}
298+
data-tour="settings"
259299
className="p-1.5 rounded-lg text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] transition-colors"
260300
title={lang === 'zh' ? '设置' : 'Settings'}
261301
>

0 commit comments

Comments
 (0)