Compare commits
2
Commits
c294b68e96
...
c2292da442
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2292da442 | ||
|
|
69c9ce1c2a |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "peoplelib",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "peoplelib",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"foliate-js": "1.0.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "peoplelib",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"description": "多源开放文献、电子书与本地书库客户端",
|
||||
"main": "main.js",
|
||||
"author": "peoplelib",
|
||||
|
||||
@@ -593,6 +593,35 @@ app.whenReady().then(async () => {
|
||||
});
|
||||
})()`)
|
||||
);
|
||||
const longShelfName = 'TheArtOfComputerProgramming超长分类名称';
|
||||
library.updateShelf(researchShelf.id, { name: longShelfName });
|
||||
await pollJs(
|
||||
'超长分类名称刷新',
|
||||
`Array.from(document.querySelectorAll('#libraryShelfList .library-filter'))
|
||||
.some((button) => button.firstElementChild.textContent === ${JSON.stringify(longShelfName)})`
|
||||
);
|
||||
check(
|
||||
'超长分类只省略名称并完整保留数量与操作区',
|
||||
await js(`(() => {
|
||||
const button = Array.from(document.querySelectorAll('#libraryShelfList .library-filter'))
|
||||
.find((candidate) => candidate.firstElementChild.textContent === ${JSON.stringify(longShelfName)});
|
||||
const name = button.firstElementChild;
|
||||
const count = button.querySelector('.library-filter-count');
|
||||
const actions = button.closest('.library-shelf-row').querySelector('.library-shelf-actions');
|
||||
return getComputedStyle(button).display === 'flex'
|
||||
&& getComputedStyle(name).textOverflow === 'ellipsis'
|
||||
&& name.scrollWidth > name.clientWidth
|
||||
&& count.textContent === '(1)'
|
||||
&& count.getBoundingClientRect().width > 0
|
||||
&& count.getBoundingClientRect().right <= actions.getBoundingClientRect().left + 1;
|
||||
})()`)
|
||||
);
|
||||
library.updateShelf(researchShelf.id, { name: researchShelf.name });
|
||||
await pollJs(
|
||||
'恢复研究书架名称',
|
||||
`Array.from(document.querySelectorAll('#libraryShelfList .library-filter'))
|
||||
.some((button) => button.firstElementChild.textContent === ${JSON.stringify(researchShelf.name)})`
|
||||
);
|
||||
check(
|
||||
'标签侧栏显示真实聚合计数',
|
||||
await js(`(() => {
|
||||
|
||||
@@ -16,6 +16,11 @@ test('注册表:每个源都实现完整接口', () => {
|
||||
}
|
||||
assert.ok(s.name, `${s.id} 缺 name`);
|
||||
}
|
||||
assert.strictEqual(list.find((source) => source.id === 'zlib').downloadOnDemand, true);
|
||||
assert.ok(
|
||||
list.filter((source) => source.id !== 'zlib').every((source) => source.downloadOnDemand === false),
|
||||
'只有会消耗下载额度的 Z-Library 应按点击解析'
|
||||
);
|
||||
});
|
||||
|
||||
test('注册表:开放教材与中英文维基文库已启用', () => {
|
||||
@@ -527,7 +532,9 @@ test('zlib: postId 缺 hash 时详情仍可用', async () => {
|
||||
});
|
||||
const d = await zlib.detail('123/');
|
||||
assert.strictEqual(d.title, 'B');
|
||||
assert.strictEqual(urls.length, 1, '打开详情不应顺带请求下载接口');
|
||||
assert.ok(urls[0].includes('/eapi/book/123'), urls[0]);
|
||||
assert.ok(!urls[0].includes('/file'), '打开详情消耗了下载额度: ' + urls[0]);
|
||||
assert.ok(!urls[0].includes('/eapi/book/123/?'), '缺 hash 时不该留下尾斜杠: ' + urls[0]);
|
||||
} finally {
|
||||
auth.getSession = origSession;
|
||||
|
||||
+31
-1
@@ -218,6 +218,28 @@ test('下载区接入全局任务中心且完成按钮使用高对比绿色底
|
||||
assert.match(rule[1], /color:\s*#07130b/);
|
||||
});
|
||||
|
||||
test('Z-Library 进入详情不消耗下载额度,点击下载后才解析并开始任务', () => {
|
||||
const browse = fs.readFileSync(path.join(__dirname, '..', 'ui', 'views', 'browse.js'), 'utf8');
|
||||
const openDetail = browse.slice(
|
||||
browse.indexOf('async function openDetail'),
|
||||
browse.indexOf('function sourceNameOf')
|
||||
);
|
||||
assert.match(openDetail, /if \(sourceDownloadsOnDemand\(activeSourceId\)\)/);
|
||||
assert.match(openDetail, /renderOnDemandDownload\(postId, activeSourceId, token\)/);
|
||||
assert.match(openDetail, /else\s*\{\s*loadDownload\(postId, activeSourceId, token\)/);
|
||||
|
||||
const onDemand = browse.slice(
|
||||
browse.indexOf('function renderOnDemandDownload'),
|
||||
browse.indexOf('// 当前条目的元数据')
|
||||
);
|
||||
const clickAt = onDemand.indexOf('btn.onclick = async');
|
||||
const resolveAt = onDemand.indexOf('window.api.sources.download', clickAt);
|
||||
const startAt = onDemand.indexOf('download.click()', resolveAt);
|
||||
assert.ok(clickAt >= 0 && resolveAt > clickAt, '下载地址在用户点击前被获取');
|
||||
assert.ok(startAt > resolveAt, '解析下载地址后没有立即开始真实下载');
|
||||
assert.match(onDemand, /每日免费下载额度有限,仅在点击后获取下载地址/);
|
||||
});
|
||||
|
||||
test('主窗口在设置旁提供持久化明暗主题切换', () => {
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8');
|
||||
const app = fs.readFileSync(path.join(__dirname, '..', 'ui', 'app.js'), 'utf8');
|
||||
@@ -620,7 +642,15 @@ test('侧栏行内操作不占据布局,选中高亮与静态筛选项等宽',
|
||||
assert.doesNotMatch(css, /\.library-shelf-actions\s*\{\s*display:\s*flex;\s*flex:\s*none/);
|
||||
assert.match(css, /\.library-shelf-row \.library-filter,\s*\n\.library-tag-row \.library-filter\s*\{[^}]*padding-right:\s*48px/);
|
||||
// 分类数量紧跟名称显示为「分类(数量)」,不再像标签数量那样浮到右侧
|
||||
assert.match(css, /#libraryTab \.library-filter\[data-shelf\] \.library-filter-count,\s*\n\.library-shelf-row \.library-filter-count\s*\{\s*float:\s*none/);
|
||||
assert.match(css, /\.library-shelf-row \.library-filter\s*\{[^}]*display:\s*flex/);
|
||||
const shelfName = css.match(
|
||||
/\.library-shelf-row \.library-filter > span:first-child\s*\{([^}]*)\}/
|
||||
);
|
||||
assert.ok(shelfName, '缺少长分类名称的独立省略样式');
|
||||
assert.match(shelfName[1], /min-width:\s*0/);
|
||||
assert.match(shelfName[1], /overflow:\s*hidden/);
|
||||
assert.match(shelfName[1], /text-overflow:\s*ellipsis/);
|
||||
assert.match(css, /#libraryTab \.library-filter\[data-shelf\] \.library-filter-count,\s*\n\.library-shelf-row \.library-filter-count\s*\{[^}]*flex:\s*none;[^}]*float:\s*none/);
|
||||
const actionButtons = css.match(
|
||||
/\.library-shelf-actions \.notes-icon-btn,\s*\n\.library-tag-actions \.notes-icon-btn\s*\{([^}]*)\}/
|
||||
);
|
||||
|
||||
@@ -36,7 +36,12 @@ const sources = [
|
||||
const byId = new Map(sources.map((s) => [s.id, s]));
|
||||
|
||||
function listSources() {
|
||||
return sources.map((s) => ({ id: s.id, name: s.name, supportsSearch: s.supportsSearch !== false }));
|
||||
return sources.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
supportsSearch: s.supportsSearch !== false,
|
||||
downloadOnDemand: s.downloadOnDemand === true
|
||||
}));
|
||||
}
|
||||
|
||||
function getSource(id) {
|
||||
|
||||
@@ -270,6 +270,7 @@ module.exports = {
|
||||
id: 'zlib',
|
||||
name: 'Z-Library',
|
||||
supportsSearch: true,
|
||||
downloadOnDemand: true,
|
||||
|
||||
// 无关键词时展示热门书目
|
||||
async list(page) {
|
||||
|
||||
+14
-1
@@ -714,6 +714,16 @@ body {
|
||||
.library-tag-row { position: relative; display: flex; align-items: center; }
|
||||
.library-shelf-row .library-filter,
|
||||
.library-tag-row .library-filter { flex: 1; padding-right: 48px; }
|
||||
.library-shelf-row .library-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.library-shelf-row .library-filter > span:first-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.library-shelf-actions,
|
||||
.library-tag-actions {
|
||||
position: absolute;
|
||||
@@ -741,7 +751,10 @@ body {
|
||||
}
|
||||
.library-filter-count { float: right; color: var(--text-dim); }
|
||||
#libraryTab .library-filter[data-shelf] .library-filter-count,
|
||||
.library-shelf-row .library-filter-count { float: none; }
|
||||
.library-shelf-row .library-filter-count {
|
||||
flex: none;
|
||||
float: none;
|
||||
}
|
||||
.library-content { min-width: 0; }
|
||||
.library-search {
|
||||
display: flex;
|
||||
|
||||
+42
-2
@@ -314,7 +314,11 @@ const Browse = (() => {
|
||||
}
|
||||
state.currentDetail = res.data;
|
||||
renderDetail(res.data);
|
||||
loadDownload(postId, activeSourceId, token);
|
||||
if (sourceDownloadsOnDemand(activeSourceId)) {
|
||||
renderOnDemandDownload(postId, activeSourceId, token);
|
||||
} else {
|
||||
loadDownload(postId, activeSourceId, token);
|
||||
}
|
||||
refreshAddButton();
|
||||
}
|
||||
|
||||
@@ -323,6 +327,11 @@ const Browse = (() => {
|
||||
return s ? s.name : id;
|
||||
}
|
||||
|
||||
function sourceDownloadsOnDemand(id) {
|
||||
const source = state.sourceList.find((item) => item.id === id);
|
||||
return !!(source && source.downloadOnDemand);
|
||||
}
|
||||
|
||||
function renderDetail(d) {
|
||||
const tagsHtml = (d.tags || []).map((t) => {
|
||||
const idx = t.indexOf(':');
|
||||
@@ -391,7 +400,10 @@ const Browse = (() => {
|
||||
$('dlRetry').onclick = () => loadDownload(postId, sourceId, token);
|
||||
return;
|
||||
}
|
||||
const d = res.data;
|
||||
renderDownloadResult(box, res.data);
|
||||
}
|
||||
|
||||
function renderDownloadResult(box, d) {
|
||||
let html = '';
|
||||
const files = d.files || [];
|
||||
if (files.length) {
|
||||
@@ -412,6 +424,34 @@ const Browse = (() => {
|
||||
box.querySelectorAll('[data-open]').forEach((a) => { a.onclick = (e) => { e.preventDefault(); window.api.openExternal(a.dataset.open); }; });
|
||||
}
|
||||
|
||||
function renderOnDemandDownload(postId, sourceId, token) {
|
||||
const box = $('downloadBox');
|
||||
if (!box) return;
|
||||
box.innerHTML = `<div class="dl-files"><div class="dl-file-row">
|
||||
<span class="dl-file-name">每日免费下载额度有限,仅在点击后获取下载地址</span>
|
||||
<button class="dl-btn" id="onDemandDownloadBtn">下载</button>
|
||||
</div></div>`;
|
||||
const btn = $('onDemandDownloadBtn');
|
||||
btn.onclick = async () => {
|
||||
const oldError = box.querySelector('.dl-error');
|
||||
if (oldError) oldError.remove();
|
||||
btn.disabled = true;
|
||||
btn.textContent = '获取中...';
|
||||
const res = await window.api.sources.download(sourceId, postId);
|
||||
if (!box.isConnected || token !== state.detailToken) return;
|
||||
if (!res.ok) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '重试';
|
||||
btn.title = res.error || '获取下载地址失败';
|
||||
box.insertAdjacentHTML('beforeend', `<div class="dl-error">获取失败:${escapeHtml(res.error)}</div>`);
|
||||
return;
|
||||
}
|
||||
renderDownloadResult(box, res.data);
|
||||
const download = box.querySelector('.dl-btn[data-file]');
|
||||
if (download) download.click();
|
||||
};
|
||||
}
|
||||
|
||||
// 当前条目的元数据,供下载时自动建库用
|
||||
function entryMeta() {
|
||||
const d = state.currentDetail || {};
|
||||
|
||||
Reference in New Issue
Block a user