Bibliothek: größerer „Zuletzt gelesen“-Bereich
Das zuletzt gelesene Buch groß (Cover, Titel, Fortschritt, Seite, „vor 2 Stunden“, Knopf „Weiterlesen“), darunter die davor gelesenen in einer Reihe mit Cover und Fortschritt. Breite Bildschirme größer, E-Ink in Schwarz-Weiß; Fortschrittsbalken auf E-Ink hell mit schwarzem Anteil. sw.js: diora-v125. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f1158e32a7
commit
c52da00405
2 changed files with 64 additions and 12 deletions
|
|
@ -199,21 +199,53 @@ function _libCard(b, label) {
|
||||||
<span class="lib-meta">${_libEsc(by)}</span></div>`;
|
<span class="lib-meta">${_libEsc(by)}</span></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- "Zuletzt gelesen": the book read last, large, and the ones read before it in a row --------
|
||||||
|
|
||||||
function _libContinueBook(books) {
|
function _libContinueBook(books) {
|
||||||
if (_lib.status !== 'all' || _lib.folder != null || _lib.q || _lib.selecting) return null;
|
if (_lib.status !== 'all' || _lib.folder != null || _lib.q || _lib.selecting) return null;
|
||||||
return _recentlyReadBooks(books).filter(x => !x.is_read && x.keyOk !== false)[0] || null;
|
return _recentlyReadBooks(books).filter(x => !x.is_read && x.keyOk !== false)[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _libContinue(b) {
|
// The others read lately (finished ones too), newest first, without the large one.
|
||||||
|
function _libRecentBooks(books, going) {
|
||||||
|
if (!going) return [];
|
||||||
|
return _recentlyReadBooks(books).filter(x => x !== going && x.keyOk !== false).slice(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "vor 5 Minuten", "gestern", "vor 3 Tagen", "am 4. Okt."
|
||||||
|
function _libAgo(iso) {
|
||||||
|
const t = Date.parse(iso || '');
|
||||||
|
if (!t) return '';
|
||||||
|
const min = Math.round((Date.now() - t) / 60000);
|
||||||
|
if (min < 2) return 'gerade eben';
|
||||||
|
if (min < 60) return `vor ${min} Minuten`;
|
||||||
|
const h = Math.round(min / 60);
|
||||||
|
const day = d => new Date(d).toDateString();
|
||||||
|
if (day(t) === day(Date.now())) return h === 1 ? 'vor einer Stunde' : `vor ${h} Stunden`;
|
||||||
|
if (day(t) === day(Date.now() - 86400000)) return 'gestern';
|
||||||
|
const days = Math.round((Date.now() - t) / 86400000);
|
||||||
|
if (days < 7) return `vor ${days} Tagen`;
|
||||||
|
return 'am ' + new Date(t).toLocaleDateString('de-DE', {day: 'numeric', month: 'short'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _libContinue(b, recent) {
|
||||||
if (!b) return '';
|
if (!b) return '';
|
||||||
const pages = b.pages ? ` · Seite ${Math.max(1, Math.round(b.pages * (b.scroll_fraction || 0)))} von ${b.pages}` : '';
|
const pages = b.pages ? ` · Seite ${Math.max(1, Math.round(b.pages * (b.scroll_fraction || 0)))} von ${b.pages}` : '';
|
||||||
return `<div class="lib-continue" data-lib-book="${b.id}">${_libCover(b, 'lib-thumb lib-thumb-l')}
|
const ago = _libAgo(b.last_read);
|
||||||
<span class="lib-text"><span class="lib-kicker">Weiterlesen</span>
|
const row = (recent || []).map(r => `<div class="lib-recent-item" data-lib-book="${r.id}">
|
||||||
<span class="lib-title">${_libEsc(b.title)}</span>
|
<span class="lib-coverbox">${_libCover(r, 'lib-cover')}${r.is_read ? '<span class="lib-badge">✓</span>' : ''}</span>
|
||||||
|
<span class="lib-bar"><span style="width:${_libPct(r)}%"></span></span>
|
||||||
|
<span class="lib-title">${_libEsc(r.title)}</span></div>`).join('');
|
||||||
|
return `<section class="lib-recent" aria-label="Zuletzt gelesen">
|
||||||
|
<div class="lib-continue" data-lib-book="${b.id}">${_libCover(b, 'lib-hero-cover')}
|
||||||
|
<span class="lib-text"><span class="lib-kicker">Weiterlesen${ago ? ' · ' + ago : ''}</span>
|
||||||
|
<span class="lib-title lib-hero-title">${_libEsc(b.title)}</span>
|
||||||
<span class="lib-meta">${_libEsc(b.author || '')}</span>
|
<span class="lib-meta">${_libEsc(b.author || '')}</span>
|
||||||
<span class="lib-bar"><span style="width:${_libPct(b)}%"></span></span>
|
<span class="lib-hero-progress"><span class="lib-bar"><span style="width:${_libPct(b)}%"></span></span>
|
||||||
<span class="lib-meta">${_libPct(b)} %${pages}</span></span>
|
<span class="lib-meta">${_libPct(b)} %${pages}</span></span>
|
||||||
<span class="lib-play" aria-hidden="true">▶</span></div>`;
|
<span class="lib-play" aria-hidden="true">▶ Weiterlesen</span></span></div>
|
||||||
|
${row ? `<div class="lib-recent-head">Zuletzt gelesen</div><div class="lib-recent-row">${row}</div>` : ''}
|
||||||
|
</section>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _libVolumeLabel(b) {
|
function _libVolumeLabel(b) {
|
||||||
|
|
@ -312,7 +344,7 @@ function _libRender(allBooks) {
|
||||||
} else {
|
} else {
|
||||||
const going = _libContinueBook(books);
|
const going = _libContinueBook(books);
|
||||||
const pool = shown.slice();
|
const pool = shown.slice();
|
||||||
h += _libContinue(going);
|
h += _libContinue(going, _libRecentBooks(books, going));
|
||||||
if (going) shown.splice(shown.indexOf(going), 1); // once, on top
|
if (going) shown.splice(shown.indexOf(going), 1); // once, on top
|
||||||
// Series as stacks, unless searching, filtering by state or choosing several.
|
// Series as stacks, unless searching, filtering by state or choosing several.
|
||||||
const stack = !magazines && !_lib.q && _lib.status === 'all' && !_lib.selecting;
|
const stack = !magazines && !_lib.q && _lib.status === 'all' && !_lib.selecting;
|
||||||
|
|
@ -697,9 +729,24 @@ function _libAddMenu() {
|
||||||
'#book-list .lib-btn, .lib-sheet-item { min-height: 36px; padding: 0 14px; border-radius: 18px; border: 1px solid var(--border-strong, #444); background: none; color: inherit; font: inherit; font-size: 14px; cursor: pointer; }',
|
'#book-list .lib-btn, .lib-sheet-item { min-height: 36px; padding: 0 14px; border-radius: 18px; border: 1px solid var(--border-strong, #444); background: none; color: inherit; font: inherit; font-size: 14px; cursor: pointer; }',
|
||||||
'.lib-sheet-item { display: block; width: 100%; text-align: left; border: none; border-radius: 0; border-bottom: 1px solid var(--border, #2a2a2a); min-height: 46px; }',
|
'.lib-sheet-item { display: block; width: 100%; text-align: left; border: none; border-radius: 0; border-bottom: 1px solid var(--border, #2a2a2a); min-height: 46px; }',
|
||||||
'.lib-danger { color: var(--accent, #e63946); }',
|
'.lib-danger { color: var(--accent, #e63946); }',
|
||||||
'#book-list .lib-continue { display: flex; align-items: center; gap: 12px; padding: 10px; margin: 0 0 16px; border: 1px solid var(--border, #2a2a2a); border-radius: 10px; background: var(--card, #0b0b0b); cursor: pointer; }',
|
|
||||||
'#book-list .lib-kicker { color: var(--accent, #e63946); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }',
|
'#book-list .lib-kicker { color: var(--accent, #e63946); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }',
|
||||||
'#book-list .lib-play { flex: none; width: 46px; height: 46px; border-radius: 50%; background: var(--accent, #e63946); color: var(--on-accent, #fff); display: flex; align-items: center; justify-content: center; font-size: 17px; }',
|
// "Zuletzt gelesen": the last book large (cover, title, progress, a button), the others in a row.
|
||||||
|
'#book-list .lib-recent { margin: 0 0 22px; }',
|
||||||
|
'#book-list .lib-continue { display: flex; align-items: stretch; gap: 16px; padding: 14px; border: 1px solid var(--border, #2a2a2a); border-radius: 12px; background: var(--card, #0b0b0b); cursor: pointer; }',
|
||||||
|
'#book-list .lib-hero-cover { flex: none; width: 104px; height: 156px; border-radius: 5px; object-fit: cover; box-shadow: 0 2px 8px rgba(0,0,0,.35); }',
|
||||||
|
'#book-list .lib-hero-cover.lib-nocover { padding: 8px; }',
|
||||||
|
'#book-list .lib-continue .lib-text { justify-content: center; gap: 4px; }',
|
||||||
|
'#book-list .lib-continue .lib-hero-title { font-size: 18px; font-weight: 700; line-height: 1.25; -webkit-line-clamp: 3; }',
|
||||||
|
'#book-list .lib-hero-progress { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }',
|
||||||
|
'#book-list .lib-hero-progress .lib-bar { height: 5px; margin-top: 0; }',
|
||||||
|
'#book-list .lib-play { align-self: flex-start; margin-top: 10px; padding: 9px 18px; border-radius: 20px; background: var(--accent, #e63946); color: var(--on-accent, #fff); font-size: 14px; font-weight: 600; white-space: nowrap; }',
|
||||||
|
'@media (min-width: 721px) { #book-list .lib-hero-cover { width: 136px; height: 204px; } #book-list .lib-continue .lib-hero-title { font-size: 22px; } #book-list .lib-continue { padding: 18px; gap: 22px; } }',
|
||||||
|
'#book-list .lib-recent-head { margin: 16px 0 8px; font-size: 13px; color: var(--muted, #9a9a9a); }',
|
||||||
|
'#book-list .lib-recent-row { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px; scrollbar-width: none; }',
|
||||||
|
'#book-list .lib-recent-row::-webkit-scrollbar { display: none; }',
|
||||||
|
'#book-list .lib-recent-item { flex: none; width: 92px; display: flex; flex-direction: column; gap: 3px; cursor: pointer; }',
|
||||||
|
'@media (min-width: 721px) { #book-list .lib-recent-item { width: 112px; } }',
|
||||||
|
'#book-list .lib-recent-item .lib-title { font-size: 12px; }',
|
||||||
'#book-list .lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 18px 14px; }',
|
'#book-list .lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 18px 14px; }',
|
||||||
'@media (max-width: 480px) { #book-list .lib-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 10px; } }',
|
'@media (max-width: 480px) { #book-list .lib-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 10px; } }',
|
||||||
'#book-list .lib-card { display: flex; flex-direction: column; gap: 3px; min-width: 0; cursor: pointer; }',
|
'#book-list .lib-card { display: flex; flex-direction: column; gap: 3px; min-width: 0; cursor: pointer; }',
|
||||||
|
|
@ -732,6 +779,11 @@ function _libAddMenu() {
|
||||||
'#book-list .lib-count { opacity: .7; }',
|
'#book-list .lib-count { opacity: .7; }',
|
||||||
'#book-list .lib-empty { color: var(--muted, #9a9a9a); font-size: 14px; }',
|
'#book-list .lib-empty { color: var(--muted, #9a9a9a); font-size: 14px; }',
|
||||||
'body.eink #book-list .lib-add, body.eink #book-list .lib-play { background: #000; color: #fff; }',
|
'body.eink #book-list .lib-add, body.eink #book-list .lib-play { background: #000; color: #fff; }',
|
||||||
|
'body.eink #book-list .lib-continue { border: 2px solid #000; background: #fff; }',
|
||||||
|
'body.eink #book-list .lib-hero-cover { box-shadow: none; border: 1px solid #000; }',
|
||||||
|
// Progress on e-ink: a light track, the part read in black (the border colour is black there).
|
||||||
|
'body.eink #book-list .lib-bar { background: #ccc; }',
|
||||||
|
'body.eink #book-list .lib-bar > span { background: #000; }',
|
||||||
'#tab-books.lib-drop { outline: 2px dashed var(--accent, #e63946); outline-offset: -6px; }',
|
'#tab-books.lib-drop { outline: 2px dashed var(--accent, #e63946); outline-offset: -6px; }',
|
||||||
'#book-list .lib-stackbox::before, #book-list .lib-stackbox::after { content: ""; position: absolute; left: 7%; right: 7%; height: 100%; border-radius: 4px; border: 1px solid var(--border-strong, #444); background: var(--panel, #161616); }',
|
'#book-list .lib-stackbox::before, #book-list .lib-stackbox::after { content: ""; position: absolute; left: 7%; right: 7%; height: 100%; border-radius: 4px; border: 1px solid var(--border-strong, #444); background: var(--panel, #161616); }',
|
||||||
'#book-list .lib-stackbox::before { top: -9px; left: 14%; right: 14%; }',
|
'#book-list .lib-stackbox::before { top: -9px; left: 14%; right: 14%; }',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* diora service worker — caches the app shell for offline use.
|
* diora service worker — caches the app shell for offline use.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE = 'diora-v124';
|
const CACHE = 'diora-v125';
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
'/static/css/app.css',
|
'/static/css/app.css',
|
||||||
'/static/js/app/01-core.js',
|
'/static/js/app/01-core.js',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue