Noch keine Bücher. Mit + eine .epub- oder .pdf-Datei hinzufügen.
';
} else if (!books.length) {
h += `
${magazines ? 'Noch keine Zeitschriften. Ein Heft unter Bücher über ⋮ → „Als Zeitschrift führen“ hierher holen.' : 'Alle Bücher stehen bei den Zeitschriften.'}
`;
} else {
const going = _libContinueBook(books);
h += _libContinue(going);
if (going) shown.splice(shown.indexOf(going), 1); // once, on top
if (!shown.length && !going) h += '
Nichts gefunden.
';
else if (magazines && _lib.sort === 'recent' && !_lib.q) h += _libMagazines(shown);
else h += `
${shown.map(_libCard).join('')}
`;
}
const q = listEl.querySelector('.lib-q');
const focused = q && document.activeElement === q;
listEl.innerHTML = h;
const status = document.getElementById('book-upload-status');
if (status && status.textContent.trim()) listEl.querySelector('.lib-head').after(status);
_libChips(books);
if (focused || (_lib.searching && !_lib.q)) {
const n = listEl.querySelector('.lib-q');
if (n) { n.focus(); n.setSelectionRange(n.value.length, n.value.length); }
}
}
function _libChips(books) {
const el = document.querySelector('#book-list .lib-chips');
if (!el || !window.DioraFolders) return;
const folders = _libFolderList(books);
const none = books.filter(b => !_libFolders(b).length).length;
DioraFolders.chips(el, {
lead: [
{id: 'started', label: 'Angefangen', count: books.filter(_libStarted).length, on: _lib.status === 'started'},
{id: 'unread', label: 'Ungelesen', count: books.filter(_libUnread).length, on: _lib.status === 'unread'},
{id: 'read', label: 'Gelesen', count: books.filter(b => b.is_read).length, on: _lib.status === 'read'},
],
folders, active: _lib.folder, none: folders.length ? none : 0,
onPick(p) {
if (p && p.lead) _lib.status = _lib.status === p.lead ? 'all' : p.lead;
else _lib.folder = p;
renderBookList(_lastBookListData);
},
onManage: folders.length ? _libManage : null,
});
}
window.renderBookList = _libRender;
// ---- folders ---------------------------------------------------------------------------------------
// Into folders: one book (its folders as they are), or several (the folders they share).
function _libAssign(ids) {
const books = ids.map(id => _lastBookListData.find(b => b.id === id)).filter(Boolean);
if (!books.length || !window.DioraFolders) return;
const common = _libFolders(books[0]).filter(f => books.every(b => _libFolders(b).indexOf(f) >= 0));
DioraFolders.assign({
title: books.length === 1 ? `Ordner für „${books[0].title}“` : `Ordner für ${books.length} Bücher`,
folders: _libFolderList(_lastBookListData).map(f => f.name),
selected: common,
async onDone(picked) {
try {
for (const b of books) {
const kept = _libFolders(b).filter(f => common.indexOf(f) < 0 || picked.indexOf(f) >= 0);
const next = kept.concat(picked.filter(f => kept.indexOf(f) < 0));
if (next.join('\u0000') !== _libFolders(b).join('\u0000')) await _updateBookMeta(b.id, {folders: next});
}
} catch (e) {
customAlert('Das ließ sich nicht speichern: ' + e.message);
}
_lib.selecting = null;
renderBookList(_lastBookListData);
},
});
}
window.assignBookFolder = id => _libAssign([Number(id)]);
function _libManage() {
if (!window.DioraFolders) return;
const rewrite = async fn => {
for (const b of _lastBookListData) {
const was = _libFolders(b);
const next = fn(was).filter((f, i, all) => f && all.indexOf(f) === i);
if (next.join('\u0000') !== was.join('\u0000')) await _updateBookMeta(b.id, {folders: next});
}
renderBookList(_lastBookListData);
};
DioraFolders.manage({
title: 'Ordner der Bücher',
folders: _libFolderList(_lastBookListData).map(f => f.name),
onRename: (old, n) => {
if (_lib.folder === old) _lib.folder = n;
return rewrite(fs => fs.map(f => (f === old ? n : f))).then(() =>
_libSaveOrder((_lib.order || []).map(f => (f === old ? n : f)).filter((f, i, a) => a.indexOf(f) === i)));
},
onDelete: name => {
if (_lib.folder === name) _lib.folder = null;
return rewrite(fs => fs.filter(f => f !== name)).then(() => _libSaveOrder((_lib.order || []).filter(f => f !== name)));
},
onOrder: order => _libSaveOrder(order).then(() => renderBookList(_lastBookListData)).catch(() => {}),
});
}
// ---- a book's actions (the apps may bring their own sheet: __dioraBookSheet) -------------------------
function _libSheet(b) {
if (window.__dioraBookSheet && window.__dioraBookSheet(b) !== false) return;
const broken = typeof _brokenBooks !== 'undefined' && _brokenBooks.has(b.id);
const items = [
b.keyOk !== false && ['Lesen', () => openBook(b.id)],
['In Ordner …', () => _libAssign([b.id])],
[_isMagazine(b) ? 'Als Buch führen' : 'Als Zeitschrift führen', () => toggleBookMagazine(b.id)],
[b.is_read ? 'Als ungelesen markieren' : 'Als gelesen markieren', () => toggleBookRead(b.id, !!b.is_read)],
b.has_highlights && ['Markierungen exportieren', () => downloadBookAnnotationsFromList(b.id, null)],
['Regal-Kategorie abrufen', () => lookupBookMetadata(b.id)],
['Mehrere auswählen', () => { _lib.selecting = new Set([b.id]); renderBookList(_lastBookListData); }],
broken && ['Reparieren', () => repairBook(b.id)],
['Löschen', () => deleteBook(b.id), true],
].filter(Boolean);
const wrap = document.createElement('div');
wrap.className = 'dfo-scrim';
wrap.innerHTML = `
${_libEsc(b.title)}
${items.map((it, i) => ``).join('')}
`;
const close = () => wrap.remove();
wrap.addEventListener('click', e => {
const t = e.target.closest('[data-i]');
if (e.target === wrap || e.target.closest('[data-x]')) { close(); return; }
if (t) { close(); items[Number(t.dataset.i)][1](); }
});
document.body.appendChild(wrap);
}
// ---- events ------------------------------------------------------------------------------------------
(function () {
const listEl = $('book-list');
if (!listEl) return;
let pressTimer = 0;
let pressed = null;
// First on the list: the apps' own card clicks (native-ui.js) don't run for these cards.
listEl.addEventListener('click', e => {
const t = e.target;
const more = t.closest('[data-lib-more]');
const act = t.closest('[data-lib]');
const card = t.closest('[data-lib-book]');
if (!more && !act && !card) return;
e.stopImmediatePropagation();
if (pressed) { pressed = null; return; } // the long press already chose
if (more) {
const b = _lastBookListData.find(x => x.id === Number(more.dataset.libMore));
if (b) _libSheet(b);
return;
}
if (act) {
const a = act.dataset.lib;
if (a === 'search') { _lib.searching = true; renderBookList(_lastBookListData); }
else if (a === 'layout') {
_lib.layout = _libLayout() === 'grid' ? 'list' : 'grid';
try { localStorage.setItem('diora_lib_layout', _lib.layout); } catch (err) {}
renderBookList(_lastBookListData);
} else if (a === 'add') _libAddMenu();
else if (a === 'sel-done') { _lib.selecting = null; renderBookList(_lastBookListData); }
else if (a === 'sel-folders') _libAssign(Array.from(_lib.selecting || []));
return;
}
const id = Number(card.dataset.libBook);
if (_lib.selecting) {
if (_lib.selecting.has(id)) _lib.selecting.delete(id); else _lib.selecting.add(id);
renderBookList(_lastBookListData);
return;
}
const b = _lastBookListData.find(x => x.id === id);
if (b && b.keyOk === false) {
customAlert('Dieses Buch lässt sich mit deinem Schlüssel nicht entschlüsseln. Über ⋮ → „Schlüssel einfügen …“ den richtigen Schlüssel eintragen.');
return;
}
openBook(id);
}, true);
// A long press on a book: choosing several.
listEl.addEventListener('pointerdown', e => {
const card = e.target.closest('[data-lib-book]');
if (!card || e.target.closest('button')) return;
clearTimeout(pressTimer);
pressTimer = setTimeout(() => {
const id = Number(card.dataset.libBook);
_lib.selecting = _lib.selecting || new Set();
_lib.selecting.add(id);
pressed = id;
renderBookList(_lastBookListData);
}, 550);
});
['pointerup', 'pointercancel', 'pointerleave'].forEach(n => listEl.addEventListener(n, () => clearTimeout(pressTimer)));
listEl.addEventListener('input', e => {
if (!e.target.classList.contains('lib-q')) return;
_lib.q = e.target.value;
renderBookList(_lastBookListData);
});
listEl.addEventListener('change', e => {
if (!e.target.classList.contains('lib-sort')) return;
_lib.sort = e.target.value;
try { localStorage.setItem('diora_lib_sort', _lib.sort); } catch (err) {}
renderBookList(_lastBookListData);
});
// Back (Android): out of choosing, out of a search.
const back = window.__dioraBack;
window.__dioraBack = function () {
const shown = $('tab-books') && $('tab-books').style.display !== 'none';
if (shown && _lib.selecting) { _lib.selecting = null; renderBookList(_lastBookListData); return true; }
if (shown && (_lib.q || _lib.searching)) { _lib.q = ''; _lib.searching = false; renderBookList(_lastBookListData); return true; }
return back ? back() : false;
};
if (window.DioraCovers) {
DioraCovers.onReady(id => {
const url = DioraCovers.url({id});
document.querySelectorAll(`#book-list [data-cover-id="${id}"]`).forEach(el => {
if (el.tagName === 'IMG') { el.src = url; return; }
const img = document.createElement('img');
img.className = el.className.replace('lib-nocover', '').trim();
img.alt = '';
img.dataset.coverId = id;
img.src = url;
el.replaceWith(img);
});
});
}
window.addEventListener('resize', () => { if (_lastBookListData && _lastBookListData.length) renderBookList(_lastBookListData); });
_libLoadPrefs();
})();
function _libAddMenu() {
const wrap = document.createElement('div');
wrap.className = 'dfo-scrim';
const cloud = !(window.DioraNative && DioraNative.isReaderApp && DioraNative.isReaderApp());
wrap.innerHTML = `