diora-web/docs/library.md
mrwnslz 3c7be76a93
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 9m0s
Test / e2e (push) Successful in 2m0s
Zeitschriften: Artikel richtig erkennen (GegenStandpunkt, PDF-Hefte)
- EPUB: ein Artikel ist eine Datei; die Einträge danach in dieselbe Datei
  sind seine Abschnitte, gleich auf welcher Ebene (beim GegenStandpunkt
  stehen Zwischenüberschriften teils ganz oben). Unterzeile = die Schlagzeile
  unter dem Titel, nicht nummerierte Abschnitte.
- PDF: die meisten Hefte haben keine Lesezeichen – die Artikel kommen jetzt
  aus dem gedruckten Inhaltsverzeichnis (Jacobin, NLR, LuXemburg,
  manuskripte): Seitenzahl vorn oder hinten, Autor in eigener Spalte oder
  darunter („Von …“), Titel in Versalien über mehrere Zeilen, Rubriken; die
  gedruckten Seitenzahlen werden über die Titel auf die PDF-Seiten abgebildet.
- Index-Version 2: schon gebaute Indizes werden neu gebaut.
sw.js: diora-v128.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:48:44 +02:00

145 lines
8.2 KiB
Markdown

# 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:
- **EPUB**: the table of contents, by file — an article is a file (the first
entry pointing into it); the entries after it into the same file are its
sections, whatever their depth (GegenStandpunkt puts some at the top level);
an entry whose children point into other files is a Rubrik. A one-file EPUB:
the leaves. No table of contents: each spine file with text. The teaser is
the headline under the title (the headings before the first paragraph that
the title doesn't say, not numbered sections), else the first paragraph.
- **PDF**: a real outline (word processors' `_Hlk…`/`_Toc…` leftovers don't
count); most magazines' PDFs have none, so mostly **the contents page
printed in the issue**: among the first 16 pages the one with most numbered
rows (numbers that merely count 1, 2, 3 … are a list, not a contents), plus
its neighbours with entries. A row: the page number at the start
("14 Der Krieg gegen Palästina", Jacobin), or at the end, maybe with the
author in a column before the title ("Nathan Sperber | Beyond Neoliberalism?
| 23", NLR); lines under it in its type size continue the title, an author
line ("Von …", a name) gives the author, lines in other type the teaser; a
title in capitals may begin on the lines above its number, a lone word on top
is its kicker (LuXemburg: GESPRÄCH, INFOGRAFIK); "Name: Titel"
(manuskripte). Capitals in the contents' type are Rubriken. The printed
numbers are mapped to the file's pages by the offset most titles agree on
(found in the page's text, a headline in capitals preferred); running heads
and the contents page's own head are dropped.
Reading times count 200 words a minute. It is kept in the encrypted meta as
`issue: {v: 2, a: [{h, t, r, x, m, a?, p?, e?}]}` (href, title, Rubrik, teaser,
minutes, author, a PDF's first and last page), or `{v: 2, a: [], none: true}`
for an issue without a usable table of contents (read as a whole). An index of
another version is built anew. 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).