diff --git a/README.md b/README.md index bbbb939..0182565 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ DRM 保护的 MOBI/AZW/AZW3、KFX、Topaz 以及损坏或不兼容的文件不 2. 双击目录中的 `PeopleLib.exe`。 3. 保留整个程序目录,不要只移动 exe。用户数据默认保存在程序同级的 `data/`。 -当前版本为 **2.1.1**。可在「设置」中手动检查更新,也可启用启动时自动检查。检测到新版本后,应用会打开对应的 GitHub Release 下载页,更新前请退出旧版本并覆盖程序文件,`data/` 目录无需替换。 +当前版本为 **2.1.2**。可在「设置」中手动检查更新,也可启用启动时自动检查。检测到新版本后,应用会打开对应的 GitHub Release 下载页,更新前请退出旧版本并覆盖程序文件,`data/` 目录无需替换。 ## 开发 diff --git a/package-lock.json b/package-lock.json index 9fec391..95541c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "peoplelib", - "version": "2.1.1", + "version": "2.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "peoplelib", - "version": "2.1.1", + "version": "2.1.2", "license": "MIT", "dependencies": { "foliate-js": "1.0.1", diff --git a/package.json b/package.json index 0df2d56..5cf415c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peoplelib", - "version": "2.1.1", + "version": "2.1.2", "description": "多源开放文献、电子书与本地书库客户端", "main": "main.js", "author": "peoplelib", diff --git a/src/_test/electron/library-notes.integration.js b/src/_test/electron/library-notes.integration.js index 171c0e9..b2de48d 100644 --- a/src/_test/electron/library-notes.integration.js +++ b/src/_test/electron/library-notes.integration.js @@ -569,6 +569,22 @@ app.whenReady().then(async () => { ); readerWindow.destroy(); await wait(200); + check( + '分类侧栏显示全部、未分类和各书架的真实书籍数量', + await js(`(() => { + const all = document.querySelector('#libraryTab .library-filter[data-shelf=""]'); + const uncategorized = document.querySelector( + '#libraryTab .library-filter[data-shelf="__uncategorized__"]' + ); + const shelves = Array.from(document.querySelectorAll('#libraryShelfList .library-filter')); + const research = shelves.find((button) => button.textContent.includes('研究书架')); + const review = shelves.find((button) => button.textContent.includes('待复核书架')); + return all.querySelector('.library-filter-count').textContent === '3' + && uncategorized.querySelector('.library-filter-count').textContent === '1' + && research.querySelector('.library-filter-count').textContent === '1' + && review.querySelector('.library-filter-count').textContent === '1'; + })()`) + ); check( '标签侧栏显示真实聚合计数', await js(`(() => { @@ -632,7 +648,7 @@ app.whenReady().then(async () => { await js(`(() => { const button = Array.from(document.querySelectorAll('#libraryShelfList .library-filter')) - .find((candidate) => candidate.textContent.trim() === '研究书架'); + .find((candidate) => candidate.firstElementChild.textContent.trim() === '研究书架'); button.click(); })()`); await pollJs( @@ -681,7 +697,7 @@ app.whenReady().then(async () => { !!uiShelf && await js(`(() => { const button = Array.from(document.querySelectorAll('#libraryShelfList .library-filter')) - .find((candidate) => candidate.textContent.trim() === '界面书架'); + .find((candidate) => candidate.firstElementChild.textContent.trim() === '界面书架'); return !!button && button.classList.contains('active') && document.getElementById('libStatus').textContent === '显示 0 条,共 3 条'; })()`) @@ -783,7 +799,7 @@ app.whenReady().then(async () => { await js(`(() => { const button = Array.from(document.querySelectorAll('#libraryShelfList .library-filter')) - .find((candidate) => candidate.textContent.trim() === '界面书架已重命名'); + .find((candidate) => candidate.firstElementChild.textContent.trim() === '界面书架已重命名'); button.click(); })()`); await pollJs( @@ -859,7 +875,7 @@ app.whenReady().then(async () => { // 否则批量操作会误伤用户看不到的书 await js(`(() => { const button = Array.from(document.querySelectorAll('#libraryShelfList .library-filter')) - .find((candidate) => candidate.textContent.trim() === '研究书架'); + .find((candidate) => candidate.firstElementChild.textContent.trim() === '研究书架'); button.click(); })()`); await pollJs( diff --git a/src/_test/main.test.js b/src/_test/main.test.js index 31be910..5ef3d43 100644 --- a/src/_test/main.test.js +++ b/src/_test/main.test.js @@ -73,7 +73,7 @@ test('下载校验协议,拒绝 file:// 等非 http(s)', () => { test('下载请求使用可识别且带项目地址的 User-Agent', () => { assert.match(mainSrc, /UA:\s*DL_UA[\s\S]+require\('\.\/src\/sources\/http'\)/); const httpSrc = fs.readFileSync(path.join(__dirname, '..', 'sources', 'http.js'), 'utf8'); - assert.match(httpSrc, /PeopleLib\/2\.1\.1 \(\+https:\/\/github\.com\/lofyer\/peoplelib\)/); + assert.match(httpSrc, /PeopleLib\/2\.1\.2 \(\+https:\/\/github\.com\/lofyer\/peoplelib\)/); }); test('移除书籍默认保留阅读资料,仅显式勾选时清理', () => { diff --git a/src/_test/sources.test.js b/src/_test/sources.test.js index b42a193..25c98a5 100644 --- a/src/_test/sources.test.js +++ b/src/_test/sources.test.js @@ -455,7 +455,7 @@ test('wikisource: 搜索使用整数偏移并携带可识别 User-Agent', async assert.strictEqual(result.items[0].subtitle, '中文'); assert.strictEqual(result.maxPage, 500, 'MediaWiki 搜索最多允许偏移到 10000 条'); assert.ok(request.url.includes('sroffset=20'), request.url); - assert.match(request.options.headers['User-Agent'], /PeopleLib\/2\.1\.1/); + assert.match(request.options.headers['User-Agent'], /PeopleLib\/2\.1\.2/); }); test('wikisource: 浏览按 continuation 令牌翻页', async () => { diff --git a/src/_test/store.test.js b/src/_test/store.test.js index 316f949..93964a2 100644 --- a/src/_test/store.test.js +++ b/src/_test/store.test.js @@ -613,6 +613,25 @@ test('书架 CRUD 强制唯一非空名称并返回深拷贝', () => { assert.throws(() => store.updateShelf(other.id, { name: ' 文学 ' }), /已存在/); }); +test('书架列表返回每个分类当前包含的书籍数量', () => { + freshRoot('shelf-counts'); + const research = store.addShelf('研究'); + const archive = store.addShelf('归档'); + const first = store.add({ title: 'A', shelfId: research.id }); + store.add({ title: 'B', shelfId: research.id }); + store.add({ title: '未分类' }); + + assert.deepStrictEqual( + store.listShelves().map((shelf) => [shelf.name, shelf.count]), + [['研究', 2], ['归档', 0]] + ); + store.update(first.id, { shelfId: archive.id }); + assert.deepStrictEqual( + store.listShelves().map((shelf) => [shelf.name, shelf.count]), + [['研究', 1], ['归档', 1]] + ); +}); + test('删除书架只清空条目 shelfId 且组织变更触发通知', () => { freshRoot('shelf-remove'); let changes = 0; diff --git a/src/_test/ui.test.js b/src/_test/ui.test.js index c11e3e8..9c16b2e 100644 --- a/src/_test/ui.test.js +++ b/src/_test/ui.test.js @@ -165,6 +165,22 @@ test('书库页提供可管理标签目录和整理多选下拉', () => { assert.doesNotMatch(library, /id="libraryBookTags" type="text"/); }); +test('全部书籍、未分类和每个书架都显示实时书籍数量', () => { + const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8'); + const library = fs.readFileSync(libFile, 'utf8'); + assert.match( + html, + /data-shelf="">[\s\S]*全部书籍[\s\S]*class="library-filter-count">0<\/span>/ + ); + assert.match( + html, + /data-shelf="__uncategorized__">[\s\S]*未分类[\s\S]*class="library-filter-count">0<\/span>/ + ); + assert.match(library, /items\.filter\(\(item\) => !item\.shelfId\)\.length/); + assert.match(library, /count\.textContent = String\(shelf\.count \|\| 0\)/); + assert.match(library, /filter\.append\(name, count\)/); +}); + test('下载区接入全局任务中心且完成按钮使用高对比绿色底色', () => { const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8'); const browse = fs.readFileSync(path.join(__dirname, '..', 'ui', 'views', 'browse.js'), 'utf8'); diff --git a/src/library/store.js b/src/library/store.js index ab9a5e5..8ca4b6a 100644 --- a/src/library/store.js +++ b/src/library/store.js @@ -494,7 +494,12 @@ function findBySource(sourceId, sourcePostId) { function listShelves() { load(); - return shelves.map((shelf) => ({ ...shelf })); + const counts = new Map(); + for (const item of items) { + if (!item.shelfId) continue; + counts.set(item.shelfId, (counts.get(item.shelfId) || 0) + 1); + } + return shelves.map((shelf) => ({ ...shelf, count: counts.get(shelf.id) || 0 })); } function listTags() { diff --git a/src/sources/http.js b/src/sources/http.js index ceaef65..d1d6331 100644 --- a/src/sources/http.js +++ b/src/sources/http.js @@ -1,4 +1,4 @@ -const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 PeopleLib/2.1.1 (+https://github.com/lofyer/peoplelib)'; +const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 PeopleLib/2.1.2 (+https://github.com/lofyer/peoplelib)'; const { fetch: undiciFetch, ProxyAgent } = require('undici'); // 代理配置:默认直连(空字符串)。一旦设置,所有 HTTP 请求统一走该代理。 diff --git a/src/ui/index.html b/src/ui/index.html index 161f68d..48665a2 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -69,8 +69,12 @@