Files
rin-mobile-userscript/CLAUDE.md
T
Andre 07b4a7437a feat: give the list view the same page navigation as the thread
The list built its own page bar from every link on the page carrying a
start parameter. Topic rows bring their own "Go to page: 1 … 42, 43" along,
so on the main forum that bar came to 103 entries, most of them jumping into
the middle of some unrelated thread rather than to a page of the list. Even
the correct entries were only the handful phpBB renders, which for 610 pages
means there is no way to reach page 300.

The thread already solved this: read per_page out of the page's own jumpto
function, compute every target, and offer them through a fixed bar and a
sheet. Both views paginate over the same parameter, so the bar, the sheet
and the state behind them now sit in one place and serve both. The list
leaves the bar out where a single page holds everything, which a thread of
posts never does but a category page always does.

Bottom clearance moved onto the bar for that reason — tied to the view, it
would reserve an empty strip under lists that have no bar.
2026-08-02 15:35:04 +02:00

202 lines
11 KiB
Markdown

# CLAUDE.md
Ein Userscript, das die phpBB-Seiten von `cs.rin.ru` fuer Safari auf iOS in eine
mobile Ansicht umbaut. Kein Build, keine Abhaengigkeiten, kein `package.json` —
die `.user.js`-Datei ist das Produkt.
| Datei | Greift auf | Zweck |
|---|---|---|
| `rin-mobile.user.js` | `*://cs.rin.ru/forum/*` | Beide Ansichten, Weiche nach Seitentyp |
| `dev/rin-mobile.dev.user.js` | zusaetzlich `localhost:*` | Loader, bindet per `@require file:///` das echte Script ein |
| `dev/viewtopic.php` | — | Fixture: gespeicherter Thread, 1114 Seiten |
| `dev/viewforum.php` | — | Fixture: Main Forum, 610 Seiten, mit Unterforen, Ankuendigungen und Stickies |
Die Fixtures sind ueber `.gitignore` (`dev/*.php`) ausgeschlossen. Nach einem
frischen Klon sind sie nicht da und muessen nach der Anleitung in der README neu
gespeichert werden — die Seite verlangt einen Login.
## Aufbau
Ein IIFE, streng der Reihe nach:
1. `DEBUG`-Flag und `log`-Array
2. Weiche: `view` ist `'thread'`, `'liste'` oder `''` — abgeleitet aus
`location.pathname + location.search`
3. Nur bei gesetztem `view`: Viewport-Meta (idempotent ueber `[data-rm]`) und
der komplette Style-Block als Template-Literal
4. `replaceBody(root)` blendet alle bisherigen `body`-Kinder aus und haengt die
neue Ansicht an
5. `pageState()`, `pageSheet()` und `pageNav()` — die Paginierung, von beiden
Ansichten benutzt
6. `buildList()` beziehungsweise `buildThread()` lesen das Original-DOM und
bauen ein `div.rm-root`; beide haengen zum Schluss ueber `addCrumbs()` die
fixierte Brotkrumenleiste an
7. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an
8. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'`
Auf Seiten ausserhalb der Weiche (`posting.php`, Profile, …) passiert nichts,
insbesondere wird kein Stylesheet injiziert.
## Fallen, die nicht aus dem Code hervorgehen
**Beide Ansichten teilen sich einen Klassennamensraum.** Das war der Grund fuer
die Zusammenlegung: als getrennte Scripts matchte `rin-mobile` auch
`viewtopic.php` und injizierte dort sein Stylesheet, obwohl es nichts baute.
Gleich benannte Regeln kollidierten mit identischer Spezifitaet, und die
Ladereihenfolge der Userscripts entschied ueber das Layout.
- Geteilt: `.rm-root`, `.rm-hidden`, `.rm-dbg`, die globale `img`-Regel
- Geteilt sind ausserdem die Leiste und ihr Sheet: `.rm-nav`, `.rm-sheet*`,
`.rm-goto`, `.rm-grid`
- Unterschiede zwischen den Ansichten ueber einen Modifier am Root loesen,
nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-paged` haelt
so die 64px hinter der fixierten Leiste frei — gesetzt von `pageNav()`
selbst, weil es die Leiste nicht auf jeder Liste gibt.
- Namen sind ansichtsspezifisch zu halten: `.rm-jump` und `.rm-group` gehoeren
der Liste, `.rm-post` und `.rm-sig` dem Thread.
- `.rm-av` braucht `!important` auf Breite und Hoehe, sonst gewinnt die globale
`img { height:auto !important }`-Regel gegen die Avatarmasse.
**Was nach `body` soll, muss nach `replaceBody()` angehaengt werden.**
Die Funktion markiert *alle* vorhandenen `body`-Kinder mit `.rm-hidden`. Das
Sheet wird deshalb erst beim ersten Antippen gebaut — das loest das Problem
nebenbei mit.
**In der Liste entscheidet die Dokumentreihenfolge ueber den Zeilentyp.**
`buildList()` sammelt Links auf `viewforum.php` und `viewtopic.php` in einem
Rutsch ein und ordnet jede `tr` nach ihrem ersten verwertbaren Link zu. Eine
Forenzeile verlinkt in der Spalte „letzter Beitrag" ebenfalls `viewtopic.php` —
zaehlte man Themenlinks zuerst, faellt die Zeile als Thema an, der Forenname
verschwindet und nur die Beschreibung bleibt als `.rm-last` uebrig. Genau so
war es vor 3.2. Brotkrumen und „Mark forums read" verlinken ebenfalls Foren,
stehen aber in Zeilen ohne Forenmerkmale und werden verworfen.
**Der Typ einer Themenzeile steht im Symbol, nicht am Titel.** `rinDark`
unterscheidet `global_read.png`, `announce_read.png`, `sticky_read.png` und
`topic_read.png` in der ersten Spalte, jeweils auch als `_locked`-Variante. Die
Trennzeilen der Seite (`<td class="row3" colspan="6"><b>Stickies</b>`) tragen
keinen Link und fallen beim Einsammeln durch; ihr Text ist trotzdem die beste
Beschriftung und wird ueber die Vorgaengerzeilen nachgeschlagen. Achtung beim
Zaehlen im Fixture: dieselben Symbole stehen unten noch einmal in der Legende,
ein Grep zaehlt also mehr, als es Zeilen gibt.
**Der Menuebalken der Seite verlinkt Themen.** „Forum rules" und die FAQ stehen
in einer Layoutzeile ohne Tabelle oder Script darin, die Bremse in `rowKind()`
greift dort also nicht. Ohne das Merkmal `a.topictitle` beziehungsweise zwei
Zahlenspalten wurde daraus eine Geisterkarte am Kopf jeder Liste — mit
Gruppenkoepfen faellt sie als eigener Abschnitt „Themen 1" auf.
**Der Gruppenkopf entfaellt hinter einer Kategoriezeile.** Sonst stuende auf
der Forenwurzel unter jeder Kategorie noch ein „FOREN n" — die Kategorie leitet
die Foren darunter bereits ein. Die Karten werden deshalb erst vollstaendig
gebaut und dann ausgegeben: die Anzahl im Kopf steht erst fest, wenn feststeht,
welche Zeilen ueberhaupt eine Karte hergeben.
**Das Seitengeruest ist selbst eine Tabelle.** `closest('tr')` liefert fuer
einen Link ausserhalb einer Listenzeile die Layoutzeile, die die ganze Seite
enthaelt. Ohne Bremse wurde daraus eine Karte mit dem kompletten Seitentext —
Kopfzeile, Stylesheet und Inline-Scripts inklusive — als Forenbeschreibung.
Zeilen mit `table`, `script`, `style` oder `form` darin sind deshalb keine
Kandidaten, und die Beschreibung wird nur aus den direkten Textknoten der
Titelzelle gebildet, nie aus deren gesamtem Text.
**Das Theme ist `rinDark`, nicht subsilver2.** Fuer die Forenliste waren die
Klassennamen aus der phpBB-Dokumentation (`forumlink`, `forumdesc`) deshalb
zunaechst nur eine Vermutung; `dev/viewforum.php` bestaetigt sie inzwischen. Der
Rueckfall auf die Form der Zeile bleibt trotzdem stehen — er kostet nichts und
traegt die Erkennung, wenn das Theme wechselt.
**Die Brotkrumenleiste ist fixiert, nicht sticky.** `.rm-root` traegt
`overflow:hidden` und wuerde damit zum Scrollport einer `position:sticky`
-Leiste, in dem nie gescrollt wird — sie klebte nie. Fixierte Nachfahren
klammert das `overflow` dagegen nicht, weil der Root keinen Containing Block
aufspannt. Die Freihaltung oben haengt an `body.rm-crumbed`, damit ohne
Brotkrumen im Markup kein leerer Streifen bleibt.
**`Page X of Y` steht als `Page <strong>1</strong> of <strong>1114</strong>`
im Markup.** Ein Grep im Roh-HTML nach `Page 1 of` findet nichts, die Regex auf
`txt(document.body)` greift trotzdem. Nicht vorschnell fuer kaputt halten.
**Seitenlinks stehen nicht nur im Paginierungsblock.** Jede Themenzeile mit
mehr als einer Seite bringt ihr eigenes „Go to page: 1 … 42, 43" mit, und diese
Links tragen ebenfalls `start=`. Die Liste baute ihre Seitenleiste bis 3.5 aus
allen `a[href*="start="]` der Seite und kam so auf 103 Eintraege, von denen die
meisten mitten in einen fremden Thread fuehrten. `pageState()` macht es richtig
und liest nur den Block um den `jumpto()`-Link.
**`per_page` kommt aus der `jumpto()`-Funktion der Seite**
(`var per_page = '15';`). Nur damit lassen sich Seiten-URLs als
`start = (Seite - 1) * per_page` selbst bauen. Die gerenderten Seitenlinks
taugen nicht als Quelle — phpBB zeigt bei 1114 Seiten nur `1, 2, 3, 4, 5 … 1114`.
Ohne `per_page` degradiert die Leiste auf erste Seite plus die
Original-Zurueck/Weiter-Links, das Sheet verschwindet.
**`postRow()` steigt bewusst mehrere `tr` hoch.** Der Beitragstext liegt in
einer verschachtelten Tabelle, `closest('tr')` trifft die innere Zeile ohne
Autor- und Profilspalte.
**`DEBUG` steht auf `true`** und muss vor dem Ausliefern auf `false`. Die
Versionsnummer steht doppelt: im `@version`-Header und im Text des
Debug-Overlays.
## Testen
Es gibt keine Testsuite. Bewaehrt hat sich ein Wegwerf-Harness mit jsdom im
Scratchpad (nicht im Repo ablegen — das Projekt soll abhaengigkeitsfrei
bleiben):
```js
const dom = new JSDOM(html, { url: 'https://cs.rin.ru/forum/viewtopic.php?f=10&t=68689&start=45' });
```
Drei Stolpersteine dabei:
- JSDOM parst asynchron fertig. Nach `eval(src)` rund 60 ms warten, sonst hat
das Script noch auf `DOMContentLoaded` gewartet und nichts gebaut.
- `location.href` ist in JSDOM nicht ueberschreibbar. Fuer Navigationstests im
eingelesenen Quelltext `location.href = u;` durch einen Hook ersetzen.
- `requestAnimationFrame` und `scrollIntoView` fehlen und muessen gestubbt
werden.
Sinnvolle Faelle: erste Seite, zweite Seite, Mitte, letzte Seite, dazu die
Degradation ohne `per_page` und ein Thread ohne Paginierung. Fixture-Eckdaten:
`f=10&t=68689`, `per_page = 15`, 1114 Seiten, letzte Seite `start=16695`.
Dieselben Faelle gelten seit 3.5 fuer die Liste, die sich die Leiste mit dem
Thread teilt. Eckdaten von `dev/viewforum.php`: `f=10`, `per_page = 100`, 610
Seiten, letzte Seite `start=60900`, dazu 4 Unterforen, 7 globale
Ankuendigungen, 2 Ankuendigungen, 8 Stickies und 92 Themen. Achtung beim
Nachbauen einer einseitigen Liste: die Seitenlinks zu entfernen genuegt nicht,
`Page 1 of 610` steht auch im Text und traegt die Gesamtzahl allein.
Fuer die Weiche und die Listenansicht genuegt eine handgeschriebene Tabelle mit
`a.topictitle`-Zeilen. Zu pruefen ist dabei auch eine Seite ausserhalb der
Weiche wie `posting.php`: dort darf weder eine Ansicht gebaut noch ein
`<style>` injiziert werden.
Fuer die Forenliste braucht es eine zweite Tabelle im subsilver2-Schnitt:
`td.cat > h4 > a` als Kategoriezeile, darunter Zeilen mit `a.forumlink`, drei
`p.forumdesc` (Beschreibung, Moderatoren, Unterforen), zwei Zahlenspalten und
einer Spalte „letzter Beitrag" mit Link auf `viewtopic.php`. Vier Faelle sind
sinnvoll: reine Forenliste, reine Themenliste, beides auf einer Seite, und
dieselbe Forenliste ohne die Klassen `forumlink`/`forumdesc` — die muss ueber
die Zahlenspalten dasselbe Ergebnis liefern.
Am Geraet zu pruefen bleibt, was Blink nicht zeigt: `env(safe-area-inset-*)`,
fixierte Leisten gegen Safaris ein- und ausfahrende Toolbar,
`-webkit-backdrop-filter`, Schriftskalierung. Details und die
Tampermonkey-Einrichtung stehen in der README.
## Konventionen
- **ES5-Stil im Script-Code:** `var`, `function`, keine Arrow Functions, kein
`let`/`const`. Template-Literals und `Set` sind in Gebrauch. Keine
Transpilation, kein Linter.
- **Kommentare deutsch, Umlaute als `ae`/`oe`/`ue` transliteriert.** Echte
Umlaute nur in Strings und Regexes, die im UI landen.
- Kommentare begruenden, warum etwas so gebaut ist (welche DOM-Eigenheit von
phpBB dahintersteckt), nicht was der Code tut.
- CSS-Klassen durchgaengig mit `rm-` praefixiert.
- Neue nutzersichtbare Funktionen in der README dokumentieren — zweisprachig,
siehe globale Anweisung.