Both documents had drifted away from the repository they describe: they pointed at fixtures and a helper script that are not present, documented a local-server workflow that is no longer used, and filed one page type under the wrong view. Following them meant setting up things that cannot work while trusting claims that no longer held. The README also explained every design decision before showing a single result, so the question a reader asks first — what does this actually look like — took the longest to answer. Screenshots and a short feature list now carry that job. The reasoning stays where it is needed, next to the code. Restricting the dev loader to the live site closes a side effect that only surfaced locally: its patterns matched page names common enough that the stylesheet would have leaked into unrelated local projects. The version number existed in two places and had already drifted apart. Reading it back from the metadata block leaves one place to change it.
26 KiB
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:
DEBUG-Flag undlog-Array- Weiche:
viewist'thread','liste','login'oder''— abgeleitet auslocation.pathname + location.search - Nur bei gesetztem
view: Viewport-Meta (idempotent ueber[data-rm]) und der komplette Style-Block als Template-Literal replaceBody(root)blendet alle bisherigenbody-Kinder aus und haengt die neue Ansicht anpageState(),pageSheet()undpageNav()— die Paginierung, von Liste und Thread benutzt, nicht vom LoginbuildList(),buildThread()beziehungsweisebuildLogin()lesen das Original-DOM und bauen eindiv.rm-root; alle haengen zum Schluss ueberaddCrumbs()die fixierte Brotkrumenleiste an, die zugleich ueberloginState()die Login/Logout-Pille traegt. Zwei der drei Ansichten verzweigen intern weiter, weil sich mehrere Seitenformen dieselbe URL teilen:buildList()versucht zuerstbuildSearchForm()(grosses Suchformular aufsearch.php), dannbuildSearchResults()(Trefferliste im Modus "Posts", Karten ueberbuildHit()); erst wenn beidefalseliefern, laeuft das gewoehnliche ZeileneinsammelnbuildLogin()faellt ohne Formular aufbuildLoginInfo()zurueck ("Information"-Vorlage nach dem Absenden)buildThread()legt ueberbuildThreadSearch()die zweite Leiste an, die die Lupe in den Brotkrumen pertoggleThreadSearch()ein- und ausblendet
run()waehlt die Ansicht und haengt beiDEBUGdas gruene Overlay an- Start ueber
DOMContentLoaded, fallsreadyState === '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 globaleimg-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-pagedhaelt so die 64px hinter der fixierten Leiste frei — gesetzt vonpageNav()selbst, weil es die Leiste nicht auf jeder Liste gibt. - Namen sind ansichtsspezifisch zu halten:
.rm-jumpund.rm-groupgehoeren der Liste,.rm-postund.rm-sigdem Thread. .rm-avbraucht!importantauf Breite und Hoehe, sonst gewinnt die globaleimg { 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.
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):
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 aufDOMContentLoadedgewartet und nichts gebaut. location.hrefist in JSDOM nicht ueberschreibbar. Fuer Navigationstests im eingelesenen Quelltextlocation.href = u;durch einen Hook ersetzen.requestAnimationFrameundscrollIntoViewfehlen 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.
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, keinlet/const. Template-Literals undSetsind in Gebrauch. Keine Transpilation, kein Linter. - Kommentare deutsch, Umlaute als
ae/oe/uetransliteriert. 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.