mirror of
https://github.com/Andre-Bzk/rin-mobile-userscript.git
synced 2026-11-15 09:33:08 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e459973149 | ||
|
|
2ea94f8a2a | ||
|
|
c477537826 |
@@ -1,18 +1,17 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Ein Userscript, das die phpBB-Seiten von `cs.rin.ru` fuer Safari auf iOS in eine
|
||||
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/*` | 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 |
|
||||
| `dev/index.php` | — | Fixture: Forumwurzel, zwei Kategorien mit Forenzeilen, keine Paginierung |
|
||||
| `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/after_login/ucp.php` | — | Fixture: dieselbe URL nach erfolgreichem Login, "Information"-Vorlage statt Formular |
|
||||
| `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) |
|
||||
|
||||
@@ -20,6 +19,20 @@ 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:
|
||||
@@ -34,9 +47,20 @@ Ein IIFE, streng der Reihe nach:
|
||||
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 drei haengen zum Schluss
|
||||
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
|
||||
`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'`
|
||||
|
||||
@@ -125,7 +149,10 @@ Titelzelle gebildet, nie aus deren gesamtem Text.
|
||||
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.
|
||||
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`
|
||||
@@ -137,8 +164,9 @@ 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` und `dev/after_login/ucp.php`
|
||||
sind eingeloggt aufgenommen (`Logout [ <Benutzername> ]`) und bestaetigen das 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.
|
||||
@@ -151,7 +179,9 @@ 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.
|
||||
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
|
||||
@@ -205,6 +235,38 @@ Original-Zurueck/Weiter-Links, das Sheet verschwindet.
|
||||
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
|
||||
@@ -228,6 +290,15 @@ Suchformular auf `search.php`.** Sowohl `viewforum.php` als auch
|
||||
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
|
||||
@@ -289,9 +360,19 @@ 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` steht auf `true`** und muss vor dem Ausliefern auf `false`. Die
|
||||
Versionsnummer steht doppelt: im `@version`-Header und im Text des
|
||||
Debug-Overlays.
|
||||
**`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
|
||||
|
||||
@@ -315,6 +396,22 @@ Drei Stolpersteine dabei:
|
||||
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
|
||||
@@ -337,16 +434,17 @@ 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) und
|
||||
die Pille im ausgeloggten Zustand. Fuer den eingeloggten Zustand der Pille
|
||||
`dev/index.php` oder `dev/after_login/ucp.php` laden — beide sind eingeloggt
|
||||
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.
|
||||
|
||||
Fuer die Meldung nach dem Login genuegt `dev/after_login/ucp.php`
|
||||
unveraendert, URL `ucp.php?mode=login`: geprueft werden Titel, uebernommene
|
||||
Nachricht samt Link und die Pille. Zu pruefen ist ausserdem, dass `dev/ucp.php`
|
||||
(das Formular, gleiche URL) weiterhin die Formular-Zweig traf und nicht in die
|
||||
Meldungs-Erkennung faellt.
|
||||
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
|
||||
@@ -360,15 +458,15 @@ 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 auslösen, die Forenliste muss wie gewohnt Karten liefern.
|
||||
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 und die Seitennavigation (`per_page=15`, 96
|
||||
Seiten). Zu pruefen ist ausserdem, dass `dev/search.php` (das leere Formular)
|
||||
`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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# iOS Safari Userscripts — cs.rin.ru
|
||||
# RIN Mobile — cs.rin.ru for iOS Safari
|
||||
|
||||
[English](#english) | [Deutsch](#deutsch)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## English
|
||||
|
||||
A single userscript that rebuilds the phpBB2 pages of `cs.rin.ru` into a mobile
|
||||
A single userscript that rebuilds the phpBB3 pages of `cs.rin.ru` into a mobile
|
||||
layout for Safari on iOS. It replaces the original table layout entirely: the
|
||||
existing page content is hidden and a freshly built DOM is appended to `<body>`.
|
||||
|
||||
@@ -15,111 +15,57 @@ page type:
|
||||
|
||||
| Page | View |
|
||||
|---|---|
|
||||
| `viewforum.php`, `search.php`, `index.php`, forum root | Topic and forum list as cards |
|
||||
| `viewforum.php`, `index.php`, forum root | Topic and forum list as cards |
|
||||
| `search.php` | The query form restyled, or — for a submitted search in "Posts" mode — its hits as cards |
|
||||
| `viewtopic.php` | Thread as a post stream with a fixed bottom navigation bar |
|
||||
| `ucp.php?mode=login` | Login form, restyled in place; on the same URL after a successful submit, the resulting information page instead |
|
||||
| anything else | Untouched — no styles are injected |
|
||||
|
||||
A `viewforum.php` page is not necessarily a list of topics: a category page
|
||||
lists forums instead, and a forum page can carry both. Each table row is
|
||||
therefore classified by its first usable link. Forum rows become cards with
|
||||
name, description, subforum links and counts; topic rows keep their existing
|
||||
card; category headers become section headings. Rows are recognised by phpBB's
|
||||
own `forumlink` / `forumdesc` classes, and failing that by their shape — two
|
||||
purely numeric columns are only ever a forum's topic and post count.
|
||||
### Screenshots
|
||||
|
||||
Forums, global announcements, announcements, stickies and topics all end up in
|
||||
the same stream of cards, which loses the separation the original page draws
|
||||
between them. A header strip naming the section and its card count is therefore
|
||||
inserted wherever the kind changes, and forum cards additionally carry a
|
||||
coloured left edge. The label is taken from the board's own separator row
|
||||
(`Global Announcements`, `Stickies`, `Topics`), so sections this script does not
|
||||
know are named correctly too. Which kind a topic row is comes from the icon in
|
||||
its first column, not from its title. The strip is left out directly below a
|
||||
category heading, which already introduces the forums under it.
|
||||
| Forum list, with the sections above the topics collapsed | Thread, with the in-topic search bar revealed |
|
||||
|---|---|
|
||||
| <img src="docs/Screenshot_mobile_1.png" alt="Forum list as cards: breadcrumb bar with magnifier, an open FOREN section with four forum cards, collapsed GLOBAL ANNOUNCEMENTS, ANNOUNCEMENTS and STICKIES strips, and the page bar showing 1 / 610" width="100%"> | <img src="docs/Screenshot_mobile_2.png" alt="Thread view: breadcrumbs, a second bar with a search field, and a post card with avatar, author, rank and post count above the message" width="100%"> |
|
||||
| **Search form, top: quick links and forum picker** | **Search form, bottom: options and buttons** |
|
||||
| <img src="docs/Screenshot_mobile_3.png" alt="Search form: quick links as chips, the keyword fields and the forum picker with Main Forum preselected" width="100%"> | <img src="docs/Screenshot_mobile_4.png" alt="Search form continued: search subforums set to No, the remaining option groups, and the Search and Reset buttons" width="100%"> |
|
||||
|
||||
Tapping a header collapses its section. Global announcements, announcements and
|
||||
stickies start collapsed: they precede the topics on *every* page of a forum and
|
||||
otherwise fill the first screen. The state is stored per kind in `localStorage`,
|
||||
so collapsing stickies once applies to every forum. `localStorage` is used
|
||||
rather than `GM_setValue` because it needs no `@grant` and behaves the same in
|
||||
Tampermonkey and in the iOS app; the trade-off is that Safari may clear
|
||||
script-writable storage after long inactivity, which resets the sections to
|
||||
their default.
|
||||
### What it does
|
||||
|
||||
Both views share the same fixed navigation bar at the bottom: first / previous /
|
||||
next / last and a page indicator. Tapping the indicator opens a sheet with a
|
||||
number field and a grid of all pages. Targets are computed as
|
||||
`start = (page - 1) × per_page` rather than taken from the rendered page links,
|
||||
because phpBB only ever shows a handful of them. `per_page` is read from the
|
||||
`jumpto()` function the forum embeds in the page; without it, only first page
|
||||
and the original previous/next links remain available. In the list the bar is
|
||||
built only where there is more than one page — forum pages run to hundreds of
|
||||
them, category pages to one.
|
||||
The original page is hidden and a mobile layout is built from the same data,
|
||||
one view per page type:
|
||||
|
||||
Both views carry a fixed breadcrumb bar at the top, taken from the forum's own
|
||||
`Board index » … » Main Forum` trail. Without it there is no way back out of a
|
||||
thread, because the original page is hidden and phpBB links the parent forum
|
||||
nowhere else. The bar scrolls horizontally if the trail is too long for the
|
||||
screen, and is left out entirely on pages that do not carry a trail.
|
||||
- **Lists as cards.** Forum and topic rows become cards with title, description,
|
||||
counts and last post; category headers become section headings.
|
||||
- **Section strips.** Forums, announcements, stickies and topics are separated
|
||||
by a header carrying the section name and its card count. Announcements and
|
||||
stickies start collapsed — they repeat above the topics on *every* page of a
|
||||
forum. Tapping a header toggles its section, and the state is remembered per
|
||||
section across forums.
|
||||
- **Fixed page bar.** First / previous / next / last and a page indicator.
|
||||
Tapping the indicator opens a sheet with a number field and every page of the
|
||||
forum or thread, not just the handful phpBB renders.
|
||||
- **Fixed breadcrumb bar** with a login pill showing the account name. Without
|
||||
it there is no way back out of a thread, since the original page is hidden.
|
||||
- **Search from anywhere.** With a session, a magnifier sits next to the pill:
|
||||
it opens `search.php`, or inside a thread reveals a bar that searches the
|
||||
topic.
|
||||
- **Readable posts.** Avatar, author, rank and timestamp above the message;
|
||||
signatures fold into a collapsed box.
|
||||
- **Forms are moved, not rebuilt.** Login, search and the page after a
|
||||
successful login reuse the original elements, so hidden fields and the plain
|
||||
server-side POST stay intact. The search form starts on Main Forum with
|
||||
subforums off, and the board rules on the login page fold away.
|
||||
- **Search hits as cards** in "Posts" display mode, each with a link straight to
|
||||
the post. "Topics" mode falls through to the regular list view.
|
||||
|
||||
The breadcrumb bar also carries a login pill, built on every page the script
|
||||
touches, not only the login form itself. Session state is read from the
|
||||
forum's own menu bar: a `mode=login` link means logged out, a `mode=logout`
|
||||
link means logged in, and the pill switches between a "Login" link and the
|
||||
account name accordingly. The name is parsed from the link text with a
|
||||
`Logout [ Name ]` pattern, confirmed against a real logged-in session's markup;
|
||||
if it ever fails to match — a different language, a different bracket style —
|
||||
the pill still shows "Logout", just without a name, rather than breaking.
|
||||
The script's own labels are German; anything taken from the board keeps its
|
||||
original wording — `FOREN` in the first screenshot is the script's name for a
|
||||
group the board does not label, while `STICKIES` and `TOPICS` are the board's
|
||||
own words.
|
||||
|
||||
Once a session exists, the login pill also carries a magnifier button
|
||||
directly to its left, so it does not compete with the breadcrumb trail for
|
||||
space and only shows up where cs.rin.ru's own search is actually reachable.
|
||||
Outside a thread it links straight to `search.php`; inside one it instead
|
||||
reveals a second bar under the crumbs with a search field, reusing the
|
||||
original "Search this topic…" form in place rather than rebuilding it, so the
|
||||
hidden topic-id and scope fields stay intact.
|
||||
|
||||
The login form itself is not rebuilt from scratch: the original `<form>`
|
||||
element is moved into the new layout and restyled, table rows collapsed to a
|
||||
vertical stack. Reusing the element rather than reconstructing it keeps the
|
||||
hidden `sid` and `redirect` fields, the `autologin` / `viewonline` checkboxes
|
||||
and the plain server-side POST intact — the script never talks to the forum's
|
||||
session handling directly, it only changes how the existing form looks.
|
||||
|
||||
Submitting the form re-renders the same URL with a generic "Information"
|
||||
template instead of the form — a short confirmation ("You have been
|
||||
successfully logged in.") before phpBB's own `<meta refresh>` moves on to the
|
||||
forum index. The script recognises this template by its shape (a single-cell
|
||||
header followed by a message cell), not by its wording, and restyles it the
|
||||
same way as the form: the original cell — message and link included — is
|
||||
reused in place rather than rebuilt.
|
||||
|
||||
`search.php` gets its own treatment when it shows the query form rather than
|
||||
results. It is recognised by its forum picker — a `select[name="fid[]"]`
|
||||
multi-select that only exists on this form, not on the small "Search this
|
||||
forum…" / "Search this topic…" widgets embedded elsewhere — and then moved
|
||||
into the new layout the same way the login form is: table rows collapsed to a
|
||||
vertical stack, radio groups and selects restyled for touch, and the quick
|
||||
links above it ("View new posts", "View your posts", …) turned into a row of
|
||||
chips. "Search in forums" defaults to Main Forum and "Search subforums"
|
||||
defaults to No, overriding the server's own blank/Yes defaults, since that is
|
||||
the scope most searches from the mobile view actually want.
|
||||
|
||||
A submitted search lands back on `search.php`, now with results — which, in
|
||||
the "Posts" display mode the in-thread search bar produces, look nothing like
|
||||
a topic list: each hit is a post excerpt with its own author, timestamp, and
|
||||
forum/topic context. Recognised by a `p.topictitle` marker unique to this
|
||||
mode, these get their own cards, built before the regular list view's row
|
||||
scanning would otherwise misread the topic and profile links inside them. The
|
||||
"Topics" display mode is not handled specially — no fixture shows what it
|
||||
looks like, so it still falls through to the regular list view, which may or
|
||||
may not render it sensibly.
|
||||
|
||||
The script runs at `document-start`, injects a mobile viewport meta tag, and
|
||||
carries a `DEBUG` flag. While `DEBUG` is `true`, a green diagnostic overlay is
|
||||
prepended to the page showing which view was chosen and how many rows were
|
||||
converted. Set it to `false` before daily use.
|
||||
It runs at `document-start` and injects a mobile viewport meta tag. A `DEBUG`
|
||||
flag in the source, off by default, adds a green diagnostic overlay showing the
|
||||
version, the chosen view and how many rows were converted.
|
||||
|
||||
### Installation on iOS
|
||||
|
||||
@@ -143,7 +89,7 @@ Tampermonkey.
|
||||
The loader in `dev/` references the script by **absolute path**:
|
||||
|
||||
```js
|
||||
// @require file:///C:/Projekte/ios_safari_userscripts/rin-mobile.user.js
|
||||
// @require file:///C:/Projekte/rin-mobile-userscript/rin-mobile.user.js
|
||||
```
|
||||
|
||||
This path is hardcoded and only valid on the machine it was written for. After
|
||||
@@ -151,7 +97,7 @@ cloning the repository or moving the project folder, adjust the `@require` line
|
||||
in `dev/rin-mobile.dev.user.js` to your own location.
|
||||
|
||||
Use forward slashes and the `file:///` prefix, including the drive letter —
|
||||
`file:///D:/projects/ios_safari_userscripts/rin-mobile.user.js`. A `@require`
|
||||
`file:///D:/projects/rin-mobile-userscript/rin-mobile.user.js`. A `@require`
|
||||
that cannot be resolved causes Tampermonkey to skip the script **entirely and
|
||||
without an error message on the page**, which looks exactly like the script not
|
||||
being installed at all.
|
||||
@@ -179,26 +125,28 @@ cards and two debug overlays.
|
||||
|
||||
#### Test fixtures
|
||||
|
||||
`cs.rin.ru` requires a login, so save the pages you need once and iterate
|
||||
against local copies:
|
||||
Layout work happens on the live site, with the loader active in Chrome. The
|
||||
saved pages under `dev/` serve a different purpose: `cs.rin.ru` requires a
|
||||
login, and its markup is the only source for what the script has to cope with,
|
||||
so the pages that matter are kept as snapshots — as a reference to read, and as
|
||||
input for a throwaway jsdom harness when a change needs to be checked against
|
||||
several pages at once.
|
||||
|
||||
1. Open the page while logged in, then in DevTools right-click `<html>` →
|
||||
*Copy outerHTML*.
|
||||
2. Save it as `dev/viewtopic.php.html` or `dev/viewforum.php.html`.
|
||||
3. Serve the folder: `python -m http.server 8777 --directory .`
|
||||
2. Save it under the page's own name — `dev/viewtopic.php`, `dev/viewforum.php`,
|
||||
`dev/index.php`, `dev/ucp.php`, `dev/search.php`.
|
||||
|
||||
The file name matters twice over. The `.html` extension makes the server send
|
||||
the file as HTML instead of offering it as a download, and the retained
|
||||
`viewtopic.php` / `viewforum.php` part is what the loader's `@include` rules
|
||||
and the page-type check in `rin-mobile.user.js` match against.
|
||||
The loader deliberately matches `cs.rin.ru` only, so these files are not meant
|
||||
to be opened in the browser. Serving them locally would take `@include` rules
|
||||
for `localhost`, and those match on page names common enough (`index.php`,
|
||||
anything containing `search`) that the script would inject its stylesheet into
|
||||
unrelated local projects.
|
||||
|
||||
Forum images and stylesheets are missing from such a snapshot. That is fine for
|
||||
layout work, because the script rebuilds the DOM anyway — only avatars stay
|
||||
empty, since their `src` is carried over unchanged.
|
||||
|
||||
Note that a snapshot saved while logged out contains `span.link_removed`
|
||||
placeholders instead of links, and may use different markup for author names
|
||||
than the logged-in view.
|
||||
A snapshot saved while logged out contains `span.link_removed` placeholders
|
||||
instead of links, and may use different markup for author names than the
|
||||
logged-in view — worth knowing before puzzling over a fixture that behaves
|
||||
differently from the real page.
|
||||
|
||||
#### Device emulation
|
||||
|
||||
@@ -224,32 +172,35 @@ Windows (`npx playwright install webkit`).
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
Install `dev/tm-test.user.js` — it injects a red bar and nothing else. If the
|
||||
bar does not appear, the problem is Tampermonkey (see setup above), not the
|
||||
script code. If it does appear, check the loader's *Externals* tab in the
|
||||
Tampermonkey dashboard: an empty or failed entry means the `@require` path is
|
||||
wrong.
|
||||
If nothing happens at all, separate the two possible causes before reading any
|
||||
code. Check the loader's *Externals* tab in the Tampermonkey dashboard: an
|
||||
empty or failed entry means the `@require` path is wrong. If the entry is
|
||||
there and the script still does nothing, set `DEBUG = true` in
|
||||
`rin-mobile.user.js` — the green overlay appears on every page the script
|
||||
touches, so its absence means the script never ran, while an overlay reporting
|
||||
`ansicht=keine` means it ran but the URL matched no view.
|
||||
|
||||
If Tampermonkey keeps serving a stale copy despite the update interval, replace
|
||||
the `file://` require with a local HTTP server, which does not cache:
|
||||
|
||||
```js
|
||||
// @require http://localhost:8777/rin-mobile.user.js
|
||||
```
|
||||
If Tampermonkey keeps serving a stale copy despite the update interval, open
|
||||
the loader's *Externals* tab and trigger an update on the `@require` entry by
|
||||
hand, or disable and re-enable the loader — both discard the cached copy.
|
||||
|
||||
### Repository layout
|
||||
|
||||
```
|
||||
rin-mobile.user.js topic list → cards, thread → post stream
|
||||
rin-mobile.user.js all views: lists, thread, login, search
|
||||
CLAUDE.md notes for working on the script
|
||||
dev/
|
||||
rin-mobile.dev.user.js loader, @require with absolute path
|
||||
*.php saved pages, markup reference, not in git
|
||||
docs/
|
||||
Screenshot_mobile_*.png the screenshots above
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deutsch
|
||||
|
||||
Ein Userscript, das die phpBB2-Seiten von `cs.rin.ru` für Safari auf iOS in eine
|
||||
Ein Userscript, das die phpBB3-Seiten von `cs.rin.ru` für Safari auf iOS in eine
|
||||
mobile Ansicht umbaut. Es ersetzt das ursprüngliche Tabellenlayout vollständig:
|
||||
der vorhandene Seiteninhalt wird ausgeblendet und ein neu gebautes DOM an
|
||||
`<body>` gehängt.
|
||||
@@ -259,121 +210,61 @@ anhand des Seitentyps:
|
||||
|
||||
| Seite | Ansicht |
|
||||
|---|---|
|
||||
| `viewforum.php`, `search.php`, `index.php`, Forumwurzel | Themen- und Forenliste als Karten |
|
||||
| `viewforum.php`, `index.php`, Forumwurzel | Themen- und Forenliste als Karten |
|
||||
| `search.php` | Das Suchformular umgestylt oder — bei abgeschickter Suche im Modus „Posts" — die Treffer als Karten |
|
||||
| `viewtopic.php` | Thread als Beitragsstrom mit fixierter Navigationsleiste |
|
||||
| `ucp.php?mode=login` | Login-Formular, an Ort und Stelle umgestylt; auf derselben URL nach erfolgreichem Absenden stattdessen die Informationsseite |
|
||||
| alles andere | Unverändert — es werden keine Stile injiziert |
|
||||
|
||||
Eine `viewforum.php`-Seite ist nicht zwangsläufig eine Themenliste: eine
|
||||
Kategorieseite listet Foren auf, eine Forenseite kann beides enthalten. Jede
|
||||
Tabellenzeile wird deshalb anhand ihres ersten verwertbaren Links eingeordnet.
|
||||
Forenzeilen werden zu Karten mit Name, Beschreibung, Unterforen und Zahlen,
|
||||
Themenzeilen behalten ihre bisherige Karte, Kategoriezeilen werden zu
|
||||
Zwischenüberschriften. Erkannt werden die Zeilen an phpBBs eigenen Klassen
|
||||
`forumlink` / `forumdesc` und ersatzweise an ihrer Form — zwei rein numerische
|
||||
Spalten hat nur eine Forenzeile mit Themen- und Beitragszahl.
|
||||
### Screenshots
|
||||
|
||||
Foren, globale Ankündigungen, Ankündigungen, Stickies und Themen landen alle im
|
||||
selben Kartenstrom, womit die Trennung verlorengeht, die die Originalseite
|
||||
zwischen ihnen zieht. Bei jedem Wechsel wird deshalb ein Gruppenkopf mit dem
|
||||
Namen des Abschnitts und der Zahl seiner Karten eingezogen; Forenkarten tragen
|
||||
zusätzlich eine farbige linke Kante. Die Beschriftung stammt aus der Trennzeile
|
||||
der Seite selbst (`Global Announcements`, `Stickies`, `Topics`), damit auch
|
||||
Abschnitte richtig heißen, die dieses Script nicht kennt. Welcher Art eine
|
||||
Themenzeile ist, verrät das Symbol ihrer ersten Spalte, nicht der Titel. Direkt
|
||||
unter einer Kategorieüberschrift entfällt der Gruppenkopf, weil die Überschrift
|
||||
die Foren darunter schon einleitet.
|
||||
| Forenliste, die Abschnitte über den Themen zugeklappt | Thread, mit eingeblendeter Suchleiste |
|
||||
|---|---|
|
||||
| <img src="docs/Screenshot_mobile_1.png" alt="Forenliste als Karten: Brotkrumenleiste mit Lupe, aufgeklappter Abschnitt FOREN mit vier Forenkarten, zugeklappte Streifen für GLOBAL ANNOUNCEMENTS, ANNOUNCEMENTS und STICKIES, unten die Seitenleiste mit 1 / 610" width="100%"> | <img src="docs/Screenshot_mobile_2.png" alt="Thread-Ansicht: Brotkrumen, zweite Leiste mit Suchfeld, darunter eine Beitragskarte mit Avatar, Autor, Rang und Beitragszahl über dem Text" width="100%"> |
|
||||
| **Suchformular, oben: Schnellzugriffe und Forenwähler** | **Suchformular, unten: Optionen und Schaltflächen** |
|
||||
| <img src="docs/Screenshot_mobile_3.png" alt="Suchformular: Schnellzugriffe als Chips, die Feldgruppen für Suchbegriffe und der Forenwähler mit vorausgewähltem Main Forum" width="100%"> | <img src="docs/Screenshot_mobile_4.png" alt="Suchformular, Fortsetzung: Unterforen auf No, die übrigen Optionsgruppen sowie die Schaltflächen Search und Reset" width="100%"> |
|
||||
|
||||
Ein Tipp auf den Kopf klappt seinen Abschnitt zu. Globale Ankündigungen,
|
||||
Ankündigungen und Stickies starten zugeklappt: sie stehen auf *jeder* Seite
|
||||
eines Forums vor den Themen und füllen sonst den ersten Bildschirm. Der Zustand
|
||||
liegt je Art in `localStorage`, einmal zugeklappte Stickies gelten also in jedem
|
||||
Forum. `localStorage` statt `GM_setValue`, weil es ohne `@grant` auskommt und
|
||||
sich in Tampermonkey wie in der iOS-App gleich verhält; dafür kann Safari
|
||||
schreibbaren Speicher nach längerer Nichtnutzung räumen, dann stehen die
|
||||
Abschnitte wieder auf ihrer Voreinstellung.
|
||||
### Was das Script liefert
|
||||
|
||||
Beide Ansichten teilen sich dieselbe fixierte Navigationsleiste am unteren
|
||||
Rand: erste / vorherige / nächste / letzte Seite und eine Positionsanzeige. Ein
|
||||
Tipp darauf öffnet ein Sheet mit Zahlenfeld und einem Raster aller Seiten. Die
|
||||
Ziele werden als `start = (Seite - 1) × per_page` berechnet und nicht aus den
|
||||
gerenderten Seitenlinks übernommen, weil phpBB davon immer nur eine Handvoll
|
||||
anzeigt. `per_page` stammt aus der `jumpto()`-Funktion, die das Forum in die
|
||||
Seite einbettet; fehlt sie, bleiben nur die erste Seite und die ursprünglichen
|
||||
Zurück-/Weiter-Links nutzbar. In der Liste entsteht die Leiste nur, wo es mehr
|
||||
als eine Seite gibt — Forenseiten haben hunderte, Kategorieseiten eine.
|
||||
Die Originalseite wird ausgeblendet und aus denselben Daten eine mobile Ansicht
|
||||
gebaut, je eine pro Seitentyp:
|
||||
|
||||
Beide Ansichten haben oben eine fixierte Brotkrumenleiste, übernommen aus dem
|
||||
`Board index » … » Main Forum`-Pfad des Forums. Ohne sie führt aus einem Thread
|
||||
kein Weg zurück, weil die Originalseite ausgeblendet ist und phpBB das
|
||||
Elternforum nirgends sonst verlinkt. Die Leiste lässt sich waagerecht schieben,
|
||||
wenn der Pfad breiter ist als der Bildschirm, und entfällt auf Seiten ohne
|
||||
Pfadangabe.
|
||||
- **Listen als Karten.** Foren- und Themenzeilen werden zu Karten mit Titel,
|
||||
Beschreibung, Zahlen und letztem Beitrag, Kategoriezeilen zu
|
||||
Zwischenüberschriften.
|
||||
- **Gruppenköpfe.** Foren, Ankündigungen, Stickies und Themen trennt jeweils ein
|
||||
Kopf mit dem Namen des Abschnitts und der Zahl seiner Karten. Ankündigungen
|
||||
und Stickies starten zugeklappt — sie stehen auf *jeder* Seite eines Forums
|
||||
über den Themen. Ein Tipp auf den Kopf klappt seinen Abschnitt um, der Zustand
|
||||
gilt je Abschnitt über alle Foren hinweg.
|
||||
- **Fixierte Seitenleiste.** Erste / vorherige / nächste / letzte Seite und eine
|
||||
Positionsanzeige. Ein Tipp darauf öffnet ein Sheet mit Zahlenfeld und allen
|
||||
Seiten des Forums oder Threads, nicht nur der Handvoll, die phpBB rendert.
|
||||
- **Fixierte Brotkrumenleiste** mit einer Login-Pille, die den Kontonamen zeigt.
|
||||
Ohne sie führt aus einem Thread kein Weg zurück, weil die Originalseite
|
||||
ausgeblendet ist.
|
||||
- **Suche von überall.** Besteht eine Sitzung, sitzt neben der Pille eine Lupe:
|
||||
sie öffnet `search.php` oder blendet innerhalb eines Threads eine Leiste ein,
|
||||
die das Thema durchsucht.
|
||||
- **Lesbare Beiträge.** Avatar, Autor, Rang und Zeitstempel über dem Text,
|
||||
Signaturen wandern in einen zugeklappten Kasten.
|
||||
- **Formulare werden verschoben, nicht nachgebaut.** Login, Suche und die Seite
|
||||
nach erfolgreichem Login übernehmen die Original-Elemente, damit versteckte
|
||||
Felder und der schlichte serverseitige POST erhalten bleiben. Das Suchformular
|
||||
startet auf Main Forum ohne Unterforen, die Forenregeln im Login klappen weg.
|
||||
- **Suchtreffer als Karten** im Anzeigemodus „Posts", jeder mit einem Link
|
||||
direkt zum Beitrag. Der Modus „Topics" läuft durch die gewöhnliche
|
||||
Listenansicht.
|
||||
|
||||
Die Brotkrumenleiste trägt außerdem eine Login-Pille, gebaut auf jeder Seite,
|
||||
die das Script anfasst — nicht nur auf dem Login-Formular selbst. Der
|
||||
Sitzungszustand wird aus dem Menübalken der Originalseite gelesen: ein
|
||||
`mode=login`-Link bedeutet ausgeloggt, ein `mode=logout`-Link eingeloggt, die
|
||||
Pille wechselt entsprechend zwischen einem „Login"-Link und dem Kontonamen.
|
||||
Der Name wird über das Muster `Logout [ Name ]` aus dem Linktext gelesen, das
|
||||
gegen das Markup einer echten eingeloggten Sitzung bestätigt ist; greift es
|
||||
einmal nicht — andere Sprache, andere Klammerform — bleibt die Pille bei
|
||||
„Logout" stehen, nur ohne Namen, statt zu reißen.
|
||||
Die Beschriftungen des Scripts sind deutsch, alles aus dem Forum Übernommene
|
||||
behält seinen Wortlaut — `FOREN` im ersten Screenshot ist die Bezeichnung des
|
||||
Scripts für eine Gruppe, die die Seite selbst nicht beschriftet, `STICKIES` und
|
||||
`TOPICS` sind ihre eigenen Worte.
|
||||
|
||||
Besteht eine Sitzung, trägt die Login-Pille zusätzlich einen Lupen-Button
|
||||
direkt links neben sich, damit er nicht mit der Brotkrumenleiste um Platz
|
||||
konkurriert, und erscheint nur dort, wo cs.rins eigene Suche überhaupt
|
||||
erreichbar ist. Außerhalb eines Threads führt er direkt zu `search.php`;
|
||||
innerhalb eines Threads blendet er stattdessen eine zweite Leiste unter den
|
||||
Brotkrumen mit einem Suchfeld ein — das ist das Original-Formular „Search this
|
||||
topic…", unverändert an Ort und Stelle wiederverwendet statt neu gebaut, damit
|
||||
die versteckten Felder für Themen-Id und Suchbereich erhalten bleiben.
|
||||
|
||||
Das Login-Formular selbst wird nicht neu gebaut: das Original-`<form>`-Element
|
||||
wandert unverändert in das neue Layout und wird umgestylt, Tabellenzeilen
|
||||
werden zu einer senkrechten Abfolge zusammengeklappt. Die Wiederverwendung
|
||||
statt Neubau erhält die versteckten Felder `sid` und `redirect`, die
|
||||
Checkboxen `autologin` / `viewonline` und den schlichten serverseitigen POST —
|
||||
das Script spricht nie selbst mit der Sitzungsverwaltung des Forums, es ändert
|
||||
nur, wie das vorhandene Formular aussieht.
|
||||
|
||||
Nach dem Absenden liefert dieselbe URL statt des Formulars eine allgemeine
|
||||
„Information"-Vorlage aus — eine kurze Bestätigung („You have been
|
||||
successfully logged in."), bevor phpBBs eigenes `<meta refresh>` zum
|
||||
Forenindex weiterleitet. Das Script erkennt diese Vorlage an ihrer Form, nicht
|
||||
am Wortlaut (eine Kopfzeile mit einer einzelnen Zelle, gefolgt von einer
|
||||
Nachrichtenzelle), und stylt sie wie das Formular um: die Original-Zelle —
|
||||
Nachricht und Link inklusive — wandert unverändert an ihren Platz statt neu
|
||||
gebaut zu werden.
|
||||
|
||||
`search.php` bekommt eine eigene Behandlung, wenn es das Suchformular statt
|
||||
Treffern zeigt. Erkannt wird das am Forenwähler — einem Mehrfachauswahlfeld
|
||||
`select[name="fid[]"]`, das es nur in diesem Formular gibt, nicht in den
|
||||
kleinen „Search this forum…" / „Search this topic…"-Widgets an anderer
|
||||
Stelle — und dann wie das Login-Formular in das neue Layout verschoben:
|
||||
Tabellenzeilen zu einer senkrechten Abfolge zusammengeklappt, Radio-Gruppen
|
||||
und Auswahlfelder für Touch umgestylt, die Schnellzugriffe darüber („View new
|
||||
posts", „View your posts", …) zu einer Reihe aus Chips. „Search in forums"
|
||||
ist dabei standardmäßig auf Main Forum vorausgewählt, „Search subforums" auf
|
||||
No — statt der leeren/„Yes"-Vorgabe des Servers, weil das dem Suchumfang
|
||||
entspricht, der von der mobilen Ansicht aus meistens gewollt ist.
|
||||
|
||||
Eine abgeschickte Suche landet wieder auf `search.php`, diesmal mit Treffern —
|
||||
die im Anzeigemodus „Posts", den die Suchleiste im Thread erzeugt, aber
|
||||
überhaupt nicht wie eine Themenliste aussehen: jeder Treffer ist ein
|
||||
Beitragsauszug mit eigenem Autor, Zeitstempel und Forum-/Thema-Kontext.
|
||||
Erkannt an einem Merkmal (`p.topictitle`), das es nur in diesem Modus gibt,
|
||||
bekommen sie eigene Karten, gebaut bevor das Zeileneinsammeln der gewöhnlichen
|
||||
Listenansicht die enthaltenen Themen- und Profillinks falsch deuten würde.
|
||||
Der Anzeigemodus „Topics" bekommt keine eigene Behandlung — keine Fixture
|
||||
zeigt, wie er aussieht, er läuft also weiterhin durch die gewöhnliche
|
||||
Listenansicht, was passend sein kann oder auch nicht.
|
||||
|
||||
Das Script läuft bei `document-start`, setzt ein mobiles Viewport-Meta-Tag und
|
||||
hat ein `DEBUG`-Flag. Solange `DEBUG` auf `true` steht, wird ein grünes
|
||||
Diagnose-Overlay über die Seite gelegt, das zeigt, welche Ansicht gewählt wurde
|
||||
und wie viele Zeilen umgewandelt wurden. Für den täglichen Gebrauch auf `false`
|
||||
setzen.
|
||||
Das Script läuft bei `document-start` und setzt ein mobiles Viewport-Meta-Tag.
|
||||
Ein `DEBUG`-Flag im Quelltext, standardmäßig aus, legt ein grünes
|
||||
Diagnose-Overlay über die Seite: Version, gewählte Ansicht und Zahl der
|
||||
umgewandelten Zeilen.
|
||||
|
||||
### Installation unter iOS
|
||||
|
||||
@@ -397,7 +288,7 @@ laden — keine Neuinstallation in Tampermonkey nötig.
|
||||
Der Loader in `dev/` verweist mit einem **absoluten Pfad** auf das Script:
|
||||
|
||||
```js
|
||||
// @require file:///C:/Projekte/ios_safari_userscripts/rin-mobile.user.js
|
||||
// @require file:///C:/Projekte/rin-mobile-userscript/rin-mobile.user.js
|
||||
```
|
||||
|
||||
Dieser Pfad ist fest eingetragen und gilt nur auf dem Rechner, für den er
|
||||
@@ -406,7 +297,7 @@ Projektordners muss die `@require`-Zeile in `dev/rin-mobile.dev.user.js` auf den
|
||||
eigenen Ablageort angepasst werden.
|
||||
|
||||
Dabei Schrägstriche und das Präfix `file:///` inklusive Laufwerksbuchstaben
|
||||
verwenden — `file:///D:/projekte/ios_safari_userscripts/rin-mobile.user.js`.
|
||||
verwenden — `file:///D:/projekte/rin-mobile-userscript/rin-mobile.user.js`.
|
||||
Ein `@require`, das nicht aufgelöst werden kann, führt dazu, dass Tampermonkey
|
||||
das Script **komplett und ohne Fehlermeldung auf der Seite** überspringt. Das
|
||||
sieht exakt so aus, als wäre gar kein Script installiert.
|
||||
@@ -433,6 +324,31 @@ Den Loader nicht parallel zum unveränderten Script betreiben. Sind beide in
|
||||
Tampermonkey aktiv, läuft der Code doppelt auf derselben Seite, mit doppelten
|
||||
Karten und zwei Debug-Overlays als Folge.
|
||||
|
||||
#### Testfixtures
|
||||
|
||||
Die Layoutarbeit findet auf der echten Seite statt, mit dem Loader aktiv in
|
||||
Chrome. Die gespeicherten Seiten unter `dev/` dienen etwas anderem: `cs.rin.ru`
|
||||
verlangt einen Login, und sein Markup ist die einzige Quelle dafür, womit das
|
||||
Script zurechtkommen muss. Die relevanten Seiten liegen deshalb als Mitschnitt
|
||||
vor — zum Nachlesen und als Eingabe für ein Wegwerf-Harness mit jsdom, wenn
|
||||
eine Änderung gegen mehrere Seiten auf einmal zu prüfen ist.
|
||||
|
||||
1. Die Seite eingeloggt öffnen, in den DevTools mit der rechten Maustaste auf
|
||||
`<html>` → *Copy outerHTML*.
|
||||
2. Unter dem Namen der Seite ablegen — `dev/viewtopic.php`,
|
||||
`dev/viewforum.php`, `dev/index.php`, `dev/ucp.php`, `dev/search.php`.
|
||||
|
||||
Der Loader greift bewusst nur auf `cs.rin.ru`, diese Dateien sind also nicht
|
||||
zum Öffnen im Browser gedacht. Sie lokal auszuliefern verlangte
|
||||
`@include`-Regeln für `localhost`, und die passen auf Seitennamen, die dort
|
||||
häufig genug vorkommen (`index.php`, alles mit „search" darin), dass das Script
|
||||
sein Stylesheet in fremde lokale Projekte injizieren würde.
|
||||
|
||||
Ein ausgeloggt gespeicherter Mitschnitt enthält statt der Links
|
||||
`span.link_removed`-Platzhalter und kann Autorennamen anders auszeichnen als
|
||||
die eingeloggte Ansicht — gut zu wissen, bevor man über eine Fixture rätselt,
|
||||
die sich anders verhält als die echte Seite.
|
||||
|
||||
#### Geräte-Emulation
|
||||
|
||||
DevTools → *Device Toolbar* (`Strg+Umschalt+M`) → *Edit…* → eigenes Gerät mit
|
||||
@@ -458,24 +374,27 @@ Windows mit (`npx playwright install webkit`).
|
||||
|
||||
#### Fehlersuche
|
||||
|
||||
`dev/tm-test.user.js` installieren — das Script blendet nur einen roten Balken
|
||||
ein, sonst nichts. Erscheint der Balken nicht, liegt das Problem bei
|
||||
Tampermonkey (siehe Einrichtung oben) und nicht am Script-Code. Erscheint er,
|
||||
im Tampermonkey-Dashboard den Tab *Externals* des Loaders prüfen: ein leerer
|
||||
oder fehlgeschlagener Eintrag bedeutet, dass der `@require`-Pfad falsch ist.
|
||||
Passiert überhaupt nichts, zuerst die beiden möglichen Ursachen trennen, bevor
|
||||
Code gelesen wird. Im Tampermonkey-Dashboard den Tab *Externals* des Loaders
|
||||
prüfen: ein leerer oder fehlgeschlagener Eintrag bedeutet, dass der
|
||||
`@require`-Pfad falsch ist. Ist der Eintrag da und trotzdem passiert nichts, in
|
||||
`rin-mobile.user.js` `DEBUG = true` setzen — das grüne Overlay erscheint auf
|
||||
jeder Seite, die das Script anfasst. Bleibt es aus, lief das Script gar nicht;
|
||||
meldet es `ansicht=keine`, lief es, aber die URL passte auf keine Ansicht.
|
||||
|
||||
Liefert Tampermonkey trotz Update-Intervall weiterhin einen veralteten Stand
|
||||
aus, das `file://`-Require durch einen lokalen HTTP-Server ersetzen, der nicht
|
||||
zwischenspeichert:
|
||||
|
||||
```js
|
||||
// @require http://localhost:8777/rin-mobile.user.js
|
||||
```
|
||||
aus, im Tab *Externals* des Loaders den `@require`-Eintrag von Hand
|
||||
aktualisieren oder den Loader einmal deaktivieren und wieder aktivieren —
|
||||
beides verwirft die zwischengespeicherte Fassung.
|
||||
|
||||
### Aufbau des Repositories
|
||||
|
||||
```
|
||||
rin-mobile.user.js Themenliste → Karten, Thread → Beitragsstrom
|
||||
rin-mobile.user.js alle Ansichten: Listen, Thread, Login, Suche
|
||||
CLAUDE.md Notizen zur Arbeit am Script
|
||||
dev/
|
||||
rin-mobile.dev.user.js Loader, @require mit absolutem Pfad
|
||||
*.php gespeicherte Seiten, Markup-Vorlage, nicht in git
|
||||
docs/
|
||||
Screenshot_mobile_*.png die Screenshots oben
|
||||
```
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
// @name [DEV] RIN Mobile
|
||||
// @namespace rin-dev
|
||||
// @description Dev-Loader: laedt rin-mobile.user.js live von der Platte
|
||||
// @version 2.0
|
||||
// @version 2.1
|
||||
// @match *://cs.rin.ru/forum/*
|
||||
// @include http://localhost:*/*viewforum*
|
||||
// @include http://127.0.0.1:*/*viewforum*
|
||||
// @include http://localhost:*/*viewtopic*
|
||||
// @include http://127.0.0.1:*/*viewtopic*
|
||||
// @require file:///C:/Projekte/ios_safari_userscripts/rin-mobile.user.js
|
||||
// @require file:///C:/Projekte/rin-mobile-userscript/rin-mobile.user.js
|
||||
// @run-at document-start
|
||||
// @grant none
|
||||
// ==/UserScript==
|
||||
|
||||
// Der eigentliche Code steckt in der @require-Datei.
|
||||
// Aenderungen dort greifen nach einem Reload der Seite.
|
||||
//
|
||||
// Der Loader laeuft nur auf der echten Seite. Die Fixtures unter dev/ sind
|
||||
// Markup-Vorlagen fuer das jsdom-Harness, keine Seiten zum Anschauen im
|
||||
// Browser — dafuer braeuchte es einen lokalen Server und passende
|
||||
// @include-Zeilen, die auf localhost zu breit streuen wuerden.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
+73
-2
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name RIN Mobile
|
||||
// @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom
|
||||
// @version 3.14
|
||||
// @version 3.15
|
||||
// @match *://cs.rin.ru/forum/*
|
||||
// @run-at document-start
|
||||
// ==/UserScript==
|
||||
@@ -12,6 +12,14 @@
|
||||
var DEBUG = false;
|
||||
var log = [];
|
||||
|
||||
// Die Version steht ausschliesslich im Metadatenblock. Ein zweiter Literal
|
||||
// im Code lief beim Release regelmaessig davon weg, ohne dass es auffiel —
|
||||
// GM_info liest denselben Header zurueck. Fehlt es (Userscript-Manager ohne
|
||||
// GM_info), bleibt das Overlay ohne Versionsangabe, statt eine falsche zu
|
||||
// zeigen.
|
||||
var VERSION = (typeof GM_info !== 'undefined' && GM_info.script &&
|
||||
GM_info.script.version) || '';
|
||||
|
||||
function h() { return document.head || document.documentElement; }
|
||||
|
||||
function txt(el) { return (el ? el.textContent || '' : '')
|
||||
@@ -52,6 +60,12 @@
|
||||
: '';
|
||||
log.push('ansicht=' + (view || 'keine'));
|
||||
|
||||
// Muss zum fruehestmoeglichen Zeitpunkt stehen, nicht erst beim Aufbau: iOS
|
||||
// Safari stellt die Scrollposition einer schon besuchten Adresse von sich aus
|
||||
// wieder her, und zwar bevor das Script ueberhaupt etwas gebaut hat. Ein
|
||||
// spaeterer Reset kaeme dagegen nur hinterher.
|
||||
if (view && 'scrollRestoration' in history) history.scrollRestoration = 'manual';
|
||||
|
||||
if (view && !document.querySelector('meta[name="viewport"][data-rm]')) {
|
||||
var m = document.createElement('meta');
|
||||
m.name = 'viewport';
|
||||
@@ -229,9 +243,13 @@
|
||||
font-size:21px; font-weight:650; line-height:1.3;
|
||||
overflow-wrap:break-word; }
|
||||
|
||||
/* Die Karte traegt seit dem Anker-Uebertrag die id des Beitrags und ist
|
||||
damit Sprungziel von #pXXXX. Ohne den Freiraum landet sie hinter der
|
||||
fixierten Brotkrumenleiste, der Kartenkopf waere verdeckt. */
|
||||
.rm-post { box-sizing:border-box !important;
|
||||
width:100% !important; max-width:100% !important;
|
||||
padding:14px !important;
|
||||
scroll-margin-top:calc(53px + env(safe-area-inset-top));
|
||||
border-top:1px solid rgba(128,128,128,.25) !important; }
|
||||
|
||||
.rm-head { display:flex; align-items:center; gap:9px;
|
||||
@@ -1494,6 +1512,16 @@
|
||||
pr = pr.previousElementSibling;
|
||||
}
|
||||
|
||||
// --- Sprungziel ---
|
||||
// Der Anker eines Beitrags steht als <a name="pXXXX"> in der Autorenzelle
|
||||
// und wandert mit dem Original-DOM in die Unsichtbarkeit. Ohne ihn auf der
|
||||
// Karte hat eine Adresse mit #pXXXX kein Ziel mehr, und der Browser bleibt
|
||||
// stehen, wo er beim Umbau gerade stand.
|
||||
var anc = null;
|
||||
for (var ci = 0; ci < cells.length && !anc; ci++)
|
||||
anc = cells[ci].querySelector('a[name^="p"]');
|
||||
if (anc) card.id = anc.getAttribute('name');
|
||||
|
||||
// --- Username ---
|
||||
var user = '';
|
||||
for (var i = 0; i < cells.length && !user; i++) {
|
||||
@@ -1686,6 +1714,46 @@
|
||||
|
||||
// ==========================================================================
|
||||
|
||||
// Der Umbau laeuft erst auf DOMContentLoaded, also lange nachdem der Browser
|
||||
// seine Scrollposition festgelegt hat — und replaceBody() ruehrt sie nicht an.
|
||||
// Weil ein Thread auch nach dem Umbau laenger als der Viewport bleibt, klemmt
|
||||
// der Browser sie auch nicht auf 0 zurueck: der Thread startet dann mitten im
|
||||
// ersten Beitrag statt an seinem Anfang. Auf dem Geraet faellt das auf, im
|
||||
// Chrome-Debugging nicht, weil dort jede Adresse frisch geladen wird und es
|
||||
// gar keine Position zu uebernehmen gibt.
|
||||
function toStart() {
|
||||
var id = location.hash ? location.hash.slice(1) : '';
|
||||
var target = id ? document.getElementById(id) : null;
|
||||
if (target) target.scrollIntoView();
|
||||
else window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// Einmal zuruecksetzen genuegt nicht: Safari legt die Position nicht in einem
|
||||
// Rutsch fest, sondern springt nach 'load' und nach dem Layout nachgeladener
|
||||
// Bilder noch einmal nach — ein Reset auf DOMContentLoaded wird davon einfach
|
||||
// ueberschrieben. Der Anfang wird deshalb kurz festgehalten. Die Bremse loest
|
||||
// bei der ersten eigenen Geste des Lesers sofort aus, sonst risse sie ihn
|
||||
// waehrend des Lesens zurueck; 'scroll' taugt dafuer nicht als Signal, weil
|
||||
// toStart() selbst eines ausloest.
|
||||
function holdStart() {
|
||||
var free = false;
|
||||
function release() { free = true; }
|
||||
['touchstart', 'wheel', 'keydown'].forEach(function (ev) {
|
||||
window.addEventListener(ev, release, { passive: true, once: true });
|
||||
});
|
||||
|
||||
var tries = 0;
|
||||
(function tick() {
|
||||
if (free) return;
|
||||
toStart();
|
||||
if (++tries < 12) setTimeout(tick, 50);
|
||||
})();
|
||||
|
||||
// Faengt den Sprung ab, der erst nach dem letzten Bild kommt und damit
|
||||
// jenseits der 600 ms der Schleife liegen kann.
|
||||
window.addEventListener('load', function () { if (!free) toStart(); });
|
||||
}
|
||||
|
||||
function run() {
|
||||
try {
|
||||
if (view === 'liste') buildList();
|
||||
@@ -1693,10 +1761,13 @@
|
||||
else if (view === 'login') buildLogin();
|
||||
} catch (e) { log.push('build: ' + e.message); }
|
||||
|
||||
if (view && document.querySelector('.rm-root')) holdStart();
|
||||
|
||||
if (DEBUG) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'rm-dbg';
|
||||
d.textContent = 'RIN Mobile v3.13\n' + log.join('\n');
|
||||
d.textContent = 'RIN Mobile' + (VERSION ? ' v' + VERSION : '') +
|
||||
'\n' + log.join('\n');
|
||||
document.body.insertBefore(d, document.body.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user