Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
'use strict' ;
// ---------------------------------------------------------------------------
// The library (docs/library.md): Bücher and Zeitschriften as one page — a header with
// search, sort, grid/list and +, one row of chips (Angefangen · Ungelesen · Gelesen,
// then the folders, docs/library.md: tags, any number per book), one "Weiterlesen"
// card, then the books as covers (grid) or rows (list; e-ink and diora Reader start
// with it). Folders live in each book's encrypted meta (`folders`), their order in the
// encrypted library prefs (/books/prefs/). Covers come from covers.js.
// Takes over renderBookList; loading, decrypting and the book actions stay in
// 52-book-list.js.
// ---------------------------------------------------------------------------
const _LIB _EINK = ( ) => document . documentElement . classList . contains ( 'nui-reader-app' ) ||
( document . body && document . body . classList . contains ( 'eink' ) ) ;
const _lib = {
q : '' ,
searching : false ,
sort : ( ( ) => { try { return localStorage . getItem ( 'diora_lib_sort' ) || 'recent' ; } catch ( e ) { return 'recent' ; } } ) ( ) ,
layout : ( ( ) => { try { return localStorage . getItem ( 'diora_lib_layout' ) || '' ; } catch ( e ) { return '' ; } } ) ( ) ,
status : 'all' , // all | started | unread | read
folder : null , // null: all, '': without a folder, else a name
selecting : null , // a Set of book ids while choosing several
order : null , // the folders' order (decrypted prefs)
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
series : null , // the series shown (its key), or null
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
} ;
window . _ _dioraLibraryV2 = true ;
document . documentElement . classList . add ( 'lib-v2' ) ;
const _libLayout = ( ) => _lib . layout || ( _LIB _EINK ( ) ? 'list' : 'grid' ) ;
const _libFolders = b => ( typeof _foldersOfMeta === 'function' ? _foldersOfMeta ( b ) : ( b . folder ? [ b . folder ] : [ ] ) ) ;
const _libNorm = s => String ( s || '' ) . normalize ( 'NFD' ) . replace ( /[̀-ͯ]/g , '' ) . toLowerCase ( ) ;
const _libStarted = b => ! b . is _read && ( b . scroll _fraction || 0 ) > 0 ;
const _libUnread = b => ! b . is _read && ! ( b . scroll _fraction > 0 ) ;
// ---- the folders' order: encrypted prefs ------------------------------------------------
async function _libLoadPrefs ( ) {
try { _lib . order = JSON . parse ( localStorage . getItem ( 'diora_lib_order' ) || 'null' ) ; } catch ( e ) { }
try {
const r = await fetch ( '/books/prefs/' , { cache : 'no-store' } ) ;
const d = r . ok ? await r . json ( ) : null ;
if ( d && d . prefs _ct ) {
const plain = await decryptBytes ( await getOrCreateEncKey ( ) , d . prefs _iv , d . prefs _ct ) ;
const prefs = JSON . parse ( new TextDecoder ( ) . decode ( plain ) ) ;
_lib . order = Array . isArray ( prefs . folder _order ) ? prefs . folder _order : [ ] ;
try { localStorage . setItem ( 'diora_lib_order' , JSON . stringify ( _lib . order ) ) ; } catch ( e ) { }
if ( _lastBookListData && _lastBookListData . length ) renderBookList ( _lastBookListData ) ;
}
} catch ( e ) { /* offline: the order kept here */ }
}
async function _libSaveOrder ( order ) {
_lib . order = order ;
try { localStorage . setItem ( 'diora_lib_order' , JSON . stringify ( order ) ) ; } catch ( e ) { }
const enc = await encryptBytes ( await getOrCreateEncKey ( ) , new TextEncoder ( ) . encode ( JSON . stringify ( { folder _order : order } ) ) ) ;
await fetch ( '/books/prefs/' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-CSRFToken' : getCsrfToken ( ) } ,
body : JSON . stringify ( { prefs _ct : enc . ciphertext , prefs _iv : enc . iv } ) } ) ;
}
// Every folder of the books shown (this section), in the user's order.
function _libFolderList ( books ) {
const counts = new Map ( ) ;
books . forEach ( b => _libFolders ( b ) . forEach ( f => counts . set ( f , ( counts . get ( f ) || 0 ) + 1 ) ) ) ;
const order = _lib . order || [ ] ;
const rank = n => { const i = order . indexOf ( n ) ; return i < 0 ? 1e6 : i ; } ;
return Array . from ( counts . keys ( ) ) . sort ( ( a , b ) => rank ( a ) - rank ( b ) || a . localeCompare ( b , 'de' ) )
. map ( n => ( { name : n , count : counts . get ( n ) } ) ) ;
}
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
// ---- series (docs/library.md): set per book in its encrypted meta (series, seriesIndex),
// read from the EPUB on upload, else guessed from the title ("Der Zauberberg. Erster Band").
// series '' means: none, on purpose.
const _LIB _ORD = [ 'erst' , 'zweit' , 'dritt' , 'viert' , 'fünft' , 'sechst' , 'siebt' , 'acht' , 'neunt' , 'zehnt' , 'elft' , 'zwölft' ] ;
const _LIB _ORD _EN = [ 'first' , 'second' , 'third' , 'fourth' , 'fifth' , 'sixth' , 'seventh' , 'eighth' , 'ninth' , 'tenth' ] ;
const _LIB _ROMAN = { i : 1 , ii : 2 , iii : 3 , iv : 4 , v : 5 , vi : 6 , vii : 7 , viii : 8 , ix : 9 , x : 10 } ;
function _libVolumeNumber ( text ) {
const t = String ( text || '' ) . toLowerCase ( ) ;
const d = /(\d+(?:[.,]\d+)?)/ . exec ( t ) ;
if ( d ) return parseFloat ( d [ 1 ] . replace ( ',' , '.' ) ) ;
for ( const w of t . split ( /[^a-zäöüß]+/ ) ) {
if ( _LIB _ROMAN [ w ] ) return _LIB _ROMAN [ w ] ;
for ( let i = 0 ; i < _LIB _ORD . length ; i ++ ) if ( w . indexOf ( _LIB _ORD [ i ] ) === 0 ) return i + 1 ;
for ( let i = 0 ; i < _LIB _ORD _EN . length ; i ++ ) if ( w === _LIB _ORD _EN [ i ] ) return i + 1 ;
}
return null ;
}
function _libSeriesGuess ( title ) {
const t = String ( title || '' ) ;
const m = /^(.+?)\s+[–—-]\s+(.+)$/ . exec ( t ) || /^(.+?):\s+(.+)$/ . exec ( t ) || /^(.+?)[.,]\s+(.+)$/ . exec ( t ) ;
if ( ! m || ! /\b(band|bd\.|teil|buch|folge|staffel|vol\.?|volume|part|book)\b/i . test ( m [ 2 ] ) || /bände|bundle/i . test ( m [ 2 ] ) ) return null ;
return { name : m [ 1 ] . trim ( ) , index : _libVolumeNumber ( m [ 2 ] ) , auto : true } ;
}
// A book's series: {name, index, auto} or null.
function _libSeries ( b ) {
if ( b . series === '' ) return null ;
if ( b . series ) {
const ix = typeof b . seriesIndex === 'number' ? b . seriesIndex : ( _libSeriesGuess ( b . title ) || { } ) . index ;
return { name : b . series , index : ix == null ? null : ix , auto : false } ;
}
return _libSeriesGuess ( b . title ) ;
}
const _libSeriesKey = s => s . name . toLowerCase ( ) . replace ( /\s+/g , ' ' ) . trim ( ) ;
function _libVolumeSort ( a , b ) {
const sa = _libSeries ( a ) , sb = _libSeries ( b ) ;
const ia = sa && sa . index != null ? sa . index : 1e6 , ib = sb && sb . index != null ? sb . index : 1e6 ;
return ia - ib || a . title . localeCompare ( b . title , 'de' , { numeric : true } ) ;
}
// Every series in the library: key -> {name, books (in order)}.
function _libAllSeries ( books ) {
const map = new Map ( ) ;
books . forEach ( b => {
const s = _libSeries ( b ) ;
if ( ! s ) return ;
const k = _libSeriesKey ( s ) ;
if ( ! map . has ( k ) ) map . set ( k , { key : k , name : s . name , books : [ ] } ) ;
map . get ( k ) . books . push ( b ) ;
} ) ;
map . forEach ( g => g . books . sort ( _libVolumeSort ) ) ;
return map ;
}
2026-10-19 14:27:14 +02:00
// ---- editions: one work as PDF and EPUB (or two translations …), shown once ----------------------
// Books with the same `work` key in their meta are editions of one work. The list shows one of
// them: the one read last, else an EPUB, else the first added; the others are behind "Ausgaben".
const _libWork = b => ( b && typeof b . work === 'string' && b . work ) || '' ;
function _libEditionGroups ( books ) {
const map = new Map ( ) ;
books . forEach ( b => {
const w = _libWork ( b ) ;
if ( ! w ) return ;
if ( ! map . has ( w ) ) map . set ( w , [ ] ) ;
map . get ( w ) . push ( b ) ;
} ) ;
map . forEach ( ( list , w ) => { if ( list . length < 2 ) map . delete ( w ) ; } ) ;
return map ;
}
function _libEditionLead ( list ) {
return list . slice ( ) . sort ( ( a , b ) => ( _bookRecency ( b ) - _bookRecency ( a ) ) ||
( ( a . type === 'epub' ? 0 : 1 ) - ( b . type === 'epub' ? 0 : 1 ) ) || a . id - b . id ) [ 0 ] ;
}
// The books with every work once; _lib.editions: the shown book's id -> all its editions.
function _libCollapse ( books ) {
const groups = _libEditionGroups ( books ) ;
_lib . editions = new Map ( ) ;
if ( ! groups . size ) return books ;
const hide = new Set ( ) ;
groups . forEach ( list => {
const lead = _libEditionLead ( list ) ;
_lib . editions . set ( lead . id , list ) ;
list . forEach ( b => { if ( b !== lead ) hide . add ( b . id ) ; } ) ;
} ) ;
return books . filter ( b => ! hide . has ( b . id ) ) ;
}
function _libEditionsOf ( b ) {
const w = _libWork ( b ) ;
return w ? ( _lastBookListData || [ ] ) . filter ( x => _libWork ( x ) === w ) : [ b ] ;
}
const _libFormat = b => ( b . type || 'epub' ) . toUpperCase ( ) ;
function _libFormats ( list ) {
return Array . from ( new Set ( list . map ( _libFormat ) ) ) . join ( ' · ' ) ;
}
async function _libLinkEditions ( ids ) {
const books = ids . map ( id => _lastBookListData . find ( b => b . id === id ) ) . filter ( Boolean ) ;
if ( books . length < 2 ) return ;
const key = books . map ( _libWork ) . filter ( Boolean ) [ 0 ] ||
'w' + Date . now ( ) . toString ( 36 ) + Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) ;
try {
for ( const b of books ) if ( _libWork ( b ) !== key ) await _updateBookMeta ( b . id , { work : key } ) ;
renderBookList ( _lastBookListData ) ;
} catch ( e ) { customAlert ( 'Das ließ sich nicht speichern: ' + e . message ) ; }
}
async function _libUnlinkEdition ( b ) {
const rest = _libEditionsOf ( b ) . filter ( x => x !== b ) ;
try {
await _updateBookMeta ( b . id , { work : '' } ) ;
// One left on its own is no longer an edition of anything.
if ( rest . length === 1 ) await _updateBookMeta ( rest [ 0 ] . id , { work : '' } ) ;
renderBookList ( _lastBookListData ) ;
} catch ( e ) { customAlert ( 'Das ließ sich nicht speichern: ' + e . message ) ; }
}
// "Ausgaben": the editions of a book, each to open or to set apart; or, for a book with none,
// another book to join it with (the ones with a similar title first).
function _libEditionsSheet ( b ) {
const eds = _libEditionsOf ( b ) ;
if ( eds . length > 1 ) {
const rows = eds . slice ( ) . sort ( ( x , y ) => x . id - y . id ) . map ( x => ` <div class="lib-ed-row">
< button type = "button" class = "lib-sheet-item" data - open = "${x.id}" > < b > $ { _libEsc ( _libFormat ( x ) ) } < / b > · $ { _ l i b E s c ( _ l i b S t a t e ( x ) ) }
< span class = "lib-meta" > $ { _libEsc ( x . title ) } $ { x . last _read ? ' · ' + _libEsc ( _libAgo ( x . last _read ) ) : '' } < / s p a n > < / b u t t o n >
< button type = "button" class = "lib-btn" data - unlink = "${x.id}" > Lösen < / b u t t o n > < / d i v > ` ) . j o i n ( ' ' ) ;
const wrap = _libSheetBox ( ` Ausgaben von „ ${ b . title } “ ` , ` <div class="dfo-list"> ${ rows } </div>
< div class = "dfo-actions" > < button type = "button" class = "dfo-btn" data - add = "1" > Weitere Ausgabe verbinden … < / b u t t o n > < / d i v > ` ) ;
wrap . addEventListener ( 'click' , e => {
const o = e . target . closest ( '[data-open]' ) , u = e . target . closest ( '[data-unlink]' ) , a = e . target . closest ( '[data-add]' ) ;
if ( o ) { wrap . remove ( ) ; openBook ( Number ( o . dataset . open ) ) ; }
else if ( u ) { wrap . remove ( ) ; _libUnlinkEdition ( eds . find ( x => x . id === Number ( u . dataset . unlink ) ) ) ; }
else if ( a ) { wrap . remove ( ) ; _libPickEdition ( b ) ; }
} ) ;
return ;
}
_libPickEdition ( b ) ;
}
function _libPickEdition ( b ) {
const mine = new Set ( _libEditionsOf ( b ) . map ( x => x . id ) ) ;
const words = t => _libNorm ( t ) . split ( /[^a-z0-9äöüß]+/ ) . filter ( w => w . length > 3 ) ;
const own = new Set ( words ( b . title ) . concat ( words ( b . author ) ) ) ;
const score = x => words ( x . title + ' ' + x . author ) . filter ( w => own . has ( w ) ) . length ;
const others = _lastBookListData . filter ( x => ! mine . has ( x . id ) && x . keyOk !== false )
. map ( x => [ score ( x ) , x ] ) . sort ( ( p , q ) => q [ 0 ] - p [ 0 ] || p [ 1 ] . title . localeCompare ( q [ 1 ] . title , 'de' ) ) . map ( p => p [ 1 ] ) ;
const item = x => ` <button type="button" class="lib-sheet-item" data-pick=" ${ x . id } "><b> ${ _libEsc ( _libFormat ( x ) ) } </b> · ${ _libEsc ( x . title ) }
< span class = "lib-meta" > $ { _libEsc ( x . author || '' ) } < / s p a n > < / b u t t o n > ` ;
const wrap = _libSheetBox ( ` Als Ausgabe von „ ${ b . title } “ ` , ` <input class="dfo-input lib-ed-q" type="search" placeholder="Titel, Autor …">
< div class = "dfo-list lib-ed-list" > $ { others . slice ( 0 , 30 ) . map ( item ) . join ( '' ) } < / d i v > ` ) ;
const q = wrap . querySelector ( '.lib-ed-q' ) ;
q . addEventListener ( 'input' , ( ) => {
const t = _libNorm ( q . value ) . trim ( ) ;
wrap . querySelector ( '.lib-ed-list' ) . innerHTML = others . filter ( x => ! t || _libNorm ( x . title + ' ' + x . author ) . indexOf ( t ) >= 0 )
. slice ( 0 , 30 ) . map ( item ) . join ( '' ) ;
} ) ;
wrap . addEventListener ( 'click' , e => {
const p = e . target . closest ( '[data-pick]' ) ;
if ( ! p ) return ;
wrap . remove ( ) ;
_libLinkEditions ( [ b . id , Number ( p . dataset . pick ) ] ) ;
} ) ;
}
window . bookEditions = id => { const b = ( _lastBookListData || [ ] ) . find ( x => x . id === Number ( id ) ) ; if ( b ) _libEditionsSheet ( b ) ; } ;
window . bookEditionsOf = id => { const b = ( _lastBookListData || [ ] ) . find ( x => x . id === Number ( id ) ) ; return b ? _libEditionsOf ( b ) : [ ] ; } ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
// ---- which books, in which order ---------------------------------------------------------------
2026-10-25 12:28:41 +01:00
const _libByLength = ( ) => _lib . sort === 'short' || _lib . sort === 'long' ;
// A book's length is in its meta once it was opened (62-reader.js) or uploaded (since 2026-10-25).
// For the others this device works it out once, when a length sort is chosen: the file from the
// offline copy or the server, decrypted here, its text counted (bookPagesOfFile), the pages into
// the encrypted meta. One book after the other; a book that fails isn't tried again this session.
let _libLengthRun = null ;
const _libLengthTried = new Set ( ) ;
function _libFillLengths ( ) {
if ( _libLengthRun || ! _libByLength ( ) ) return ;
const todo = ( _lastBookListData || [ ] ) . filter ( b => ! b . pages && b . keyOk !== false && ! _libLengthTried . has ( b . id ) ) ;
if ( ! todo . length ) return ;
_libLengthRun = { done : 0 , of : todo . length } ;
( async ( ) => {
let shown = Date . now ( ) ;
try {
const key = await getOrCreateEncKey ( ) ;
for ( const b of todo ) {
if ( ! _libByLength ( ) ) break ;
_libLengthTried . add ( b . id ) ;
try {
let file = await _getCachedBook ( b . id ) ;
if ( ! file ) {
const res = await fetch ( ` /books/ ${ b . id } /data/ ` ) ;
if ( ! res . ok ) throw new Error ( res . status ) ;
file = await res . json ( ) ;
}
const pages = await bookPagesOfFile ( await decryptBytes ( key , file . data _iv , file . data _ct ) , b . type ) ;
if ( pages > 0 ) await _updateBookMeta ( b . id , { pages } ) ;
} catch ( e ) { /* stays at the end */ }
_libLengthRun . done ++ ;
if ( Date . now ( ) - shown > 4000 ) { shown = Date . now ( ) ; renderBookList ( _lastBookListData ) ; }
}
} finally {
_libLengthRun = null ;
renderBookList ( _lastBookListData ) ;
}
} ) ( ) ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
function _libShown ( books ) {
const q = _libNorm ( _lib . q ) . trim ( ) ;
let list = books . filter ( b =>
( _lib . status === 'all' || ( _lib . status === 'started' ? _libStarted ( b ) : _lib . status === 'unread' ? _libUnread ( b ) : b . is _read ) ) &&
( _lib . folder == null || ( _lib . folder === '' ? ! _libFolders ( b ) . length : _libFolders ( b ) . indexOf ( _lib . folder ) >= 0 ) ) &&
( ! q || _libNorm ( b . title + ' ' + b . author ) . indexOf ( q ) >= 0 ) ) ;
const by = {
recent : ( a , b ) => ( _bookRecency ( b ) - _bookRecency ( a ) ) || String ( b . uploaded _at || '' ) . localeCompare ( String ( a . uploaded _at || '' ) ) ,
title : ( a , b ) => a . title . localeCompare ( b . title , 'de' , { numeric : true } ) ,
author : ( a , b ) => ( a . author || '~' ) . localeCompare ( b . author || '~' , 'de' ) || a . title . localeCompare ( b . title , 'de' , { numeric : true } ) ,
added : ( a , b ) => String ( b . uploaded _at || '' ) . localeCompare ( String ( a . uploaded _at || '' ) ) ,
2026-10-25 12:28:41 +01:00
// By length (pages in the meta); a book whose length isn't known yet comes last.
short : ( a , b ) => ( ( a . pages || Infinity ) - ( b . pages || Infinity ) ) || a . title . localeCompare ( b . title , 'de' , { numeric : true } ) ,
long : ( a , b ) => ( ( b . pages || 0 ) - ( a . pages || 0 ) ) || a . title . localeCompare ( b . title , 'de' , { numeric : true } ) ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
} ;
return list . slice ( ) . sort ( by [ _lib . sort ] || by . recent ) ;
}
// ---- drawing -------------------------------------------------------------------------------------
function _libEsc ( s ) {
return typeof escapeHtml === 'function' ? escapeHtml ( s ) : String ( s == null ? '' : s ) ;
}
function _libHue ( s ) {
let h = 0 ;
for ( let i = 0 ; i < s . length ; i ++ ) h = ( h * 31 + s . charCodeAt ( i ) ) % 360 ;
return h ;
}
function _libCover ( b , cls ) {
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
const url = window . DioraCovers && b . cover !== 'generated' ? DioraCovers . url ( b ) : null ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( url ) return ` <img class=" ${ cls } " src=" ${ url } " alt="" data-cover-id=" ${ b . id } "> ` ;
// None (yet): one drawn from the book's title, author, category and folders (gencover.js).
if ( window . DioraGenCover ) {
return ` <img class=" ${ cls } " src=" ${ DioraGenCover . url ( b , { gray : _LIB _EINK ( ) } )}" alt="" data-cover-id=" ${ b . id } "> ` ;
}
const initials = ( b . title || '?' ) . trim ( ) . slice ( 0 , 1 ) . toUpperCase ( ) ;
return ` <span class=" ${ cls } lib-nocover" data-cover-id=" ${ b . id } " style="--h: ${ _libHue ( b . title || '' ) } "><span> ${ _libEsc ( b . title || '' ) } </span><b> ${ _libEsc ( initials ) } </b></span> ` ;
}
function _libPct ( b ) {
return b . is _read ? 100 : Math . round ( ( b . scroll _fraction || 0 ) * 100 ) ;
}
function _libState ( b ) {
if ( b . keyOk === false ) return '⚠ falscher Schlüssel' ;
if ( b . is _read ) return '✓ gelesen' ;
const p = _libPct ( b ) ;
return p > 0 ? p + ' %' : 'neu' ;
}
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
function _libCard ( b , label ) {
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
const sel = _lib . selecting && _lib . selecting . has ( b . id ) ;
2026-10-19 14:27:14 +02:00
const eds = _lib . editions && _lib . editions . get ( b . id ) ;
const by = ( label ? label + ( b . author ? ' · ' : '' ) : '' ) + ( b . author || '' ) + // "Band 2 · Autor" in a series
2026-10-25 12:28:41 +01:00
( eds && _libLayout ( ) === 'list' ? ' · ' + _libFormats ( eds ) : '' ) +
( _libByLength ( ) && b . pages ? ( b . author || label ? ' · ' : '' ) + b . pages + ' S.' : '' ) ;
2026-10-19 14:27:14 +02:00
const edBadge = eds ? ` <span class="lib-ed" title=" ${ eds . length } Ausgaben"> ${ _libEsc ( _libFormats ( eds ) ) } </span> ` : '' ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
const pct = _libPct ( b ) ;
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
// An issue with its articles (54-issues.js): read of them instead of a percentage.
const iss = typeof _issueSummary === 'function' && _isMagazine ( b ) ? _issueSummary ( b ) : null ;
if ( iss && _libLayout ( ) === 'list' ) {
return ` <div class="lib-row ${ sel ? ' lib-sel' : '' } " data-lib-book=" ${ b . id } ">
$ { _libCover ( b , 'lib-thumb' ) }
< span class = "lib-text" > < span class = "lib-title" > $ { _libEsc ( b . title ) } < / s p a n >
< span class = "lib-meta" > $ { _libEsc ( _issueCounts ( iss ) ) } < / s p a n > $ { _ i s s u e S e g m e n t s ( i s s , - 1 , ' i s s - s e g - t h i n ' ) } < / s p a n >
< button type = "button" class = "lib-more" data - lib - more = "${b.id}" aria - label = "Optionen" > ⋮ < / b u t t o n > < / d i v > ` ;
}
if ( iss ) {
return ` <div class="lib-card ${ sel ? ' lib-sel' : '' } " data-lib-book=" ${ b . id } ">
< span class = "lib-coverbox" > $ { _libCover ( b , 'lib-cover' ) }
< button type = "button" class = "lib-more" data - lib - more = "${b.id}" aria - label = "Optionen" > ⋮ < / b u t t o n > < / s p a n >
$ { _issueSegments ( iss , - 1 , 'iss-seg-thin' ) }
< span class = "lib-title" > $ { _libEsc ( b . title ) } < / s p a n >
< span class = "lib-meta" > $ { iss . read } / $ { iss . total } gelesen < / s p a n > < / d i v > ` ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( _libLayout ( ) === 'list' ) {
return ` <div class="lib-row ${ sel ? ' lib-sel' : '' } " data-lib-book=" ${ b . id } ">
$ { _libCover ( b , 'lib-thumb' ) }
< span class = "lib-text" > < span class = "lib-title" > $ { _libEsc ( b . title ) } < / s p a n >
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
< span class = "lib-meta" > $ { _libEsc ( by ) } < / s p a n > < / s p a n >
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
< span class = "lib-state${b.is_read ? ' lib-done' : ''}" > $ { _libEsc ( _libState ( b ) ) } < / s p a n >
< button type = "button" class = "lib-more" data - lib - more = "${b.id}" aria - label = "Optionen" > ⋮ < / b u t t o n > < / d i v > ` ;
}
return ` <div class="lib-card ${ sel ? ' lib-sel' : '' } " data-lib-book=" ${ b . id } ">
2026-10-19 14:27:14 +02:00
< span class = "lib-coverbox" > $ { _libCover ( b , 'lib-cover' ) } $ { edBadge }
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
$ { b . is _read ? '<span class="lib-badge">✓</span>' : '' }
< button type = "button" class = "lib-more" data - lib - more = "${b.id}" aria - label = "Optionen" > ⋮ < / b u t t o n > < / s p a n >
< span class = "lib-bar" > < span style = "width:${pct}%" > < / s p a n > < / s p a n >
< span class = "lib-title" > $ { _libEsc ( b . title ) } < / s p a n >
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
< span class = "lib-meta" > $ { _libEsc ( by ) } < / s p a n > < / d i v > ` ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
}
2026-10-16 23:08:03 +02:00
// ---- "Zuletzt gelesen": the book read last, large, and the ones read before it in a row --------
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
function _libContinueBook ( books ) {
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 ;
}
2026-10-16 23:08:03 +02:00
// 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 ) {
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( ! b ) return '' ;
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
const iss = typeof _issueSummary === 'function' && _isMagazine ( b ) ? _issueSummary ( b ) : null ;
const nx = iss && iss . next >= 0 ? iss . arts [ iss . next ] : null ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
const pages = b . pages ? ` · Seite ${ Math . max ( 1 , Math . round ( b . pages * ( b . scroll _fraction || 0 ) ) ) } von ${ b . pages } ` : '' ;
2026-10-16 23:08:03 +02:00
const ago = _libAgo ( b . last _read ) ;
const row = ( recent || [ ] ) . map ( r => ` <div class="lib-recent-item" data-lib-book=" ${ r . id } ">
< span class = "lib-coverbox" > $ { _libCover ( r , 'lib-cover' ) } $ { r . is _read ? '<span class="lib-badge">✓</span>' : '' } < / s p a n >
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
$ { ( ( ) => { const ri = typeof _issueSummary === 'function' && _isMagazine ( r ) ? _issueSummary ( r ) : null ;
return ri ? _issueSegments ( ri , - 1 , 'iss-seg-thin' ) : ` <span class="lib-bar"><span style="width: ${ _libPct ( r ) } %"></span></span> ` ; } ) ( ) }
2026-10-16 23:08:03 +02:00
< span class = "lib-title" > $ { _libEsc ( r . title ) } < / s p a n > < / d i v > ` ) . j o i n ( ' ' ) ;
return ` <section class="lib-recent" aria-label="Zuletzt gelesen">
< div class = "lib-continue" data - lib - book = "${b.id}" > $ { _libCover ( b , 'lib-hero-cover' ) }
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
< span class = "lib-text" > < span class = "lib-kicker" > $ { iss ? 'Im Heft weiterlesen' : 'Weiterlesen' } $ { ago ? ' · ' + ago : '' } < / s p a n >
2026-10-16 23:08:03 +02:00
< span class = "lib-title lib-hero-title" > $ { _libEsc ( b . title ) } < / s p a n >
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
< span class = "lib-meta" > $ { nx ? _libEsc ( 'Artikel ' + ( iss . next + 1 ) + ' · ' + nx . t ) : _libEsc ( b . author || '' ) } < / s p a n >
$ { iss ? ` <span class="lib-hero-progress"> ${ _issueSegments ( iss , - 1 ) } <span class="lib-meta"> ${ _libEsc ( _issueCounts ( iss ) ) } </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 } < / s p a n > < / s p a n > ` }
2026-10-16 23:08:03 +02:00
< span class = "lib-play" aria - hidden = "true" > ▶ Weiterlesen < / s p a n > < / s p a n > < / d i v >
$ { row ? ` <div class="lib-recent-head">Zuletzt gelesen</div><div class="lib-recent-row"> ${ row } </div> ` : '' }
< / s e c t i o n > ` ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
}
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
function _libVolumeLabel ( b ) {
const s = _libSeries ( b ) ;
return s && s . index != null ? 'Band ' + String ( s . index ) . replace ( '.' , ',' ) : '' ;
}
function _libStackCard ( g ) {
const next = g . books . filter ( b => ! b . is _read ) [ 0 ] || g . books [ 0 ] ;
const read = g . books . filter ( b => b . is _read ) . length ;
const going = g . books . filter ( _libStarted ) [ 0 ] ;
const meta = g . books . length + ' Bände' + ( going ? ' · bei ' + ( _libVolumeLabel ( going ) || _libEsc ( going . title ) )
: read === g . books . length ? ' · gelesen' : read ? ' · ' + read + ' gelesen' : '' ) ;
const pct = Math . round ( 100 * g . books . reduce ( ( n , b ) => n + _libPct ( b ) , 0 ) / ( 100 * g . books . length ) * 100 ) / 100 ;
if ( _libLayout ( ) === 'list' ) {
return ` <div class="lib-row lib-stackrow" data-lib-series=" ${ _libEsc ( g . key ) } "> ${ _libCover ( next , 'lib-thumb' ) }
< span class = "lib-text" > < span class = "lib-title" > $ { _libEsc ( g . name ) } < / s p a n > < s p a n c l a s s = " l i b - m e t a " > $ { m e t a } < / s p a n > < / s p a n >
< span class = "lib-state" > › < / s p a n > < / d i v > ` ;
}
return ` <div class="lib-card lib-stack" data-lib-series=" ${ _libEsc ( g . key ) } ">
< span class = "lib-coverbox lib-stackbox" > $ { _libCover ( next , 'lib-cover' ) } < / s p a n >
< span class = "lib-bar" > < span style = "width:${pct}%" > < / s p a n > < / s p a n >
< span class = "lib-title" > $ { _libEsc ( g . name ) } < / s p a n >
< span class = "lib-meta" > $ { meta } < / s p a n > < / d i v > ` ;
}
// The books shown as cards, a series of two or more as one stack (in its last-read place).
function _libStacked ( list , pool ) {
const groups = _libAllSeries ( pool ) ; // the volumes in what is shown (a folder, all)
const out = [ ] ;
const placed = new Set ( ) ;
list . forEach ( b => {
const s = _libSeries ( b ) ;
const g = s ? groups . get ( _libSeriesKey ( s ) ) : null ;
if ( ! g || g . books . length < 2 ) { out . push ( _libCard ( b ) ) ; return ; }
if ( placed . has ( g . key ) ) return ;
placed . add ( g . key ) ;
out . push ( _libStackCard ( g ) ) ;
} ) ;
return out . join ( '' ) ;
}
function _libSeriesView ( books ) {
const g = _libAllSeries ( books ) . get ( _lib . series ) ;
if ( ! g ) { _lib . series = null ; return '' ; }
const cards = g . books . map ( b => _libCard ( b , _libVolumeLabel ( b ) ) ) . join ( '' ) ;
return ` <div class="lib-place"><button type="button" class="lib-btn" data-lib="series-up">‹ Bücher</button>
< strong class = "lib-place-name" > $ { _libEsc ( g . name ) } < / s t r o n g >
< button type = "button" class = "lib-icon" data - lib = "series-manage" aria - label = "Serie verwalten" > ⋯ < / b u t t o n > < / d i v >
< div class = "${_libLayout() === 'list' ? 'lib-list' : 'lib-grid'}" > $ { cards } < / d i v > ` ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
// Zeitschriften: the issues under their periodical (automatic, not a folder).
function _libMagazines ( list ) {
const groups = new Map ( ) ;
list . forEach ( b => {
const k = typeof _periodicalOf === 'function' ? _periodicalOf ( b ) : '' ;
if ( ! groups . has ( k ) ) groups . set ( k , [ ] ) ;
groups . get ( k ) . push ( b ) ;
} ) ;
const names = Array . from ( groups . keys ( ) ) . sort ( ( a , b ) => a . localeCompare ( b , 'de' ) ) ;
return names . map ( n => {
const issues = groups . get ( n ) . sort ( ( a , b ) => b . title . localeCompare ( a . title , 'de' , { numeric : true } ) ) ;
return ` <h3 class="lib-group"> ${ _libEsc ( n ) } <span class="lib-count"> ${ issues . length } </span></h3> ` +
` <div class=" ${ _libLayout ( ) === 'list' ? 'lib-list' : 'lib-grid' } "> ${ issues . map ( _libCard ) . join ( '' ) } </div> ` ;
} ) . join ( '' ) ;
}
function _libRender ( allBooks ) {
const listEl = $ ( 'book-list' ) ;
if ( ! listEl ) return ;
_lastBookListData = allBooks ;
2026-10-19 14:27:14 +02:00
const books = _libCollapse ( _bookSectionFilter ( allBooks ) ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
const magazines = window . DioraNav && _bookSection === 'magazines' ;
const shown = _libShown ( books ) ;
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
// An issue opened (54-issues.js): its contents instead of the list, without the list's search and chips.
const issueOpen = magazines && _lib . issue != null && typeof _issueView === 'function' && books . some ( b => b . id === _lib . issue ) ;
listEl . classList . toggle ( 'iss-open' , ! ! issueOpen ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
const narrow = window . matchMedia && matchMedia ( '(max-width: 720px)' ) . matches ;
2026-10-25 12:28:41 +01:00
const sorts = [ [ 'recent' , 'Zuletzt' ] , [ 'title' , 'Titel' ] , [ 'author' , 'Autor' ] , [ 'added' , 'Neu hinzugefügt' ] ,
[ 'short' , 'Kürzeste zuerst' ] , [ 'long' , 'Längste zuerst' ] ] ;
_libFillLengths ( ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
let h = ` <div class="lib-head"><h2 class="lib-h"> ${ magazines ? 'Zeitschriften' : 'Bücher' } </h2>
$ { ! narrow || _lib . searching || _lib . q ? ` <input class="lib-q" type="search" placeholder="Titel, Autor …" value=" ${ _libEsc ( _lib . q ) } "> ` :
'<button type="button" class="lib-icon" data-lib="search" aria-label="Suchen">⌕</button>' }
< select class = "lib-sort" aria - label = "Sortieren" > $ { sorts . map ( s => ` <option value=" ${ s [ 0 ] } " ${ _lib . sort === s [ 0 ] ? ' selected' : '' } > ${ s [ 1 ] } </option> ` ) . join ( '' ) } < / s e l e c t >
< button type = "button" class = "lib-icon" data - lib = "layout" aria - label = "${_libLayout() === 'grid' ? 'Als Liste' : 'Als Raster'}" > $ { _libLayout ( ) === 'grid' ? '☰' : '⊞' } < / b u t t o n >
< button type = "button" class = "lib-icon lib-add" data - lib = "add" aria - label = "Hinzufügen" > + < / b u t t o n > < / d i v >
< div class = "lib-chips" > < / d i v > ` ;
2026-10-25 12:28:41 +01:00
if ( _libLengthRun && _libByLength ( ) ) {
h += ` <p class="lib-length-note">Länge wird ermittelt … ${ _libLengthRun . done } von ${ _libLengthRun . of } Büchern</p> ` ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( _lib . selecting ) {
h += ` <div class="lib-selbar"><span> ${ _lib . selecting . size } ausgewählt</span>
< button type = "button" class = "lib-btn" data - lib = "sel-folders" $ { _lib . selecting . size ? '' : ' disabled' } > In Ordner … < / b u t t o n >
2026-10-19 14:27:14 +02:00
< button type = "button" class = "lib-btn" data - lib = "sel-editions" $ { _lib . selecting . size > 1 ? '' : ' disabled' } > Als Ausgaben verbinden < / b u t t o n >
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
< button type = "button" class = "lib-btn" data - lib = "sel-done" > Fertig < / b u t t o n > < / d i v > ` ;
}
if ( ! allBooks . length ) {
h += '<p class="lib-empty">Noch keine Bücher. Mit + eine .epub- oder .pdf-Datei hinzufügen.</p>' ;
} else if ( ! books . length ) {
h += ` <p class="lib-empty"> ${ magazines ? 'Noch keine Zeitschriften. Ein Heft unter Bücher über ⋮ → „Als Zeitschrift führen“ hierher holen.' : 'Alle Bücher stehen bei den Zeitschriften.' } </p> ` ;
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
} else if ( issueOpen ) {
h += _issueView ( books . find ( b => b . id === _lib . issue ) ) ;
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
} else if ( _lib . series && ! magazines ) {
h += _libSeriesView ( books ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
} else {
const going = _libContinueBook ( books ) ;
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
const pool = shown . slice ( ) ;
2026-10-16 23:08:03 +02:00
h += _libContinue ( going , _libRecentBooks ( books , going ) ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( going ) shown . splice ( shown . indexOf ( going ) , 1 ) ; // once, on top
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
// Series as stacks, unless searching, filtering by state or choosing several.
const stack = ! magazines && ! _lib . q && _lib . status === 'all' && ! _lib . selecting ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
if ( ! shown . length && ! going ) h += '<p class="lib-empty">Nichts gefunden.</p>' ;
else if ( magazines && _lib . sort === 'recent' && ! _lib . q ) h += _libMagazines ( shown ) ;
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
else h += ` <div class=" ${ _libLayout ( ) === 'list' ? 'lib-list' : 'lib-grid' } "> ${ stack ? _libStacked ( shown , pool ) : shown . map ( _libCard ) . join ( '' ) } </div> ` ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
}
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 ) ;
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
// Issues still without their articles' index: built now and then, in the background.
if ( magazines && typeof _issueIndexLater === 'function' ) _issueIndexLater ( books ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 ( ( ) => { } ) ,
} ) ;
}
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
// ---- series: assign, manage ------------------------------------------------------------------------
function _libSheetBox ( title , body ) {
const wrap = document . createElement ( 'div' ) ;
wrap . className = 'dfo-scrim' ;
wrap . innerHTML = ` <div class="dfo-sheet" role="dialog"><div class="dfo-head"><strong> ${ _libEsc ( title ) } </strong>
< button type = "button" class = "dfo-x" data - x = "1" aria - label = "Schließen" > ✕ < / b u t t o n > < / d i v > $ { b o d y } < / d i v > ` ;
wrap . addEventListener ( 'click' , e => { if ( e . target === wrap || e . target . closest ( '[data-x]' ) ) wrap . remove ( ) ; } ) ;
document . body . appendChild ( wrap ) ;
return wrap ;
}
// A book into a series (an existing one, or a new name) with its number, or out of any.
function _libSeriesSheet ( b ) {
const now = _libSeries ( b ) ;
const names = Array . from ( _libAllSeries ( _lastBookListData ) . values ( ) ) . map ( g => g . name ) . sort ( ( x , y ) => x . localeCompare ( y , 'de' ) ) ;
const radios = names . map ( n => ` <label class="dfo-check"><input type="radio" name="sr" value=" ${ _libEsc ( n ) } " ${ now && now . name === n ? ' checked' : '' } > ${ _libEsc ( n ) } </label> ` ) . join ( '' ) ;
const wrap = _libSheetBox ( ` Serie für „ ${ b . title } “ ` , ` <form class="lib-series-form">
< div class = "dfo-list" > $ { radios || '<p class="dfo-note">Noch keine Serien.</p>' }
< label class = "dfo-check" > < input type = "radio" name = "sr" value = "__new" $ { now ? '' : ' checked' } > Neue Serie :
< input class = "dfo-input" name = "newname" maxlength = "120" placeholder = "Name der Serie" value = "${now ? '' : _libEsc((_libSeriesGuess(b.title) || {}).name || '')}" > < / l a b e l > < / d i v >
< label class = "lib-series-ix" > Band < input class = "dfo-input" name = "ix" inputmode = "decimal" size = "5" value = "${now && now.index != null ? String(now.index).replace('.', ',') : ''}" > < / l a b e l >
< div class = "dfo-actions" > < button type = "button" class = "dfo-btn" data - none = "1" > Keine Serie < / b u t t o n >
< button type = "submit" class = "dfo-btn dfo-primary" > Übernehmen < / b u t t o n > < / d i v > < / f o r m > ` ) ;
const form = wrap . querySelector ( 'form' ) ;
form . newname . addEventListener ( 'focus' , ( ) => { form . querySelector ( 'input[value="__new"]' ) . checked = true ; } ) ;
const save = async ( series , ix ) => {
wrap . remove ( ) ;
try {
await _updateBookMeta ( b . id , { series , seriesIndex : ix } ) ;
renderBookList ( _lastBookListData ) ;
} catch ( e ) { customAlert ( 'Das ließ sich nicht speichern: ' + e . message ) ; }
} ;
wrap . querySelector ( '[data-none]' ) . addEventListener ( 'click' , ( ) => save ( '' , null ) ) ;
form . addEventListener ( 'submit' , e => {
e . preventDefault ( ) ;
const pick = ( form . querySelector ( 'input[name="sr"]:checked' ) || { } ) . value ;
const name = pick === '__new' ? form . newname . value . trim ( ) . replace ( /\s+/g , ' ' ) : pick ;
if ( ! name ) { form . newname . focus ( ) ; return ; }
const n = parseFloat ( String ( form . ix . value ) . replace ( ',' , '.' ) ) ;
save ( name . slice ( 0 , 120 ) , isFinite ( n ) ? n : null ) ;
} ) ;
}
window . assignBookSeries = id => {
const b = _lastBookListData . find ( x => x . id === Number ( id ) ) ;
if ( b ) _libSeriesSheet ( b ) ;
} ;
// The series shown: rename (every volume), order (numbers 1 … n), dissolve.
function _libSeriesManage ( ) {
const g = _libAllSeries ( _lastBookListData ) . get ( _lib . series ) ;
if ( ! g ) return ;
let order = g . books . slice ( ) ;
const draw = ( ) => order . map ( ( b , i ) => ` <div class="dfo-mrow"><span class="dfo-mname"> ${ i + 1 } . ${ _libEsc ( b . title ) } </span>
< button type = "button" class = "dfo-icon" data - up = "${i}" $ { i ? '' : ' disabled' } aria - label = "Nach oben" > ↑ < / b u t t o n >
< button type = "button" class = "dfo-icon" data - down = "${i}" $ { i < order . length - 1 ? '' : ' disabled' } aria - label = "Nach unten" > ↓ < / b u t t o n > < / d i v > ` ) . j o i n ( ' ' ) ;
const wrap = _libSheetBox ( ` Serie „ ${ g . name } “ ` , ` <div class="dfo-list lib-series-order"> ${ draw ( ) } </div>
< div class = "dfo-actions" > < button type = "button" class = "dfo-btn" data - a = "rename" > Umbenennen … < / b u t t o n >
< button type = "button" class = "dfo-btn" data - a = "dissolve" > Auflösen < / b u t t o n >
< button type = "button" class = "dfo-btn dfo-primary" data - a = "save" > Reihenfolge übernehmen < / b u t t o n > < / d i v > ` ) ;
const list = wrap . querySelector ( '.lib-series-order' ) ;
const write = async fn => {
wrap . remove ( ) ;
try {
for ( let i = 0 ; i < order . length ; i ++ ) await _updateBookMeta ( order [ i ] . id , fn ( order [ i ] , i ) ) ;
} catch ( e ) { customAlert ( 'Das ließ sich nicht speichern: ' + e . message ) ; }
renderBookList ( _lastBookListData ) ;
} ;
wrap . addEventListener ( 'click' , async e => {
const t = e . target . closest ( 'button' ) ;
if ( ! t ) return ;
if ( t . dataset . up != null || t . dataset . down != null ) {
const i = Number ( t . dataset . up != null ? t . dataset . up : t . dataset . down ) , j = t . dataset . up != null ? i - 1 : i + 1 ;
const x = order [ i ] ; order [ i ] = order [ j ] ; order [ j ] = x ;
list . innerHTML = draw ( ) ;
} else if ( t . dataset . a === 'save' ) {
write ( ( b , i ) => ( { series : g . name , seriesIndex : i + 1 } ) ) ;
} else if ( t . dataset . a === 'rename' ) {
const n = await customPrompt ( 'Neuer Name der Serie:' , g . name ) ;
const name = n == null ? '' : String ( n ) . trim ( ) . replace ( /\s+/g , ' ' ) . slice ( 0 , 120 ) ;
if ( ! name || name === g . name ) return ;
_lib . series = name . toLowerCase ( ) ;
write ( b => ( { series : name , seriesIndex : ( _libSeries ( b ) || { } ) . index == null ? null : _libSeries ( b ) . index } ) ) ;
} else if ( t . dataset . a === 'dissolve' ) {
if ( ! ( await customConfirm ( ` Serie „ ${ g . name } “ auflösen? Die Bücher bleiben. ` ) ) ) return ;
_lib . series = null ;
write ( ( ) => ( { series : '' , seriesIndex : null } ) ) ;
}
} ) ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
// ---- a book's actions (the apps may bring their own sheet: __dioraBookSheet) -------------------------
2026-10-19 20:14:17 +02:00
function _libProjectsLabel ( b ) {
const inIt = window . DioraProjects ? DioraProjects . of ( 'book' , b . id ) : [ ] ;
return 'Mit Projekt verknüpfen …' + ( inIt . length ? ` <span class="muted">( ${ inIt . map ( p => _libEsc ( p . name ) ) . join ( ', ' ) } )</span> ` : '' ) ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 ) ] ,
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
[ b . cover === 'generated' ? 'Eigenes Cover verwenden' : 'Generiertes Cover verwenden' , ( ) => toggleBookCover ( b . id ) ] ,
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
[ 'Serie …' , ( ) => _libSeriesSheet ( b ) ] ,
2026-10-19 14:27:14 +02:00
[ _libEditionsOf ( b ) . length > 1 ? ` Ausgaben ( ${ _libEditionsOf ( b ) . length } ) … ` : 'Als Ausgabe verbinden …' , ( ) => _libEditionsSheet ( b ) ] ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
[ b . is _read ? 'Als ungelesen markieren' : 'Als gelesen markieren' , ( ) => toggleBookRead ( b . id , ! ! b . is _read ) ] ,
2026-10-19 20:14:17 +02:00
// Projekte (projects.js): "Forschung › Mit Projekt verknüpfen", and where it is already.
window . DioraProjects && [ _libProjectsLabel ( b ) , ( ) => DioraProjects . link ( 'book' , b . id , { title : b . title , author : b . author } ) ] ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 = ` <div class="dfo-sheet" role="dialog"><div class="dfo-head"><strong> ${ _libEsc ( b . title ) } </strong>
< button type = "button" class = "dfo-x" data - x = "1" aria - label = "Schließen" > ✕ < / b u t t o n > < / d i v >
$ { items . map ( ( it , i ) => ` <button type="button" class="lib-sheet-item ${ it [ 2 ] ? ' lib-danger' : '' } " data-i=" ${ i } "> ${ it [ 0 ] } </button> ` ) . join ( '' ) } < / d i v > ` ;
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]' ) ;
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
const stack = t . closest ( '[data-lib-series]' ) ;
if ( ! more && ! act && ! card && ! stack ) return ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 || [ ] ) ) ;
2026-10-19 14:27:14 +02:00
else if ( a === 'sel-editions' ) {
const ids = Array . from ( _lib . selecting || [ ] ) ;
_lib . selecting = null ;
_libLinkEditions ( ids ) ;
}
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
else if ( a === 'series-up' ) { _lib . series = null ; renderBookList ( _lastBookListData ) ; }
else if ( a === 'series-manage' ) _libSeriesManage ( ) ;
return ;
}
if ( stack && ! card ) {
_lib . series = stack . dataset . libSeries ;
renderBookList ( _lastBookListData ) ;
window . scrollTo ( 0 , 0 ) ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 ;
}
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
if ( b && _isMagazine ( b ) && typeof _issueOpen === 'function' ) {
if ( card . classList . contains ( 'lib-continue' ) && _issueSummary ( b ) ) _issueContinue ( id ) ;
else _issueOpen ( id ) ;
return ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 ; }
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
if ( shown && _lib . issue != null ) { _lib . issue = null ; renderBookList ( _lastBookListData ) ; return true ; }
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
if ( shown && _lib . series ) { _lib . series = null ; renderBookList ( _lastBookListData ) ; return true ; }
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 => {
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
const b = ( _lastBookListData || [ ] ) . find ( x => x . id === id ) ;
if ( b && b . cover === 'generated' ) return ;
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
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 ) ; } ) ;
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
// Files dropped anywhere on the library (the old drop zone is hidden): one or several.
const tabEl = $ ( 'tab-books' ) ;
if ( tabEl ) {
tabEl . addEventListener ( 'dragover' , e => {
if ( e . dataTransfer && Array . from ( e . dataTransfer . types || [ ] ) . indexOf ( 'Files' ) >= 0 ) { e . preventDefault ( ) ; tabEl . classList . add ( 'lib-drop' ) ; }
} ) ;
tabEl . addEventListener ( 'dragleave' , e => { if ( e . target === tabEl ) tabEl . classList . remove ( 'lib-drop' ) ; } ) ;
tabEl . addEventListener ( 'drop' , e => {
tabEl . classList . remove ( 'lib-drop' ) ;
const files = Array . from ( ( e . dataTransfer && e . dataTransfer . files ) || [ ] ) ;
if ( ! files . length ) return ;
e . preventDefault ( ) ;
if ( files . length === 1 ) uploadEbook ( files [ 0 ] ) ;
else _importMany ( files . map ( f => ( { name : f . name , size : f . size , buf : ( ) => f . arrayBuffer ( ) } ) ) ) ;
} ) ;
}
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
_libLoadPrefs ( ) ;
} ) ( ) ;
function _libAddMenu ( ) {
const wrap = document . createElement ( 'div' ) ;
wrap . className = 'dfo-scrim' ;
const cloud = ! ( window . DioraNative && DioraNative . isReaderApp && DioraNative . isReaderApp ( ) ) ;
wrap . innerHTML = ` <div class="dfo-sheet" role="dialog"><div class="dfo-head"><strong>Hinzufügen</strong>
< button type = "button" class = "dfo-x" data - x = "1" aria - label = "Schließen" > ✕ < / b u t t o n > < / d i v >
< button type = "button" class = "lib-sheet-item" data - a = "file" > E - Book - Datei ( . epub , . pdf ) … < / b u t t o n >
$ { cloud ? '<button type="button" class="lib-sheet-item" data-a="cloud">Aus der Cloud (WebDAV, Nextcloud) …</button>' : '' }
< button type = "button" class = "lib-sheet-item" data - a = "select" > Mehrere Bücher auswählen < / b u t t o n > < / d i v > ` ;
wrap . addEventListener ( 'click' , e => {
const a = e . target . closest ( '[data-a]' ) ;
if ( e . target === wrap || e . target . closest ( '[data-x]' ) ) { wrap . remove ( ) ; return ; }
if ( ! a ) return ;
wrap . remove ( ) ;
if ( a . dataset . a === 'file' ) { const f = $ ( 'book-file-input' ) ; if ( f ) f . click ( ) ; }
else if ( a . dataset . a === 'cloud' ) toggleCloudImport ( ) ;
else { _lib . selecting = new Set ( ) ; renderBookList ( _lastBookListData ) ; }
} ) ;
document . body . appendChild ( wrap ) ;
}
// ---- styles ---------------------------------------------------------------------------------------------
( function ( ) {
const style = document . createElement ( 'style' ) ;
style . textContent = [
'html.lib-v2 #tab-books .book-drop-zone, html.lib-v2 #tab-books .book-list-filter,' ,
'html.lib-v2 #book-upload-area > button.btn, html.lib-v2 #book-upload-area > .nui-add-row { display: none !important; }' ,
'#book-list .lib-head { display: flex; align-items: center; gap: 6px; margin: 4px 0 8px; }' ,
'#book-list .lib-h { margin: 0 auto 0 0; font-size: 18px; }' ,
'#book-list .lib-q { flex: 0 1 260px; min-width: 0; padding: 8px 10px; border-radius: 6px; border: 1px solid var(--border, #2a2a2a); background: var(--bg, #000); color: inherit; font: inherit; }' ,
'#book-list .lib-sort { padding: 7px 6px; border-radius: 6px; border: 1px solid var(--border, #2a2a2a); background: var(--bg, #000); color: inherit; font: inherit; font-size: 13px; max-width: 140px; }' ,
'#book-list .lib-icon { min-width: 38px; min-height: 38px; border-radius: 19px; border: 1px solid var(--border-strong, #444); background: none; color: inherit; font: inherit; font-size: 17px; cursor: pointer; }' ,
'#book-list .lib-add { background: var(--accent, #e63946); border-color: var(--accent, #e63946); color: var(--on-accent, #fff); font-size: 20px; }' ,
'#book-list .lib-chips { margin: 0 0 10px; }' ,
'#book-list .lib-selbar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; margin: 0 0 10px; border-radius: 8px; background: var(--raised, #222); }' ,
'#book-list .lib-selbar span { flex: 1; }' ,
'#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-danger { color: var(--accent, #e63946); }' ,
'#book-list .lib-kicker { color: var(--accent, #e63946); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }' ,
2026-10-16 23:08:03 +02:00
// "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; }' ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
'#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; } }' ,
'#book-list .lib-card { display: flex; flex-direction: column; gap: 3px; min-width: 0; cursor: pointer; }' ,
'#book-list .lib-coverbox { position: relative; display: block; }' ,
'#book-list .lib-cover { display: block; width: 100%; aspect-ratio: 2 / 3; object-fit: cover; border-radius: 4px; background: var(--panel, #161616); box-shadow: 0 1px 3px rgba(0,0,0,.35); }' ,
'#book-list .lib-nocover { display: flex; flex-direction: column; justify-content: space-between; padding: 8px; box-sizing: border-box; overflow: hidden; background: hsl(var(--h), 28%, 24%); color: hsl(var(--h), 40%, 88%); }' ,
'#book-list .lib-nocover span { font-size: 12px; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; }' ,
'#book-list .lib-nocover b { font-size: 26px; opacity: .5; align-self: flex-end; }' ,
'#book-list .lib-thumb { width: 34px; height: 51px; flex: none; border-radius: 3px; object-fit: cover; }' ,
'#book-list .lib-thumb.lib-nocover { padding: 2px; }' ,
'#book-list .lib-thumb.lib-nocover span { display: none; }' ,
'#book-list .lib-thumb.lib-nocover b { font-size: 15px; align-self: center; margin: auto; }' ,
'#book-list .lib-thumb-l { width: 52px; height: 78px; }' ,
2026-10-19 14:27:14 +02:00
'#book-list .lib-ed { position: absolute; left: 4px; top: 4px; padding: 1px 6px; border-radius: 4px; background: rgba(0,0,0,.62); color: #fff; font-size: 10px; letter-spacing: .03em; }' ,
'body.eink #book-list .lib-ed { background: #fff; color: #000; border: 1px solid #000; }' ,
'.lib-ed-row { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--border, #2a2a2a); }' ,
'.lib-ed-row .lib-sheet-item { border-bottom: none; flex: 1; }' ,
'.lib-sheet-item .lib-meta { display: block; font-size: 12px; color: var(--muted, #9a9a9a); }' ,
'.lib-ed-q { width: 100%; box-sizing: border-box; margin: 0 0 8px; }' ,
'.lib-ed-list { max-height: 55vh; overflow-y: auto; }' ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
'#book-list .lib-badge { position: absolute; left: 6px; bottom: 6px; padding: 1px 6px; border-radius: 9px; background: var(--accent, #e63946); color: var(--on-accent, #fff); font-size: 11px; }' ,
'#book-list .lib-card .lib-more { position: absolute; right: 2px; top: 2px; width: 30px; height: 30px; border-radius: 15px; border: none; background: rgba(0,0,0,.45); color: #fff; font-size: 16px; cursor: pointer; opacity: 0; }' ,
'#book-list .lib-card:hover .lib-more, #book-list .lib-card:focus-within .lib-more { opacity: 1; }' ,
'@media (hover: none) { #book-list .lib-card .lib-more { opacity: .85; } }' ,
'#book-list .lib-bar { display: block; height: 3px; border-radius: 2px; background: var(--border, #2a2a2a); overflow: hidden; margin-top: 4px; }' ,
'#book-list .lib-bar > span { display: block; height: 100%; background: var(--accent, #e63946); }' ,
'#book-list .lib-title { font-size: 13px; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }' ,
'#book-list .lib-meta { font-size: 12px; color: var(--muted, #9a9a9a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }' ,
'#book-list .lib-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }' ,
'#book-list .lib-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--border, #2a2a2a); cursor: pointer; }' ,
'#book-list .lib-row .lib-more { background: none; border: none; color: inherit; font-size: 18px; min-width: 36px; min-height: 36px; cursor: pointer; }' ,
'#book-list .lib-state { font-size: 12px; color: var(--muted, #9a9a9a); white-space: nowrap; }' ,
'#book-list .lib-done { color: var(--accent, #e63946); }' ,
'#book-list .lib-sel .lib-cover, #book-list .lib-sel .lib-thumb { outline: 3px solid var(--accent, #e63946); outline-offset: 2px; }' ,
'#book-list .lib-row.lib-sel { background: var(--hover, rgba(255,255,255,.05)); }' ,
'#book-list .lib-group { margin: 18px 0 8px; font-size: 13px; font-weight: 400; color: var(--muted, #9a9a9a); }' ,
'#book-list .lib-count { opacity: .7; }' ,
'#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; }' ,
2026-10-16 23:08:03 +02:00
'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; }' ,
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
'#tab-books.lib-drop { outline: 2px dashed var(--accent, #e63946); outline-offset: -6px; }' ,
Serien für Bücher: erkannt, als Stapel, selbst zuweisen und verwalten
- Beim Hochladen liest diora die Serie aus dem E-Book (calibre:series und
series_index, EPUB 3 belongs-to-collection mit group-position); vorhandene
Bücher werden am Titel erkannt („Der Zauberberg. Erster Band“, „…: Teil 3“).
- In der Bibliothek ist eine Serie ab zwei Bänden ein Stapel („2 Bände · bei
Band 1“), darin die Bände in Reihenfolge; beim Suchen und Filtern einzeln.
- ⋮ → „Serie …“: vorhandene oder neue Serie, Band-Nummer, oder „Keine Serie“.
In der Serie: umbenennen, Reihenfolge, auflösen. Alles im verschlüsselten
Meta (series, seriesIndex; '' = bewusst keine).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:47:18 +02:00
'#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::after { top: -5px; }' ,
'#book-list .lib-stackbox .lib-cover { position: relative; z-index: 1; }' ,
'#book-list .lib-stack { margin-top: 9px; }' ,
'#book-list .lib-place { display: flex; align-items: center; gap: 10px; margin: 0 0 14px; }' ,
'#book-list .lib-place-name { flex: 1; min-width: 0; font-size: 16px; }' ,
'.lib-series-ix { display: flex; align-items: center; gap: 8px; margin: 10px 0 0; }' ,
'.lib-series-form .dfo-check .dfo-input { margin-left: 6px; }' ,
Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
Meta, cover: 'generated').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
'#book-upload-status { display: block; margin: 0 0 10px; color: var(--muted, #9a9a9a); font-size: 13px; }' ,
'.cloud-chosen { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 6px 0; }' ,
'.cloud-chosen:empty { display: none; }' ,
'.cloud-entry .cloud-check { width: 18px; height: 18px; margin: 0 8px 0 2px; flex: none; }' ,
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
'body.eink #book-list .lib-cover { box-shadow: none; border: 1px solid #000; }' ,
] . join ( '\n' ) ;
document . head . appendChild ( style ) ;
} ) ( ) ;