Blogs: Ordner verwalten und zuweisen wie überall; leere Bibliothek zeigt den Kopf mit +
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Has been cancelled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrwnslz 2026-10-16 20:56:19 +02:00
parent 8f5be379fe
commit d870ed6f9d
3 changed files with 58 additions and 1 deletions

View file

@ -72,3 +72,19 @@ test('Abos & Quellen: one page for every subscription, reached from the feeds to
await tab.locator('[data-filter="all"]').click(); await tab.locator('[data-filter="all"]').click();
await expect(tab.locator('.src-chip.on')).toContainText('Alle'); await expect(tab.locator('.src-chip.on')).toContainText('Alle');
}); });
test('the library: a header with search, sort, layout and +, the add menu', async ({ page }) => {
await page.setViewportSize({ width: 1200, height: 800 });
await page.goto('/#books');
const list = page.locator('#book-list');
await expect(list.locator('.lib-h')).toHaveText('Bücher');
await expect(list.locator('.lib-q')).toBeVisible();
await expect(list.locator('.lib-sort')).toBeVisible();
await expect(page.locator('#book-drop-zone')).toBeHidden(); // adding is behind +
await list.locator('[data-lib="add"]').click();
await expect(page.locator('.dfo-sheet')).toContainText('E-Book-Datei');
await page.locator('.dfo-sheet [data-x]').click();
await expect(page.locator('.dfo-sheet')).toHaveCount(0);
await page.goto('/#magazines');
await expect(list.locator('.lib-h')).toHaveText('Zeitschriften');
});

View file

@ -29,6 +29,8 @@ async function loadBookList() {
return; return;
} }
if (!books.length) { if (!books.length) {
// Drawn like any list: the page's header (with +) is how a first book gets in.
if (window.__dioraLibraryV2) { renderBookList([]); return; }
listEl.innerHTML = '<p class="muted">Noch keine Bücher. Zieh oben eine .epub- oder .pdf-Datei hinein.</p>'; listEl.innerHTML = '<p class="muted">Noch keine Bücher. Zieh oben eine .epub- oder .pdf-Datei hinein.</p>';
return; return;
} }

View file

@ -309,7 +309,9 @@
(sum(list, 'unread') ? ' <small>' + sum(list, 'unread') + '</small>' : '') + '</button>'; (sum(list, 'unread') ? ' <small>' + sum(list, 'unread') + '</small>' : '') + '</button>';
h += '<div class="fd-chips">' + chip(null, 'Alle', visible) + h += '<div class="fd-chips">' + chip(null, 'Alle', visible) +
folders.map(f => chip(f, f, inFolder(f))).join('') + folders.map(f => chip(f, f, inFolder(f))).join('') +
(blogs.some(b => !(b.folders || []).length) ? chip('', 'Ohne Ordner', inFolder('')) : '') + '</div>'; (blogs.some(b => !(b.folders || []).length) ? chip('', 'Ohne Ordner', inFolder('')) : '') +
(window.DioraFolders ? '<button type="button" class="fd-chip" data-act="folders-manage" title="Ordner verwalten" aria-label="Ordner verwalten">⋯</button>' : '') +
'</div>';
} }
const main = st.blogFolder === null; const main = st.blogFolder === null;
const shown = main ? blogs : inFolder(st.blogFolder); const shown = main ? blogs : inFolder(st.blogFolder);
@ -336,6 +338,7 @@
(one.hidden ? 'Im Hauptfeed ausgeblendet' : 'Im Hauptfeed ausblenden') + '</button>' + (one.hidden ? 'Im Hauptfeed ausgeblendet' : 'Im Hauptfeed ausblenden') + '</button>' +
'<button type="button" class="fd-chip' + (one.notify ? ' on' : '') + '" data-flag="notify" data-id="' + one.id + '" title="Benachrichtigung bei neuen Posts (App)">' + '<button type="button" class="fd-chip' + (one.notify ? ' on' : '') + '" data-flag="notify" data-id="' + one.id + '" title="Benachrichtigung bei neuen Posts (App)">' +
(one.notify ? '🔔 Glocke an' : '🔕 Glocke') + '</button>' + (one.notify ? '🔔 Glocke an' : '🔕 Glocke') + '</button>' +
(window.DioraFolders ? '<button type="button" class="fd-chip" data-act="blog-folders" data-id="' + one.id + '">▤ In Ordner …</button>' : '') +
'<button type="button" class="fd-chip" data-edit-open="' + one.id + '">⚙</button></div>'; '<button type="button" class="fd-chip" data-edit-open="' + one.id + '">⚙</button></div>';
} }
if (!st.items.length) return h + '<p class="fd-note">' + (st.loading ? 'Lädt …' : (st.blogUnread ? 'Alles gelesen.' : 'Keine Artikel.')) + '</p>'; if (!st.items.length) return h + '<p class="fd-note">' + (st.loading ? 'Lädt …' : (st.blogUnread ? 'Alles gelesen.' : 'Keine Artikel.')) + '</p>';
@ -823,6 +826,42 @@
try { await api('/feeds/refresh/', {}); } catch (err) {} try { await api('/feeds/refresh/', {}); } catch (err) {}
return load(); return load();
} }
// Folders the same way as everywhere (folders.js, docs/library.md).
if (act === 'folders-manage' && window.DioraFolders) {
const done = () => loadState().catch(() => {}).then(() => (st.view === 'blogs' ? load() : render()));
window.DioraFolders.manage({
title: 'Ordner der Blogs',
folders: (st.state && st.state.folders) || [],
onRename: (name, neu) => api('/feeds/folders/rename/', {name, new: neu}).catch(() => {}).then(() => {
if (st.blogFolder !== null && st.blogFolder.toLowerCase() === name.toLowerCase()) st.blogFolder = neu;
return done();
}),
onDelete: name => api('/feeds/folders/dissolve/', {name}).catch(() => {}).then(() => {
if (st.blogFolder !== null && st.blogFolder.toLowerCase() === name.toLowerCase()) st.blogFolder = null;
return done();
}),
onOrder: order => api('/feeds/folders/order/', {order}).then(d => {
if (st.state && d && d.folders) st.state.folders = d.folders;
render();
}).catch(() => {}),
});
return;
}
if (act === 'blog-folders' && window.DioraFolders) {
const src = sources().find(x => x.id === parseInt(t.dataset.id, 10));
if (!src) return;
window.DioraFolders.assign({
title: 'Ordner für „' + src.title + '“',
folders: (st.state && st.state.folders) || [],
selected: src.folders || [],
onDone: async names => {
await api('/feeds/sources/' + src.id + '/', {folders: names}).catch(() => {});
await loadState().catch(() => {});
load();
},
});
return;
}
if (act === 'manage') { if (act === 'manage') {
// Managing every subscription has its place (Abos & Quellen, docs/sources.md). // Managing every subscription has its place (Abos & Quellen, docs/sources.md).
const nav = window.DioraNav; const nav = window.DioraNav;