const $ = selector => document.querySelector(selector);
const esc = value => String(value).replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
const findBook = id => books.find(book => book.id === id);
const uniqueIds = ids => [...new Set(ids)].filter(id => findBook(id));
const today = () => new Date().toLocaleDateString('sv-SE');
const storageKey = 'weread-demo-goals-v2';
let page = 'overview', shelfTab = 'reading', discoverTab = 'books', searchQuery = '', period = 'week';
let noteBook = 'all', noteType = 'all', goalFilter = 'all', dataState = 'ready';
let detailBook = null, bookTab = 'info', reviewFilter = '全部', selectedChapter = 'all';
let editingGoalId = null, draftIds = new Set(), draftSources = [], goals = initialGoals.map(goal => ({...goal, ids:[...goal.ids], sources:[...goal.sources]}));
let toastTimer, storageWarning = '';
// 本地数据损坏时保留原存储并恢复示例;保存前仍要求用户通过有效表单提交。
try {
const saved = localStorage.getItem(storageKey);
if (saved) {
const parsed = JSON.parse(saved);
if (!Array.isArray(parsed) || !parsed.every(goal => typeof goal.id === 'string' && typeof goal.name === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(goal.start) && /^\d{4}-\d{2}-\d{2}$/.test(goal.end) && goal.start <= goal.end && Array.isArray(goal.ids) && Array.isArray(goal.sources))) throw new Error('invalid');
goals = parsed.map(goal => ({...goal,ids:uniqueIds(goal.ids)}));
}
} catch { storageWarning = '本地目标暂时无法读取,已显示示例目标;原存储未修改。'; }
function toast(message) {
clearTimeout(toastTimer); $('#toast').textContent = message; $('#toast').classList.remove('hidden');
toastTimer = setTimeout(() => $('#toast').classList.add('hidden'), 3500);
}
function persistGoals(next) {
try { localStorage.setItem(storageKey, JSON.stringify(next)); }
catch { $('#goal-error').textContent = '浏览器无法保存数据,请允许本地存储后重试。'; return false; }
goals = next; return true;
}
function setTheme(theme) {
document.documentElement.dataset.theme = theme;
$('#theme').innerHTML = ` `;
$('#theme').setAttribute('aria-label', `切换至${theme === 'dark' ? '亮' : '暗'}色主题`);
}
try { setTheme(localStorage.getItem('weread-demo-theme') === 'dark' ? 'dark' : 'light'); } catch { setTheme('light'); }
$('#theme').onclick = () => {
const theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; setTheme(theme);
try { localStorage.setItem('weread-demo-theme', theme); } catch { toast('主题已切换,本次浏览有效。'); }
};
function cover(book, mini = false) {
if (mini) return `
${esc(book.title)} ${esc(book.author)}
`;
return `${book.en} ${book.short.replaceAll('\n',' ')} ${book.author} READING SPACE · 示意封面
`;
}
function bookCard(book) {
return `${cover(book)}${book.title} ${book.author}
${book.status === 'done' ? '已读完' : book.status === 'wish' ? '想读' : `已读 ${book.progress}%`} 评分 ${Math.round(Number(book.score)*10)}/100
`;
}
function bookRow(book, source = '') {
return `${cover(book,true)}
${book.title} ${book.author}${source ? ' · '+esc(source) : ''}
${book.status === 'done' ? '已读完' : `${book.progress}%`} 详情 ↗ `;
}
function welcome(title, subtitle, action = '') {
return `A LITTLE READING, EVERY DAY
${title} ${subtitle}
${action}
`;
}
const pageNames = {overview:'阅读概览',shelf:'我的书架',goals:'阅读目标',notes:'笔记与划线',discover:'发现好书'};
function navigate(next) {
page = next; $('#crumb').textContent = pageNames[page];
document.querySelectorAll('[data-page]').forEach(button => {button.classList.toggle('active',button.dataset.page === page);button.setAttribute('aria-current',button.dataset.page === page ? 'page' : 'false');});
renderPage();
}
function renderPage() {
const views = {overview:renderOverview,shelf:renderShelf,goals:renderGoals,notes:renderNotes,discover:renderDiscover};
$('#page-content').innerHTML = views[page]();
}
function tabs(items, selected, action) {
return `${items.map(([id,label]) => `${label} `).join('')}
`;
}
// 已读状态与目标日期无关;先按书籍 ID 去重,再计算完成率。
function goalMetrics(goal, date = today()) {
const ids = uniqueIds(goal.ids), done = ids.filter(id => findBook(id).status === 'done').length;
const status = ids.length && done === ids.length ? '已完成' : date < goal.start ? '未开始' : date > goal.end ? '已到期' : '进行中';
return {total:ids.length,done,percent:ids.length ? Math.round(done/ids.length*100):0,status};
}
function goalCard(goal) {
const result = goalMetrics(goal);
return `${result.status} 编辑 ↗
${esc(goal.name)} ${goal.start} — ${goal.end}
${goal.ids.slice(0,4).map(id=>cover(findBook(id),true)).join('')}${result.total>4?`+ ${result.total-4} 本 `:''}
${result.done} / ${result.total} 本已读完${result.percent}%
`;
}
function renderGoals() {
const list = goals.filter(goal => goalFilter === 'all' || (goalFilter === 'complete' ? goalMetrics(goal).status === '已完成' : goalMetrics(goal).status !== '已完成'));
return welcome('把想读,变成读过','几本书、一份书单,或一段想留给阅读的时间。', '+ 新建阅读目标 ')+
`YOUR NEXT CHAPTER
目标不必宏大,开始就很好。 每份清单都有自己的节奏。已经读完的书,也会成为你完成目标的一部分。
${goals.length} 个目标${goals.filter(g=>goalMetrics(g).status==='已完成').length} 个已完成${uniqueIds(goals.flatMap(g=>g.ids)).length} 本不同的书
❧ `+
tabs([['all','全部目标'],['active','未完成'],['complete','已完成']],goalFilter,'goal-filter')+`${list.map(goalCard).join('') || '
这里还没有目标,创建一份属于你的阅读清单。
'}
`;
}
function summaryGoals() {
return `
我的阅读目标 全部 ↗ ${goals.slice(0,3).map(goal=>{const m=goalMetrics(goal);return `${esc(goal.name)} ${m.done} / ${m.total} 本已读完 · ${m.status}
`;}).join('')}+ 新建阅读目标 `;
}
function renderOverview() {
const values = {week:[42,65,33,57,86,71,48],month:[48,54,65,38,70,52,64,75,68,42,46],year:[8,11,14,9,18,15,12,14,27.7],overall:[75,96,128.7]}[period];
const labels = {week:['一','二','三','四','五','六','日'],month:['1日','2日','3日','4日','5日','6日','7日','8日','9日','10日','11日'],year:['1月','2月','3月','4月','5月','6月','7月','8月','9月'],overall:['2024','2025','2026']}[period];
const max = Math.ceil(Math.max(...values)/10)*10;
const totalMinutes = values.reduce((a,b)=>a+b,0)*(period==='year'||period==='overall'?60:1);
const duration = `${Math.floor(totalMinutes/60)}小时${Math.round(totalMinutes%60)}分钟`;
const days = {week:7,month:11,year:132,overall:412}[period];
const naturalDays = {week:7,month:11,year:254,overall:985}[period];
const units = period==='year'||period==='overall'?'时':'分';
const stats = [['阅读 / 收听时长',duration,'当前所选周期','clock'],['有效阅读天数',`${days}天 `,'单日满 1 分钟计一天','flag'],['自然日均阅读',`${Math.round(totalMinutes/naturalDays)}分钟 `,'按周期自然日计算','chart'],['笔记总数','10条 ','划线 2 · 想法 2 · 书签 6','note']];
return welcome('在字里行间,遇见自己','阅读、目标与灵感,在这里慢慢积累。','+ 新建目标 ')+
`${tabs([['week','本周'],['month','本月'],['year','本年'],['overall','总计']],period,'period')}统计示例 · ${period==='overall'?'2024—2026':'2026 年'}
${stats.map(([label,value,hint,icon])=>`${label}
${value}
`).join('')}
阅读的日常 每一天,都算数 ${period==='year'?'按月':period==='overall'?'按年':'按日'} ${duration}阅读与收听合计
${[max,Math.round(max*2/3),Math.round(max/3),0].map(n=>` `).join('')}
${values.map((v,i)=>`
${labels[i]}
`).join('')}
继续阅读 全部书架 ↗ ${books.filter(b=>b.status==='reading').map(bookCard).join('')}
${summaryGoals()}✦ 留给今天的一句话
不必追赶每一页, 只需为自己翻开一本书。
— 阅间 · 原创阅读手记
读得最多 ${books.slice(0,3).map((b,i)=>`${i+1}. ${b.title} ${[2,1,1][i]}小时${[12,35,6][i]}分钟
`).join('')}偏爱的阅读方向 ${[['文学',80],['心理学',60],['科幻',40]].map(([label,val])=>`${label} 相对偏好 ${val}
`).join('')}相对偏好权重,不是阅读占比
留给自己的时段 ${Array.from({length:24},(_,i)=>` `).join('')}
06:00 18:00 次日 05:00
偏好夜间阅读 · 示例时段分布
更多阅读画像 · 作者、听书、勋章与年度回顾 偏好作者与出版方 刘慈欣 3 本 · 5小时30分钟
示例出版社 4 本
示例版权方 3 本
阅读与收听 文字阅读 80%
示例分布;正式接入只在接口返回时展示。
阅读足迹 好友排行 本周第 3 · 示例
勋章 初读 · 笔记 · 坚持
年度回顾示意 → `;
}
function listCard(list, index) {
return `${list.ids.slice(0,3).map(id=>cover(findBook(id),true)).join('')}
${String(index+1).padStart(2,'0')} ${list.name}${list.description}
${list.ids.length} 本书 · ${list.source} `;
}
function renderLists(lists) { return `${lists.map(listCard).join('') || '
没有匹配的书单,换个关键词试试。
'}
`; }
function renderShelf() {
let content;
if (shelfTab==='lists') content=renderLists(booklists.filter(l=>l.source==='我的书单'));
else if (shelfTab==='audio') content=`♫
晚间听书 · 文学选读 示例演播者 · 24 集 · 内容已完结
内容完结不代表已听完;当前不纳入按本阅读目标。
`;
else if(shelfTab==='articles') content=`▤
文章收藏 这是书架中的收藏入口。
当前书架接口不提供入口内文章列表;可在发现页搜索文章。
去搜索文章 → `;
else content=`${books.filter(b=>shelfTab==='all'||b.status===shelfTab).map(bookCard).join('')}
`;
return welcome('我的书架','8 本电子书 · 1 个有声专辑 · 1 个文章收藏入口,共 10 个书架条目。')+tabs([['all','全部电子书 8'],['reading','在读 4'],['done','已读完 2'],['wish','想读 2'],['lists','我的书单 2'],['audio','有声书 1'],['articles','文章收藏']],shelfTab,'shelf-tab')+content;
}
function openList(id) {
const list=booklists.find(l=>l.id===id);
showDetail('书单详情',`A COLLECTION OF GOOD BOOKS
${list.name} ${list.description}
${list.source} · ${list.ids.length} 本 · 示例成员
${list.ids.map(id=>bookRow(findBook(id))).join('')}
加入阅读目标
${list.source.startsWith('公开')?'公开书单成员为设计示意,完整成员获取方式待真实接口验证。
':''}`);
}
function showDetail(title, html) { $('#detail-title').textContent=title;$('#detail-content').innerHTML=html;if(!$('#detail').open)$('#detail').showModal(); }
function openGoalDetail(id) {
const goal=goals.find(g=>g.id===id), m=goalMetrics(goal);
showDetail('阅读目标',`${m.status} ${goal.start} — ${goal.end}
${esc(goal.name)} ${m.done} / ${m.total} 本已读完${m.percent}% 已完成
已读完直接计入完成,不受目标日期影响。
${goal.sources.length?`加入过的书单:${goal.sources.map(esc).join('、')} · 保存时的书目
`:''}${goal.ids.map(id=>bookRow(findBook(id))).join('')}
编辑目标与书目
`);
}
function openGoalEditor(id=null, prefill=[], sources=[]) {
$('#detail').close(); editingGoalId=id;
const goal=id?goals.find(g=>g.id===id):null;
draftIds=new Set(goal?goal.ids:prefill);draftSources=[...(goal?goal.sources:sources)];
$('#editor-title').textContent=goal?'编辑阅读目标':'新建阅读目标';$('#goal-name').value=goal?goal.name:'';
$('#goal-start').value=goal?goal.start:today();$('#goal-end').value=goal?goal.end:`${today().slice(0,4)}-12-31`;
$('#goal-end').min=$('#goal-start').value;$('#goal-end').setCustomValidity('');$('#goal-name').setCustomValidity('');
$('#picker-search').value='';$('#goal-error').textContent='';renderPicker();$('#goal-editor').showModal();
}
function selectedGoalBooks() { return uniqueIds([...draftIds]); }
function renderPicker() {
const query=$('#picker-search').value.trim().toLowerCase();
const list=books.filter(b=>`${b.title}${b.author}`.toLowerCase().includes(query));
$('#selection-count').textContent=`已选 ${draftIds.size} 本 · ${[...draftIds].filter(id=>findBook(id).status==='done').length} 本已读完`;
$('#goal-picker').innerHTML=`快捷加入书单当前书目
${booklists.filter(l=>!query||l.name.includes(query)||l.ids.some(id=>list.some(b=>b.id===id))).map(l=>`${l.ids.every(id=>draftIds.has(id))?'✓':'+'} ${l.name} · ${l.ids.length} 本 `).join('')}
${list.map(b=>`
${b.title} ${b.author} ${b.status==='done'?'已读完':b.status==='wish'?'想读':b.progress+'%'} `).join('')||'
没有匹配的书籍
'}
`;
}
function saveGoal(event) {
event.preventDefault();const name=$('#goal-name').value.trim(),start=$('#goal-start').value,end=$('#goal-end').value,ids=selectedGoalBooks();
if(!name){$('#goal-name').setCustomValidity('请输入目标名称');$('#goal-name').reportValidity();return;}
if(endg.id===editingGoalId?goal:g):[goal,...goals];
if(!persistGoals(next))return;
$('#goal-editor').close();navigate('goals');toast('阅读目标已保存,已读完的书已计入完成。');
}
function chooseTarget(ids,sources=[]) {
showDetail('加入阅读目标',`选择已有目标,或为这些书新建目标。重复书籍只加入一次。
${goals.map(g=>`
${esc(g.name)} ${g.start} — ${g.end} · ${g.ids.length} 本
加入 `).join('')}
+ 新建目标
`);
pendingBooks=ids;pendingSources=sources;
}
let pendingBooks=[],pendingSources=[];
function appendGoal(id) {
const next=goals.map(g=>g.id===id?{...g,ids:uniqueIds([...g.ids,...pendingBooks]),sources:[...new Set([...g.sources,...pendingSources])]}:g);
try { localStorage.setItem(storageKey,JSON.stringify(next)); } catch { toast('无法保存到浏览器,请允许本地存储后重试。');return; }
goals=next;renderPage();openGoalDetail(id);toast('已加入目标,重复书籍已自动去重。');
}
function renderDiscover() {
const options=[['books','书籍'],['lists','书单'],['recommend','为你推荐'],['all','综合'],['author','作者'],['novel','网文'],['audio','听书'],['fulltext','全文'],['account','公众号'],['article','文章']];
const matched=books.filter(b=>`${b.title}${b.author}`.toLowerCase().includes(searchQuery.toLowerCase()));
let content='';
if(dataState!=='ready') {
const copy={loading:['正在寻找好书','加载状态示意,点击下方按钮查看结果。'],empty:['暂时没有找到','换个关键词,或看看为你推荐。'],error:['暂时无法获取数据','连接失败示意,你的阅读目标仍保存在本地。']}[dataState];
content=`${dataState==='loading'?'LOADING':dataState==='error'?'TRY AGAIN':'NOT FOUND'}
${copy[0]} ${copy[1]}
${dataState==='error'?'重试':'查看演示结果'} `;
} else if(discoverTab==='lists') content=renderLists(booklists.filter(l=>`${l.name}${l.description}`.includes(searchQuery)));
else if(discoverTab==='books'||discoverTab==='recommend') content=`${discoverTab==='recommend'?'PICKED FOR YOUR NEXT CHAPTER
读过的书,会带你遇见下一本。 根据你对文学与心理学的兴趣推荐 · 推荐理由为示例
':''}${(discoverTab==='recommend'?matched.filter(b=>b.status==='wish'||b.id==='b0'):matched).map(bookCard).join('')||'
没有找到相关书籍,试试其他书名或作者。
'}
`;
else if(discoverTab==='author') content=`${[...new Set(matched.map(b=>b.author))].map(name=>`
`).join('')||'
没有匹配的作者。
'}
`;
else if(discoverTab==='all') content=`
书籍 ${matched.slice(0,4).map(bookCard).join('')||'
没有匹配的书籍。
'}
书单 ${renderLists(booklists.filter(l=>l.name.includes(searchQuery)))}`;
else {
const examples={novel:['星河来信','网文小说 · 示例作品','连载中 · 128 章'],audio:['夜晚的文学课','有声专辑 · 示例演播者','24 集 · 内容已完结'],fulltext:['关于阅读的一段文字','全文搜索 · 原创示例文本','阅读让日常拥有了更宽阔的边界。'],account:['一页书房','公众号 · 示例账号','记录阅读、日常与思考'],article:['如何为自己留一点阅读时间','文章 · 原创示例','2026-09-11 · 一页书房']}[discoverTab];
const matches=!searchQuery||examples.join('').includes(searchQuery);
content=matches?``:'没有匹配的演示内容,可清空关键词查看该类型示意。
';
}
return welcome('下一本,遇见什么?','寻找一本书,也发现一份恰好适合你的书单。')+
`预览状态 ${[['ready','正常'],['loading','加载中'],['empty','空结果'],['error','失败']].map(([id,label])=>`${label} `).join('')}
${tabs(options,discoverTab,'discover-tab')}${content}
搜索、推荐、书单均为设计样例,未请求微信读书服务。
`;
}
function renderNotes() {
const list=personalNotes.filter(note=>(noteBook==='all'||note.bookId===noteBook)&&(noteType==='all'||note.type===noteType));
const counts=personalNotes.filter(note=>noteBook==='all'||note.bookId===noteBook);
const bookmarks=noteBook==='all'?6:noteBook==='b0'?3:noteBook==='b1'?2:noteBook==='b2'?1:0;
return welcome('把读过的,留在心里','划线、想法与每一次停顿,都有自己的意义。','↓ 导出当前书目笔记 ')+
`${counts.length+bookmarks} 条笔记划线 ${counts.filter(n=>n.type==='highlight').length} 想法 ${counts.filter(n=>n.type==='thought').length} 书签 ${bookmarks}
${tabs([['all','全部内容'],['highlight','划线'],['thought','想法 / 个人点评'],['bookmark','书签数量']],noteType,'note-type')}${noteType==='bookmark'?`
${bookmarks} 个书签 · 当前接口只提供数量,不提供书签内容。
`:list.map(note=>`
${note.type==='thought'?'我的想法':'划线示意'} ${note.text} 《${findBook(note.bookId).title}》 · ${note.date} 原创示例文本,非书中原文
查看笔记详情 → `).join('')||'
当前筛选没有笔记。
'}
总笔记数包含书签;导出包含划线和个人想法,不含公开点评及书签内容。
`;
}
function exportNotes() {
// 导出按当前选中书目取划线和想法全集,内容类型筛选不影响导出完整性。
const notes=personalNotes.filter(note=>noteBook==='all'||note.bookId===noteBook);
const text='# 阅间笔记 · 演示数据\n\n原创示例,非书中原文。书签仅提供数量,不导出内容。\n\n'+notes.map(n=>`## ${findBook(n.bookId).title}\n\n${n.type==='thought'?'想法':'划线示意'} · ${n.date}\n\n${n.text}`).join('\n\n');
const url=URL.createObjectURL(new Blob([text],{type:'text/markdown;charset=utf-8'}));
showDetail('导出笔记', `已生成当前书目的划线与想法。可下载 Markdown,或直接选中下方内容复制;不含书签内容。
`);
$('#detail').addEventListener('close',()=>URL.revokeObjectURL(url),{once:true});
}
function openBook(id) { detailBook=id;bookTab='info';selectedChapter='all';renderBookDetail(); }
function renderBookDetail() {
const book=findBook(detailBook);
showDetail('书籍详情',`${cover(book)}
READING SPACE / BOOK
${book.title} ${book.author}
评分 ${Math.round(Number(book.score)*10)}/100 · 示例评分
${book.status==='done'?'已读完':`已读 ${book.progress}%`} · 累计 2小时18分钟
+ 加入阅读目标 ${[['info','简介'],['chapters','目录'],['notes','我的笔记'],['popular','热门划线'],['reviews','读者点评'],['similar','相似推荐']].map(([id,label])=>`${label} `).join('')}
${renderBookTab(book)}
全部为设计示例 · 目录、文字和读者资料非真实接口内容。
`);
}
function renderBookTab(book) {
if(bookTab==='info')return `${book.desc}
分类: ${book.id==='b1'?'科幻':'文学 / 人文'} · 示例
最近阅读: 2026-09-11
出版社: 示例出版社
译者 / ISBN: 暂无演示数据
字数: 约 20 万字 · 示例
出版日期: 暂无演示数据
`;
if(bookTab==='chapters')return `目录结构示意;选择章节可查看该章热门划线与热度。
${['序章 · 故事的开始','第一章 · 在旅途中','第二章 · 看见新的世界','第三章 · 留给自己的答案'].map((name,i)=>`${String(i+1).padStart(2,'0')} ${name} · 示例章节 划线热度 →
`).join('')}`;
if(bookTab==='notes') {
const notes=personalNotes.filter(n=>n.bookId===book.id);
return notes.map(n=>`${n.type==='thought'?'个人想法':'划线示意'} ${n.text} ${n.date} · 原创示例文本 `).join('')||'这本书暂无个人笔记示例。
';
}
if(bookTab==='popular')return `${selectedChapter==='all'?'全书':`示例章节 ${Number(selectedChapter)+1}`} · 热门划线最多展示 20 条 全部章节 ${[0,1,2,3].map(i=>`示例章节 ${i+1} `).join('')}
${['阅读让我们有机会,在自己的生活之外再生活一次。','那些认真思考过的问题,会在未来的某一天回应你。'].map((text,i)=>`${[1286,862][i]} 人划线 · 示例热度 ${text} 原创示例文本,非书中原文 查看 ${[12,8][i]} 条想法 →
`).join('')}章节热度提供人数与位置,原文来自热门划线;这里用示例文本说明展示方式。
`;
if(bookTab==='reviews')return tabs([['全部','全部'],['推荐','推荐'],['一般','一般'],['不行','不行'],['最新','最新']],reviewFilter,'review-filter')+publicReviews.filter(r=>reviewFilter==='全部'||reviewFilter==='最新'||r.type===reviewFilter).map(review=>`${review.name} · 示例读者 ${'★'.repeat(review.rating)}${'☆'.repeat(5-review.rating)}
${review.text}
${review.date} · 公开点评示例 查看详情 →
`).join('');
return `相似推荐示例 · 根据主题与阅读兴趣推荐
${books.filter(b=>b.id!==book.id).slice(0,4).map(bookCard).join('')}
`;
}
function openThought(text, subtitle) { showDetail('想法详情',`示例内容 ${esc(text)} ${esc(subtitle)} · 2026-09-11 这里展示单条想法的完整内容;不生成未经接口提供的评论或点赞列表。
`); }
function handleAction(button) {
const action=button.dataset.action,id=button.dataset.id;
switch(action) {
case 'page':navigate(id);break;
case 'new-goal':openGoalEditor();break;
case 'edit-goal':openGoalEditor(id);break;
case 'goal-detail':openGoalDetail(id);break;
case 'goal-filter':goalFilter=id;renderPage();break;
case 'close-detail':$('#detail').close();break;
case 'close-editor':$('#goal-editor').close();break;
case 'pick-list':{
const list=booklists.find(l=>l.id===id);list.ids.forEach(bookId=>draftIds.add(bookId));
if(!draftSources.includes(list.name))draftSources.push(list.name);renderPicker();break;
}
case 'book':openBook(id);break;
case 'list':openList(id);break;
case 'add-book':chooseTarget([id]);break;
case 'add-list':{const list=booklists.find(l=>l.id===id);chooseTarget(list.ids,[list.name]);break;}
case 'append-goal':appendGoal(id);break;
case 'new-with-selection':openGoalEditor(null,pendingBooks,pendingSources);break;
case 'shelf-tab':shelfTab=id;renderPage();break;
case 'period':period=id;renderPage();break;
case 'discover-tab':discoverTab=id;renderPage();break;
case 'retry':dataState='ready';renderPage();break;
case 'notebook':noteBook=id;renderPage();break;
case 'note-type':noteType=id;renderPage();break;
case 'export-notes':exportNotes();break;
case 'personal-note':{const note=personalNotes.find(n=>n.id===id);openThought(note.text,findBook(note.bookId).title+' · 个人笔记示例');break;}
case 'book-tab':bookTab=id;renderBookDetail();break;
case 'chapter':selectedChapter=id;bookTab='popular';renderBookDetail();break;
case 'review-filter':reviewFilter=id;renderBookDetail();break;
case 'review-detail':{const r=publicReviews.find(r=>r.id===id);openThought(r.text,r.name+' · 公开点评示例');break;}
case 'highlight-thoughts':showDetail('划线下的想法',`${id==='0'?'阅读让我们有机会,在自己的生活之外再生活一次。':'那些认真思考过的问题,会在未来的某一天回应你。'}
原创划线示意
山间来信 · 示例读者 2026-09-11
这句话让我想起,每一段阅读经历都有自己的时间。
查看完整想法 → 仅展示一条想法示例,真实内容需分页获取。
`);break;
case 'thought-detail':openThought('这句话让我想起,每一段阅读经历都有自己的时间。','山间来信 · 公开想法示例');break;
case 'author-books':searchQuery=button.dataset.name;discoverTab='books';$('#global-search').value=searchQuery;renderPage();break;
case 'article-search':searchQuery='';discoverTab='article';navigate('discover');break;
case 'search-example':openThought('这是此类搜索结果的详情布局示意。实际内容、可跳转地址及字段以未来接口返回为准。','搜索结果 · '+id);break;
case 'year-report':showDetail('年度回顾',`2026 / A YEAR IN BOOKS
翻过的每一页,都算数。 128小时42分钟 · 132 个阅读日 · 示例年度数据
偏爱文学,习惯在夜晚阅读。每一次阅读,都是给自己的时间。
年报布局示意,真实年度报告入口只使用服务端返回的链接。
`);break;
}
}
document.addEventListener('click',event=>{
const action=event.target.closest('[data-action]');if(action){handleAction(action);return;}
const nav=event.target.closest('[data-page]');if(nav){searchQuery='';$('#global-search').value='';navigate(nav.dataset.page);}
});
document.addEventListener('change',event=>{
if(event.target.matches('[data-pick-book]')){const id=event.target.dataset.pickBook;event.target.checked?draftIds.add(id):draftIds.delete(id);renderPicker();}
if(event.target.id==='data-state'){dataState=event.target.value;renderPage();}
if(event.target.id==='chapter-filter'){selectedChapter=event.target.value;renderBookDetail();}
});
$('#goal-start').onchange=()=>{$('#goal-end').min=$('#goal-start').value;};
$('#goal-name').oninput=()=>$('#goal-name').setCustomValidity('');
$('#goal-form').onsubmit=saveGoal;$('#picker-search').oninput=renderPicker;
$('#global-search').oninput=event=>{searchQuery=event.target.value.trim();dataState='ready';if(page!=='discover')discoverTab='books';navigate('discover');};
document.addEventListener('input',event=>{
if(event.target.id==='discover-search'){
const position=event.target.selectionStart;searchQuery=event.target.value;$('#global-search').value=searchQuery;renderPage();
$('#discover-search').focus();$('#discover-search').setSelectionRange(position,position);
}
});
document.addEventListener('keydown',event=>{if((event.metaKey||event.ctrlKey)&&event.key==='k'){event.preventDefault();$('#global-search').focus();}});
// 原生 dialog 提供焦点管理与 Escape 关闭;只有点击实际遮罩时才关闭。
document.querySelectorAll('dialog').forEach(dialog=>dialog.addEventListener('click',event=>{
const rect=dialog.getBoundingClientRect();if(event.target===dialog&&(event.clientXrect.right||event.clientYrect.bottom))dialog.close();
}));
navigate('overview');if(storageWarning)toast(storageWarning);