diora-web/docs/library.md
mrwnslz ea7d62789f
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 9m1s
Test / e2e (push) Successful in 2m1s
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

6.7 KiB

Folders and the library (redesign)

Folders everywhere: tags shown as filter chips

One idea of a folder for every area — Bücher, Zeitschriften, Hörbücher, Podcasts, Blogs (and the news' Ressorts, which already work this way):

  • A thing can be in several folders (they work like tags); none is fine.
  • Folders are filters, not rooms: a row of chips on top — Alle · <folder> n · … · Ohne Ordner — tapping one filters in place; no "inside a folder" view, no back button. On a wide window the row wraps.
  • Assigning: mark one or more things (long press / ☐ select mode, or ⋮ on one), then "In Ordner …": a sheet with every folder of the area as checkboxes and "Neuer Ordner". No typing a name that must match.
  • Managing (⋮ on the chip row → "Ordner verwalten"): rename, merge (rename onto an existing name), delete (the things stay, only the folder goes), reorder by dragging. The order is the chip order.
  • Automatic groups stay automatic and look different from folders (no chip; a stack/group heading): audiobook series, magazine periodicals.

Where folders are kept

Area Field Notes
E-books, magazines encrypted meta folders: [names] folder (the first) kept for older clients; read folders or else [folder]
Audiobooks, podcasts PodcastFeed.folders (JSON list) feed setting folders; folder (string) still accepted
Blogs Source.folders (exists)
News Source.category (one Ressort) already chips in Nachrichten

The folder order per area: e-books in the encrypted user settings blob (names are private), the others on the server (/podcasts/folders/, /feeds/folders/, {order: [names]}).

Shared code: static/js/folders.js (diora-web, every platform)

DioraFolders.chips(el, {folders: [{name, count}], active, none: count|0, onPick(name|''|null), onManage()})
DioraFolders.assign({title, folders: [names], selected: [names], onDone(names)})     // the sheet
DioraFolders.manage({folders: [names], onRename(old, new), onDelete(name), onOrder(names)})

Each area keeps its own storage and calls these for the look and the dialogs.

The library (Bücher, Zeitschriften)

  • Header: title, search (title, author), sort (zuletzt gelesen · Titel · Autor · hinzugefügt), ⊞/☰ grid or list, + (Buch hinzufügen, Aus Cloud).
  • Filter chips: Alle · Angefangen · Ungelesen · Gelesen — then the folders.
  • Weiterlesen: one card on top (cover, title, "Seite 132 von 186 · gestern", ▶), not repeated in the list below.
  • Grid (default on phone and desktop): covers with a progress line, title. List (default on e-ink / diora Reader): one row per book, small cover, title, author, % / ✓.

Covers, end-to-end encrypted

A client that has a book's file extracts its cover (EPUB: the OPF cover image; PDF: page 1), scales it to 240 px wide JPEG, encrypts it with the user's key (AES-GCM like the meta) and uploads it once: POST /books/<id>/cover/ {cover_ct, cover_iv}. GET /books/ lists has_cover; GET /books/<id>/cover/ returns {cover_ct, cover_iv}. Clients cache the decrypted image locally. The server never sees a cover.

Generated covers

A book without a cover (or one set to cover: 'generated' in its meta) gets one drawn by static/js/gencover.js: an SVG, the same on every device, never stored. A concept (motif, palettes, typeface) is chosen by score:

  • words in the title (3), the shelf category (2) and the author (2, only at a name's start). A stem of five letters or more counts anywhere (German compounds), a shorter one at a word's start, one of three letters as a whole word; ^ / $ in the list force a word's start / end.
  • the folders, by name (FOLDER_THEMES): the first counts 2.5 — more than an author or shelf word, less than a title word — further ones 1.

Concepts: konstruktiv (Marx), geometrie (Philosophie: rings, turning squares, triangle/circle/square), psyche (Psychoanalyse: two ellipses, the Borromean knot, a spiral), karte (Geschichte), kosmos (Science-Fiction), ornament (Fantasy), raster (Politik, Soziologie), schatten (Krimi: a red line, a lit doorway), grusel (Horror), klassiker (patterned paper with a title plate), sakral (Religion & Mythologie), spiel (Kinder & Jugend), code (Informatik), typo, wellen, botanik, berge, zellen — and bauhaus when nothing matches. A hash of title and author picks palette, layout and every detail.

Zeitschriften: das Heft und seine Artikel

An issue is a unit (the editors' selection and order, its date, its theme) and so is each article in it. Neither is reduced to the other (static/js/app/54-issues.js):

  • The issue stays the document. It opens and reads through as before, in the editors' order. The reader marks the article one is in: a running head under the bar ("Schwerpunkt · Artikel 8 von 23 · Titel"), in the apps' footer "Artikel 8/23", and a pause at each article's end (aside.iss-end): "✓ gelesen", the issue's count, "Weiter im Heft" (the next article not read yet, skipping those read or skipped), "Zum Inhalt", "überspringen", "Doch nicht gelesen". In a PDF the pause is a bar that comes up when one turns from an article's last page onto the next.
  • The states determine each other. An article one was in and whose end one reached counts as read (a jump — from the contents, "Weiter" — reads nothing). The issue counts "7 von 23 gelesen · 2 übersprungen"; "Im Heft weiterlesen" goes to the article one was in, else the next one open. "Heft abschließen" marks the rest as skipped, never as read.
  • In the library an issue shows one bar with a segment per article (read, skipped, open, the one one is in). A tap opens its contents: Rubriken, the articles with their state, a teaser and the reading time; the ⋮ / mark sets read / unread / skipped by hand.

The index comes from the file, on the device: an EPUB's table of contents (its leaves are the articles, their parents the Rubriken; without one, each spine file with text), a PDF's outline (page ranges). Teasers and reading times (200 words a minute) come from the text. It is kept in the encrypted meta as issue: {v: 1, a: [{h, t, r, x, m, p?, e?}]} (href, title, Rubrik, teaser, minutes, a PDF's first and last page), or {v: 1, a: [], none: true} for an issue without a usable table of contents (read as a whole). Built in the background for issues without one (not on e-ink: there when the issue is opened, or it comes from another device).

The states are in the meta too: issueRead: {href: 'r' | 's'}, issueAt: href (the article one was in). A change goes to the server after 2.5 s; offline it waits in localStorage.diora_issue_pending and goes with the next save (or when the connection is back).