feat: 书库支持多选批量整理与移除,修正侧栏选中亮条
工具栏新增「选择」模式,卡片显示复选框,工具条提供全选、批量整理与 批量移除。多选时隐藏单卡操作并停用封面阅读入口,避免勾选途中误开 阅读器。全选只覆盖当前筛选结果,切换书架、标签或搜索后会剔除已不 可见的选中项,不对看不见的书执行批量操作。 批量标签用三态复选框:indeterminate 表示部分书持有,跳过即保持原样, 只有明确勾上或取消才统一应用。沿用单本的覆盖语义会静默抹掉其它书 已有的标签。所选书籍分属不同书架时,下拉默认「保持不变」。 侧栏行内的重命名与删除按钮改为绝对定位。此前它们虽然 opacity 为 0, 仍占据 flex 空间,使书架与标签的选中亮条比「全部书籍」窄 47px。书架 列表原为普通 block 无行间距,现与标签列表共用 .library-filter-list, 两处间距一致。
This commit is contained in:
@@ -501,6 +501,58 @@ test('书架操作对键盘焦点可见', () => {
|
||||
assert.doesNotMatch(css, /\.library-shelf-actions\s*\{\s*display:\s*none/);
|
||||
});
|
||||
|
||||
test('侧栏行内操作不占据布局,选中高亮与静态筛选项等宽', () => {
|
||||
const css = fs.readFileSync(path.join(__dirname, '..', 'ui', 'style.css'), 'utf8');
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8');
|
||||
// 留在文档流里会占掉约 47px,使书架/标签的选中亮条比「全部书籍」窄一截
|
||||
const actionsRule = css.match(
|
||||
/\.library-shelf-actions,\s*\n\.library-tag-actions\s*\{([^}]*)\}/
|
||||
);
|
||||
assert.ok(actionsRule, '书架与标签操作区应共用同一条规则');
|
||||
assert.match(actionsRule[1], /position:\s*absolute/);
|
||||
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, /\.library-filter-list\s*\{[^}]*gap:\s*1px/);
|
||||
assert.match(html, /id="libraryShelfList" class="library-filter-list"/);
|
||||
assert.match(html, /id="libraryTagList" class="library-filter-list"/);
|
||||
assert.doesNotMatch(css, /\.library-tag-list\s*\{/);
|
||||
});
|
||||
|
||||
test('书库多选提供全选、批量整理与批量移除', () => {
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8');
|
||||
const library = fs.readFileSync(libFile, 'utf8');
|
||||
assert.match(html, /id="librarySelectModeBtn"[^>]*aria-pressed="false"/);
|
||||
assert.match(html, /id="librarySelectAll"/);
|
||||
assert.match(html, /id="libraryBulkOrganizeBtn"[^>]*disabled/);
|
||||
assert.match(html, /id="libraryBulkRemoveBtn"[^>]*disabled/);
|
||||
|
||||
// 全选只覆盖当前筛选结果,且被筛掉的条目要从选中集合里剔除,
|
||||
// 否则会对用户看不见的书执行批量操作
|
||||
assert.match(library, /visibleIds = items\.map\(\(item\) => String\(item\.id\)\)/);
|
||||
assert.match(library, /if \(!visible\.has\(id\)\) selectedIds\.delete\(id\)/);
|
||||
assert.match(library, /visibleIds\.forEach\(\(id\) => selectedIds\.add\(id\)\)/);
|
||||
|
||||
// 多选时封面不能再触发阅读,否则勾选途中会误开阅读器
|
||||
assert.match(library, /const coverActs = readable && !selectMode/);
|
||||
assert.match(library, /\$\{coverActs \? 'data-act="read"/);
|
||||
|
||||
// 批量标签是增量语义:indeterminate 表示部分持有,跳过即保持原样
|
||||
assert.match(library, /if \(box\.indeterminate\) return;/);
|
||||
assert.match(library, /else if \(box\.dataset\.state !== 'none'\) strip\.push/);
|
||||
assert.match(library, /box\.indeterminate = next === 'some'/);
|
||||
assert.doesNotMatch(library, /#libraryBulkTags input\[type="checkbox"\]:checked/);
|
||||
|
||||
// 书架不一致时默认保持不变,不能把所选书籍统一挪走
|
||||
assert.match(library, /if \(shelfValue !== '__keep__'\) patch\.shelfId/);
|
||||
assert.match(library, /value="__keep__" selected>保持不变/);
|
||||
|
||||
// 批量移除沿用单本移除的两个可选项
|
||||
assert.match(library, /id="bulkDelFiles"/);
|
||||
assert.match(library, /id="bulkDelReadingData"/);
|
||||
assert.match(library, /window\.api\.library\.remove\(item\.id, choice\)/);
|
||||
});
|
||||
|
||||
test('书库长标题保持单行省略并提供完整悬浮提示', () => {
|
||||
const library = fs.readFileSync(libFile, 'utf8');
|
||||
const css = fs.readFileSync(path.join(__dirname, '..', 'ui', 'style.css'), 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user