mirror of
https://github.com/Andre-Bzk/rin-mobile-userscript.git
synced 2026-11-15 09:33:08 +01:00
The mobile view is assembled after DOMContentLoaded, long after the browser has settled on a scroll position, and the rebuild never touched it. A rebuilt thread stays taller than the viewport, so an inherited offset was never clamped back to zero and the thread opened somewhere inside its first post. Safari makes this visible where desktop Chrome does not: it restores the position of an address it has seen before and revises it again after load and after late images settle, so resetting once is not enough. Deciding what "top" means also required a target for addresses that carry a fragment. Post anchors existed only in the original markup, which the rebuild hides, so a jump from the topic list had nowhere to land and ended up wherever the browser happened to stand.
492 lines
29 KiB
Markdown
492 lines
29 KiB
Markdown
# CLAUDE.md
|
|
|
|
Ein Userscript, das die phpBB3-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/*` | Alle Ansichten, Weiche nach Seitentyp |
|
|
| `dev/rin-mobile.dev.user.js` | `*://cs.rin.ru/forum/*` | Loader, bindet per `@require file:///` das echte Script ein |
|
|
| `dev/viewtopic.php` | — | Fixture: gespeicherter Thread, `per_page=15`, 1114 Seiten |
|
|
| `dev/viewforum.php` | — | Fixture: Main Forum, `per_page=100`, 610 Seiten, mit Unterforen, Ankuendigungen und Stickies |
|
|
| `dev/index.php` | — | Fixture: Forumwurzel, zwei Kategorien mit Forenzeilen, keine Paginierung, eingeloggt aufgenommen |
|
|
| `dev/ucp.php` | — | Fixture: Login-Formular (`mode=login`), nur ausgeloggter Zustand |
|
|
| `dev/search.php` | — | Fixture: Suchformular ohne Treffer, eingeloggt aufgenommen |
|
|
| `dev/search2.php` | — | Fixture: Suchtreffer im Modus "Posts" (15 von 1438 Treffern, `t=63042`, `per_page=15`, 96 Seiten) |
|
|
|
|
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.
|
|
|
|
Der Loader greift ausschliesslich auf die echte Seite. Die Fixtures sind
|
|
Markup-Vorlagen fuer das jsdom-Harness und zum Nachlesen, keine Seiten zum
|
|
Anschauen im Browser — die Layoutarbeit laeuft in Chrome auf `cs.rin.ru`
|
|
selbst. Sie lokal auszuliefern verlangte `@include`-Regeln fuer `localhost`,
|
|
und die passen auf Seitennamen, die dort haeufig genug vorkommen (`index.php`,
|
|
alles mit `search` darin), dass der globale Teil des Stylesheets
|
|
(`html, body`, `img`, jeweils `!important`) in fremde lokale Projekte geriete.
|
|
Das Nachziehen waere billig, der Fehlersuchweg dorthin nicht.
|
|
|
|
Fuer den Zustand nach erfolgreichem Login (`ucp.php?mode=login` ohne Formular,
|
|
"Information"-Vorlage) liegt derzeit keine Fixture im Repo. `buildLoginInfo()`
|
|
wurde gegen einen Mitschnitt gebaut, der nicht mehr vorhanden ist; wer den
|
|
Zweig anfassen will, muss die Seite neu aufnehmen.
|
|
|
|
## Aufbau
|
|
|
|
Ein IIFE, streng der Reihe nach:
|
|
|
|
1. `DEBUG`-Flag und `log`-Array
|
|
2. Weiche: `view` ist `'thread'`, `'liste'`, `'login'` 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 Liste
|
|
und Thread benutzt, nicht vom Login
|
|
6. `buildList()`, `buildThread()` beziehungsweise `buildLogin()` lesen das
|
|
Original-DOM und bauen ein `div.rm-root`; alle haengen zum Schluss
|
|
ueber `addCrumbs()` die fixierte Brotkrumenleiste an, die zugleich ueber
|
|
`loginState()` die Login/Logout-Pille traegt. Zwei der drei Ansichten
|
|
verzweigen intern weiter, weil sich mehrere Seitenformen dieselbe URL
|
|
teilen:
|
|
- `buildList()` versucht zuerst `buildSearchForm()` (grosses Suchformular
|
|
auf `search.php`), dann `buildSearchResults()` (Trefferliste im Modus
|
|
"Posts", Karten ueber `buildHit()`); erst wenn beide `false` liefern,
|
|
laeuft das gewoehnliche Zeileneinsammeln
|
|
- `buildLogin()` faellt ohne Formular auf `buildLoginInfo()` zurueck
|
|
("Information"-Vorlage nach dem Absenden)
|
|
- `buildThread()` legt ueber `buildThreadSearch()` die zweite Leiste an,
|
|
die die Lupe in den Brotkrumen per `toggleThreadSearch()` ein- und
|
|
ausblendet
|
|
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 verlinken ebenfalls Foren, stehen aber in Zeilen
|
|
ohne Forenmerkmale und werden verworfen.
|
|
|
|
**„Mark topics read" braucht eine eigene Bremse, „Mark forums read" nicht.**
|
|
Beide sind reine Aktionslinks, keine Inhaltslinks, aber nur „Mark forums read"
|
|
zeigt auf `index.php` und faellt schon beim Einsammeln durch, weil das keine
|
|
der gesuchten Adressen ist. „Mark topics read" oberhalb der Themenliste zeigt
|
|
dagegen mit `?mark=topics` auf dieselbe `viewforum.php`-Seite und passt damit
|
|
auf den Sammel-Selektor. Sein Link liegt zwar in einer eigenen inneren `tr`
|
|
ohne verschachtelte Tabelle, `a.closest('td.cat')` findet aber trotzdem die
|
|
aeussere Kategoriezelle drumherum — bis 3.7 wurde daraus eine dauerhafte,
|
|
nicht einklappbare `.rm-sec`-Karte oberhalb von Global Announcements statt gar
|
|
keiner. `rowKind()` verwirft seit 3.8 jeden Link mit `?mark=` oder `&mark=`
|
|
in der Adresse, bevor die uebrige Klassifizierung ueberhaupt beginnt. Das
|
|
Repository hat keine eingeloggte `viewforum.php`-Fixture, die diesen Link mit
|
|
echtem Markup zeigt — geprueft ist die Bremse nur gegen den vermuteten
|
|
`mark=topics`-Parameter, per Texteinfuegung in `dev/viewforum.php`.
|
|
|
|
**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. `rinDark` ist allerdings eine
|
|
Ableitung von subsilver2 und laedt dessen
|
|
`styles/subsilver2/template/forum_fn.js` mit — Klassennamen aus subsilver2 sind
|
|
deshalb eine brauchbare erste Annahme, wenn eine Seite noch keine Fixture hat.
|
|
|
|
**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.
|
|
|
|
**Die Login-Erkennung ist inzwischen gegen eine echte Sitzung geprueft.**
|
|
`loginState()` unterscheidet `a[href*="mode=login"]` von
|
|
`a[href*="mode=logout"]` im Menuebalken und liest den Namen aus dem Muster
|
|
`Logout [ Name ]` im Linktext. `dev/index.php`, `dev/search.php` und
|
|
`dev/search2.php` sind eingeloggt aufgenommen (`Logout [ <Benutzername> ]`) und
|
|
bestaetigen das Muster.
|
|
Der Fallback bleibt trotzdem stehen: passt das Muster einmal nicht — andere
|
|
Sprache, anderer Klammertyp — zeigt die Pille „Logout" ohne Namen, statt eine
|
|
Ausnahme zu werfen.
|
|
|
|
**`ucp.php?mode=login` rendert nach dem Absenden dieselbe URL neu, diesmal
|
|
ohne Formular.** phpBB fuellt die Zwischenmeldung („You have been successfully
|
|
logged in.") in dieselbe generische „Information"-Vorlage, die auch fuer
|
|
andere Meldungen auf dieser Seite dient (vermutlich auch Login-Fehler, dafuer
|
|
liegt aber keine Fixture vor). `buildLoginInfo()` erkennt die Vorlage deshalb
|
|
an ihrer Form — eine Kopfzeile mit genau einer `th`-Zelle, gefolgt von einer
|
|
`td.row1`-Nachrichtenzelle — nicht am Wortlaut, und uebernimmt die Zelle
|
|
unveraendert wie das Formular selbst. Findet `buildLogin()` weder Formular
|
|
noch diese Form, baut es nichts; die Originalseite bleibt sichtbar. Achtung:
|
|
die Fixture, gegen die das gebaut wurde, liegt nicht mehr im Repo — der Zweig
|
|
ist derzeit nicht nachpruefbar, ohne die Seite neu aufzunehmen.
|
|
|
|
**`index.php` wird ueber `location.pathname` erkannt, nicht ueber
|
|
`here`.** Der Login-Redirect haengt "index.php" gelegentlich als Wert eines
|
|
Query-Parameters an `ucp.php` (`redirect=index.php`, siehe `dev/ucp.php`).
|
|
Ein Test auf `location.pathname + location.search` wie bei den uebrigen
|
|
Weichen wuerde eine solche Login-URL faelschlich als Liste einordnen, noch
|
|
bevor die Pruefung auf `ucp.php` ueberhaupt greift.
|
|
|
|
**Ist eine Seite nicht laenger als der Viewport, kann Safari nicht wegscrollen,
|
|
um seine eigene Werkzeugleiste unten freizugeben.** Bei Liste und Thread faellt
|
|
das nicht auf, weil Karten beziehungsweise Beitraege fast immer laenger sind
|
|
als der Bildschirm. Der Login hat kein `.rm-nav`, das sonst Platz reserviert,
|
|
und war als kurze Formularseite der erste Fall, bei dem die Seite genau so
|
|
hoch wie der Viewport enden konnte — die teilweise eingefahrene Leiste blieb
|
|
dann stehen und verdeckte den Submit-Button, ohne dass noch etwas zu scrollen
|
|
war, um sie wegzuschieben. `env(safe-area-inset-bottom)` allein loest das
|
|
nicht, das federt nur den Home-Indicator ab. Der Bodensatz unter dem Formular
|
|
in `.rm-login form` ist deshalb absichtlich groesser als noetig: er erzwingt
|
|
Scrollhoehe ueber den Viewport hinaus.
|
|
|
|
**`buildLogin()` baut das Formular nicht nach, sondern verschiebt es.** Wie der
|
|
Beitragstext im Thread (`bodyWrap.appendChild(pb)`) wandert das
|
|
Original-`<form>` unveraendert in `.rm-root` und wird nur per CSS auf
|
|
Block-Elemente umgelegt. Nachbauen wuerde bedeuten, `sid`, das doppelte
|
|
`redirect`-Feld und `autologin`/`viewonline` einzeln nachzuziehen — verliert
|
|
man dabei eines, scheitert der Login stumm oder landet nicht dort, wo er soll.
|
|
Aus demselben Grund faengt `buildLogin()` keine Fehlermeldung eines
|
|
gescheiterten Logins ab: ohne Fixture eines fehlgeschlagenen Versuchs waere die
|
|
Erkennung geraten. Fehlt das Formular (`input[name="username"]`), baut die
|
|
Funktion nichts und die Originalseite bleibt sichtbar.
|
|
|
|
**`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.
|
|
|
|
**Der Umbau erbt die Scrollposition, die der Browser vorher gesetzt hat.**
|
|
`replaceBody()` laeuft auf `DOMContentLoaded` und ruehrt `scrollY` nicht an;
|
|
weil ein Thread auch danach laenger als der Viewport bleibt, klemmt der Browser
|
|
sie nie auf 0 zurueck. Auf dem iPhone startete der Thread deshalb mitten im
|
|
ersten Beitrag: iOS Safari stellt die Position einer schon besuchten Adresse
|
|
von sich aus wieder her und verankert sie ausserdem beim Relayout auf
|
|
`width=device-width`. Im Chrome-Debugging faellt das nicht auf, weil dort jede
|
|
Adresse frisch geladen wird und es gar keine Position zu uebernehmen gibt. Zwei
|
|
Bremsen dagegen: `history.scrollRestoration = 'manual'` direkt beim
|
|
Script-Start — spaeter waere es zu spaet, Safari hat dann laengst gescrollt —
|
|
und `holdStart()` am Ende von `run()`.
|
|
|
|
Ein einzelner Reset auf `DOMContentLoaded` reichte nicht — am Geraet
|
|
widerlegt: Safari legt die Position nicht in einem Rutsch fest, sondern
|
|
springt nach `load` und nach dem Layout nachgeladener Bilder noch einmal nach
|
|
und ueberschreibt den Reset. `holdStart()` haelt den Anfang deshalb fest —
|
|
zwoelf Versuche im 50-ms-Takt plus einer auf `load` fuer den Sprung, der erst
|
|
nach dem letzten Bild kommt. Die Bremse loest bei der ersten eigenen Geste des
|
|
Lesers aus (`touchstart`/`wheel`/`keydown`, jeweils `once`), sonst risse sie
|
|
ihn mitten im Lesen zurueck. `scroll` taugt als Signal dafuer nicht:
|
|
`toStart()` loest selbst eines aus und wuerde sich sofort selbst abschalten.
|
|
|
|
**Die Post-Anker der Originalseite muessen auf die Karten wandern.** Ein
|
|
Beitrag traegt sein Sprungziel als `<a name="pXXXX">` in der Autorenzelle, und
|
|
die verschwindet mit dem Original-DOM. Bis 3.14 hatte `#pXXXX` deshalb kein
|
|
Ziel mehr — der „Neueste ↓"-Link aus der Themenliste landete nicht am Beitrag,
|
|
sondern dort, wo der Browser beim Umbau zufaellig stand. `buildThread()`
|
|
uebernimmt den Namen jetzt als `id` der `.rm-post`-Karte, und `toStart()`
|
|
springt darauf statt an den Anfang, wenn die Adresse ein Fragment traegt.
|
|
`scroll-margin-top` auf `.rm-post` haelt dabei die fixierte Brotkrumenleiste
|
|
frei, sonst steht der Kartenkopf dahinter.
|
|
|
|
**Die Lupe und die Login-Pille haengen an einer gemeinsamen `margin-left:auto`
|
|
auf einem Wrapper (`.rm-auth-group`), nicht je an sich selbst.** Saesse die
|
|
Regel wie zuvor direkt auf `.rm-auth`, wuerde nur die Pille an den Rand
|
|
geschoben und die Lupe bliebe links davon kleben — mit sichtbarer Luecke
|
|
dazwischen statt „direkt links neben der Pille". Die Lupe existiert im Markup
|
|
ohnehin nur bei bestehender Sitzung; ausgeloggt bleibt der Wrapper mit der
|
|
Pille als einzigem Kind und verhaelt sich exakt wie vorher.
|
|
|
|
**`#topic-search` steht dreifach identisch im Thread-Markup** (Kopf, Fuss,
|
|
ueber der Beitragsliste), jedes Mal mit derselben, eigentlich unzulaessigen
|
|
`id`. `document.querySelector('#topic-search')` trifft einfach das erste und
|
|
verschiebt es unveraendert in die zweite Leiste — alle drei tragen dieselben
|
|
versteckten Felder (`t`, `sf`, `terms`), welches der drei benutzt wird, ist
|
|
gleichgueltig.
|
|
|
|
**`select[name="fid[]"]` ist der einzige verlaessliche Marker fuer das grosse
|
|
Suchformular auf `search.php`.** Sowohl `viewforum.php` als auch
|
|
`viewtopic.php` betten ein eigenes Mini-Suchformular ein (`#forum-search` /
|
|
`#topic-search`, „Search this forum…" / „Search this topic…"), beide mit
|
|
`action="./search.php"` und einem Feld `name="keywords"` — nach diesen Merkmalen
|
|
allein liesse sich das grosse Formular nicht von den kleinen unterscheiden.
|
|
Der mehrfach waehlbare Forenwaehler existiert dagegen nur im grossen Formular.
|
|
|
|
**Die Vorbelegung des Forenwaehlers haengt an der harten Forum-ID 10.**
|
|
`buildSearchForm()` waehlt `option[value="10"]` (Main Forum) vor und setzt
|
|
`input[name="sc"][value="0"]` (Unterforen: No), weil das serverseitige
|
|
Formular sonst mit leerem Waehler und „Search subforums: Yes" startet. Beides
|
|
ist eine Bequemlichkeit, keine Funktion: vergibt das Forum die IDs neu oder
|
|
benennt es das Hauptforum um, greift die Vorauswahl stumm nicht mehr — das
|
|
Formular bleibt bedienbar, steht dann aber wieder auf der Servervorgabe. Kein
|
|
Fehlerfall, aber auch nichts, was von selbst auffaellt.
|
|
|
|
**Die Textfelder des Suchformulars tragen `style="width: 300px"` direkt im
|
|
Markup.** Eine externe Regel ohne `!important` sticht dagegen nicht, egal wie
|
|
spezifisch der Selektor ist — `buildSearchForm()` entfernt das
|
|
`style`-Attribut deshalb, statt gegen es anzuschreiben.
|
|
|
|
**Eine abgeschickte Suche liefert keine Themenliste, sondern einen ganz
|
|
anderen Aufbau.** Die anfaengliche Annahme, Trefferzeilen tragen dieselben
|
|
`topictitle`-Links wie eine Themenliste und laufen unveraendert durch
|
|
`buildList()`, stimmte nicht — zumindest nicht im Modus "Posts" (`sr=posts`),
|
|
der ueber die zweite Leiste im Thread entsteht, weil deren Formular kein `sr`
|
|
setzt. Jeder Treffer ist stattdessen ein Beitragsauszug: Autor, Zeitstempel,
|
|
Forum/Thema-Kontext, ein Stueck `postbody`. `buildSearchResults()` erkennt
|
|
diesen Modus an `p.topictitle` — als `<p>`, nicht als `<a>` wie das
|
|
gleichnamige Klassenpendant einer Themenzeile in `viewforum.php` — und baut
|
|
daraus eigene Karten (`buildHit()`), bevor `buildList()`s allgemeines
|
|
Zeileneinsammeln ueberhaupt anfaengt und die enthaltenen `viewtopic.php`-Links
|
|
falsch klassifizieren wuerde. Ungeprueft bleibt der Modus "Topics" (`sr=topics`,
|
|
Vorauswahl im grossen Suchformular) — dafuer liegt keine Fixture vor, dort
|
|
sehen Treffer laut phpBB-Doku tatsaechlich wie topictitle-Zeilen aus und
|
|
wuerden vermutlich durch den regulaeren Listenzweig laufen, das ist aber nicht
|
|
verifiziert.
|
|
|
|
**Ein Treffer im Modus "Posts" besteht aus drei `<tr>`, unterscheidbar nur an
|
|
der Position, nicht an einer Klasse.** Titelzeile (`p.topictitle`), Autor-
|
|
und Betreffzeile, dann Antworten/Views samt Beitragsauszug — alle drei
|
|
tragen row1/row2 wahllos, die einzige verlaessliche Zuordnung ist
|
|
`nextElementSibling` von der Titelzeile aus.
|
|
|
|
**Der Beitragsauszug haengt in `<td class="postbody">`, nicht in
|
|
`div`/`span.postbody` wie im echten Thread.** `buildHit()` verschiebt das
|
|
`<td>` trotzdem unveraendert per `appendChild()` in `.rm-body` — das ist reine
|
|
DOM-Manipulation, keine Neuparse, darum meckert der Browser nicht ueber ein
|
|
`<td>` ausserhalb einer Tabelle. Es rendert danach aber weiterhin als
|
|
`display:table-cell`, wenn `.rm-root.rm-hits .rm-body > td` das nicht
|
|
ausdruecklich auf `block` zwingt.
|
|
|
|
**"Replies: 5403" und "Views: 951837" stehen im Markup nur durch ein `<br />`
|
|
getrennt, ohne Leerzeichen.** `txt()` normalisiert Whitespace, erzeugt aber
|
|
keinen wo im Original-Markup keiner steht — `txt(meta)` liefert deshalb
|
|
`5403Views:951837` statt zwei getrennter Werte. `buildHit()` greift beide
|
|
Zahlen einzeln per Regex heraus und reicht sie an dieselbe `metaRow()` wie bei
|
|
Themenkarten weiter, die den Trenner per CSS (`::before`) selbst einfuegt.
|
|
|
|
**`input[type="submit"]`/`input[type="reset"]` setzen eigenes Padding und
|
|
Zentrierung in iOS Safari unzuverlaessig um — auch mit `-webkit-appearance:
|
|
none`.** Zuerst aufgefallen bei Suchen/Zuruecksetzen im Suchformular
|
|
nebeneinander, wo die feste Flex-Aufteilung die Verzerrung besonders sichtbar
|
|
machte: zu schmale Buttons, Text nicht mittig. `-webkit-appearance:none` plus
|
|
`box-sizing:border-box`/`line-height:normal` (erster Versuch) reichte laut
|
|
Test am Geraet nicht aus — das legt einen tieferen WebKit-Bug nahe, bei dem
|
|
`input[type=submit/reset]` die native Knopfoptik trotz `appearance:none` nicht
|
|
vollstaendig ablegt. Der tragfaehige Fix ist `inputToButton()`: die
|
|
Original-`<input>`-Elemente werden vor dem Verschieben durch echte
|
|
`<button type="submit/reset">` ersetzt (Name und Value wandern mit, am
|
|
uebermittelten Formularfeld aendert sich nichts) — `<button>` wird von WebKit
|
|
unter `appearance:none` zuverlaessig respektiert. Betrifft grundsaetzlich
|
|
jeden Submit-Button im Script: Suchformular, Login, Thread-Suchleiste nutzen
|
|
alle `inputToButton()`. Ungeprueft, ob `-webkit-appearance:none` allein bei
|
|
einer anderen iOS-Version genuegt haette — der Wechsel auf `<button>` umgeht
|
|
die Frage, statt sie zu beantworten.
|
|
|
|
**`DEBUG` muss vor dem Ausliefern auf `false` stehen** (aktuell tut es das).
|
|
Solange es auf `true` steht, haengt `run()` das gruene Overlay an — das ist
|
|
der einzige Effekt des Flags, ein vergessenes `true` bricht nichts, sieht auf
|
|
dem Geraet aber unfertig aus.
|
|
|
|
**Die Versionsnummer steht nur im `@version`-Header.** Das Overlay liest sie
|
|
ueber `GM_info.script.version` zurueck, statt sie ein zweites Mal als Literal
|
|
zu fuehren — genau das lief zwischen 3.13 und 3.14 auseinander, ohne dass es
|
|
jemandem auffiel, weil das Overlay im Auslieferzustand ohnehin aus ist. Wer
|
|
den Overlay-Text anfasst: keinen neuen Versionsliteral einbauen. Fehlt
|
|
`GM_info` (Manager ohne diese API), zeigt das Overlay den Namen ohne Version;
|
|
das ist der gewollte Rueckfall, weil eine fehlende Angabe harmloser ist als
|
|
eine falsche.
|
|
|
|
## 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`.
|
|
Am Beitrag selbst zu pruefen: alles hinter dem ersten `<hr>` im `postbody`
|
|
landet als zugeklapptes `details` („Signatur") am Kartenfuss, Beitraege ohne
|
|
`<hr>` behalten ihren Text vollstaendig im Koerper.
|
|
|
|
Fuer den Sprung an den Threadanfang genuegt derselbe Fixture zweimal, einmal
|
|
mit und einmal ohne Fragment in der Adresse: ohne Fragment darf nur
|
|
`scrollTo(0, 0)` fallen, mit `#pXXXX` nur ein `scrollIntoView()` auf genau der
|
|
Karte mit dieser `id` — und jede der 15 Karten muss eine tragen. Fuer
|
|
`holdStart()` kommen drei zeitabhaengige Faelle dazu: dass ohne Zutun weitere
|
|
Resets nachkommen, dass ein abgesetztes `touchstart` sie sofort stoppt, und
|
|
dass `load` nach dem Ende der Schleife noch einmal greift, aber nach einer
|
|
eigenen Geste eben nicht mehr. Achtung: jsdom
|
|
kennt `history.scrollRestoration` nicht, das Script laeuft dort stumm in seinen
|
|
Rueckfall. Wer den Zweig pruefen will, definiert die Eigenschaft vor `eval()`
|
|
selbst auf `window.history`. `scrollTo`, `scrollIntoView` und
|
|
`requestAnimationFrame` fehlen ohnehin und muessen gestubbt werden.
|
|
|
|
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.
|
|
|
|
Fuer den Login genuegt `dev/ucp.php` unveraendert: geprueft werden das
|
|
uebernommene Formular (Username-/Passwortfeld, `sid`, `redirect`, Submit), der
|
|
Regeltext als zugeklapptes `details` („Regeln") und die Pille im ausgeloggten
|
|
Zustand. Fuer den eingeloggten Zustand der Pille `dev/index.php`,
|
|
`dev/search.php` oder `dev/search2.php` laden — alle drei sind eingeloggt
|
|
aufgenommen und tragen das echte `Logout [ Name ]`-Markup.
|
|
|
|
Die Meldung nach dem Login (`buildLoginInfo()`) ist mangels Fixture nicht
|
|
nachpruefbar. Wer sie anfasst, nimmt `ucp.php?mode=login` nach einem echten
|
|
Login neu auf; zu pruefen waeren dann Titel, uebernommene Nachricht samt Link
|
|
und die Pille — und dass `dev/ucp.php` (das Formular, gleiche URL) weiterhin
|
|
im Formular-Zweig landet und nicht in die Meldungs-Erkennung faellt.
|
|
|
|
Fuer die Forumwurzel genuegt `dev/index.php`, URL `index.php?sid=…`: zwei
|
|
Kategorien mit zusammen 16 Forenkarten, keine Paginierung. Zu pruefen ist
|
|
zusaetzlich eine `ucp.php?mode=login&redirect=index.php`-URL — sie muss trotz
|
|
des `index.php`-Substrings im Login-Zweig landen, nicht im Listen-Zweig.
|
|
|
|
Fuer das Suchformular genuegt `dev/search.php` unveraendert, URL
|
|
`search.php`: geprueft werden der Forenwaehler als Erkennungsmerkmal, das
|
|
verschobene Formular samt versteckter Felder (`t`, `sid`), die entfernten
|
|
inline-`style`-Attribute der Textfelder und die Schnellzugriffs-Chips aus
|
|
`p.searchbar`. Zu pruefen ist ausserdem `dev/viewforum.php` auf derselben
|
|
Weiche: dessen eingebettetes `#forum-search`-Mini-Formular teilt sich Aktion
|
|
und `keywords`-Feld mit dem grossen Suchformular und darf `buildSearchForm()`
|
|
nicht faelschlich ausloesen, die Forenliste muss wie gewohnt Karten liefern.
|
|
|
|
Fuer Suchtreffer genuegt `dev/search2.php` unveraendert, URL beliebig (die
|
|
Erkennung haengt nicht an `location`, sondern an `p.topictitle` im Markup):
|
|
geprueft werden alle 15 Trefferkarten, die verschobene "Searched topic /
|
|
Search term used"-Zelle samt ihrer beiden Links, Autor und Zeitstempel pro
|
|
Treffer, die per Regex getrennten Antworten-/Views-Zahlen, der verschobene
|
|
`td.postbody`-Beitragsauszug, der „Zum Beitrag ↓"-Link am Kartenfuss und die
|
|
Seitennavigation (`per_page=15`, 96 Seiten). Zu pruefen ist ausserdem, dass `dev/search.php` (das leere Formular)
|
|
weiterhin den Formular-Zweig trifft und nicht in die Treffer-Erkennung faellt,
|
|
und dass `dev/viewforum.php` weiterhin normale Karten liefert — beide haben
|
|
kein `p.topictitle`. Der Modus "Topics" (`sr=topics`) ist mangels Fixture
|
|
ungeprueft.
|
|
|
|
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.
|