diff --git a/CLAUDE.md b/CLAUDE.md index 746dcc2..8461ee7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,7 @@ die `.user.js`-Datei ist das Produkt. | `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/ucp.php` | — | Fixture: Login-Formular (`mode=login`), nur ausgeloggter Zustand | 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 @@ -20,17 +21,18 @@ gespeichert werden — die Seite verlangt einen Login. Ein IIFE, streng der Reihe nach: 1. `DEBUG`-Flag und `log`-Array -2. Weiche: `view` ist `'thread'`, `'liste'` oder `''` — abgeleitet aus - `location.pathname + location.search` +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 beiden - Ansichten benutzt -6. `buildList()` beziehungsweise `buildThread()` lesen das Original-DOM und - bauen ein `div.rm-root`; beide haengen zum Schluss ueber `addCrumbs()` die - fixierte Brotkrumenleiste an +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 + ueber `addCrumbs()` die fixierte Brotkrumenleiste an, die zugleich ueber + `loginState()` die Login/Logout-Pille traegt 7. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an 8. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'` @@ -113,6 +115,37 @@ 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 ungetestet gegen eine echte Sitzung.** `loginState()` +unterscheidet `a[href*="mode=login"]` von `a[href*="mode=logout"]` im +Menuebalken und liest den Namen aus dem vermuteten Muster `Logout [ Name ]` im +Linktext. Dem Repository liegt keine eingeloggte Fixture vor — passt das +Namensmuster nicht, bleibt die Pille bei „Logout" ohne Namen stehen, statt eine +Ausnahme zu werfen. Vor einer Aenderung an `loginState()` erst mit einer echten +Sitzung pruefen, wie die Zeile tatsaechlich aussieht. + +**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-`
` 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 1 of 1114` 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. @@ -182,6 +215,14 @@ 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) und +die Pille in beiden Zustaenden — Letzteres per Textersetzung von +`mode=login` zu `mode=logout&sid=…Logout [ Name ]…` in der eingelesenen +Fixture, da eine echte eingeloggte Seite fehlt. Das Namensmuster bleibt damit +nur gegen die eigene Annahme getestet, nicht gegen echtes Markup — siehe die +Falle oben. + 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 diff --git a/README.md b/README.md index 9609d25..6630193 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ page type: |---|---| | `viewforum.php`, `search.php`, forum root | Topic and forum list as cards | | `viewtopic.php` | Thread as a post stream with a fixed bottom navigation bar | +| `ucp.php?mode=login` | Login form, restyled in place | | anything else | Untouched — no styles are injected | A `viewforum.php` page is not necessarily a list of topics: a category page @@ -64,6 +65,23 @@ screen, and is left out entirely on pages that do not carry a trail. Note that `Board index` leads to `index.php`, which the script does not rebuild — that page arrives in its desktop layout. +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; that pattern is unverified against a real logged-in +session, since the repository's fixtures are all captured while logged out — +if the pattern does not match, the pill still shows "Logout", just without a +name, rather than breaking. + +The login form itself is not rebuilt from scratch: the original `` +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. + 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 @@ -209,6 +227,7 @@ anhand des Seitentyps: |---|---| | `viewforum.php`, `search.php`, Forumwurzel | Themen- und Forenliste als Karten | | `viewtopic.php` | Thread als Beitragsstrom mit fixierter Navigationsleiste | +| `ucp.php?mode=login` | Login-Formular, an Ort und Stelle umgestylt | | alles andere | Unverändert — es werden keine Stile injiziert | Eine `viewforum.php`-Seite ist nicht zwangsläufig eine Themenliste: eine @@ -258,6 +277,24 @@ wenn der Pfad breiter ist als der Bildschirm, und entfällt auf Seiten ohne Pfadangabe. `Board index` führt auf `index.php` — diese Seite baut das Script nicht um, sie erscheint im Desktop-Layout. +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 +Muster ist gegen keine echte eingeloggte Sitzung geprüft, da alle Fixtures des +Repositories ausgeloggt aufgenommen wurden — greift es nicht, bleibt die +Pille bei „Logout" stehen, nur ohne Namen, statt zu reißen. + +Das Login-Formular selbst wird nicht neu gebaut: das Original-``-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. + 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 diff --git a/rin-mobile.user.js b/rin-mobile.user.js index 5e84601..3049309 100644 --- a/rin-mobile.user.js +++ b/rin-mobile.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name RIN Mobile // @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom -// @version 3.5 +// @version 3.6 // @match *://cs.rin.ru/forum/* // @run-at document-start // ==/UserScript== @@ -26,6 +26,7 @@ var view = /viewtopic\.php/.test(here) ? 'thread' : /viewforum\.php|search\.php/.test(here) || location.pathname === '/forum/' ? 'liste' + : /ucp\.php/.test(here) && /mode=login/.test(here) ? 'login' : ''; log.push('ansicht=' + (view || 'keine')); @@ -40,7 +41,7 @@ if (view) { var st = document.createElement('style'); st.textContent = ` - /* === Grundlage, beide Ansichten === */ + /* === Grundlage, alle Ansichten === */ html, body { max-width:100% !important; overflow-x:hidden !important; margin:0 !important; padding:0 !important; } @@ -61,7 +62,7 @@ Leiste selbst, damit ohne sie kein leerer Streifen bleibt. */ .rm-root.rm-paged { padding-bottom:64px !important; } - /* Brotkrumen, beide Ansichten. Fixiert statt sticky: .rm-root traegt + /* Brotkrumen, alle Ansichten. Fixiert statt sticky: .rm-root traegt overflow:hidden und wuerde als Scrollport einer sticky-Leiste dienen, die darin nie klebt. Fixierte Nachfahren klammert es nicht, weil der Root keinen eigenen Containing Block aufspannt. */ @@ -80,6 +81,10 @@ color:#eee !important; text-decoration:none !important; } .rm-crumbs a:last-of-type { font-weight:650; } .rm-crumbs .rm-sep { flex:none; font-size:13px; color:#eee; opacity:.45; } + .rm-crumbs a.rm-auth { flex:none; margin-left:auto; padding:6px 12px !important; + font-size:13px !important; border-radius:99px; + background:rgba(128,128,128,.18); + border:1px solid rgba(160,160,160,.4); } /* Nur gesetzt, wenn die Leiste wirklich gebaut wurde — ohne Brotkrumen im Original-DOM bleibt der Platz sonst leer reserviert. */ body.rm-crumbed .rm-root { @@ -200,6 +205,49 @@ .rm-sig { margin-top:12px; font-size:13px; opacity:.5; } .rm-sig summary { cursor:pointer; opacity:.8; } + /* === Login === */ + + .rm-login-title { padding:16px 14px 6px; font-size:21px; + font-weight:650; line-height:1.3; } + .rm-login-note { margin:8px 14px 4px; padding:10px 12px; + font-size:13px; opacity:.75; border-radius:10px; + background:rgba(128,128,128,.14); + overflow-wrap:break-word; } + .rm-login-note summary { cursor:pointer; font-weight:650; opacity:.9; } + .rm-login-note[open] summary { margin-bottom:8px; } + + /* Das Original-Formular ist tabellenbasiert und wird fuer den gestapelten + Kartenstil komplett auf Block-Elemente umgelegt — derselbe Trick wie bei + .rm-body table im Thread. Ohne Paginierung fehlt hier die .rm-nav, die + sonst per .rm-root.rm-paged den unteren Rand freihaelt. Der grosse + Bodensatz ist kein Stilmittel, sondern erzwingt genug Seitenhoehe zum + Scrollen: ist die Seite so kurz wie der Viewport, bleibt Safaris eigene, + nur teilweise eingefahrene Werkzeugleiste unten stehen und verdeckt den + Button dauerhaft, weil nichts mehr zu scrollen ist, um sie wegzuschieben. */ + .rm-login form { display:block; + padding:4px 14px calc(140px + env(safe-area-inset-bottom)); } + .rm-login form table, .rm-login form tbody, + .rm-login form tr, .rm-login form td { + display:block !important; width:100% !important; } + .rm-login form th { display:none !important; } + .rm-login form td { padding:0 !important; } + .rm-login form b.gensmall { display:block; margin:16px 0 6px; + font-size:13px; font-weight:650; opacity:.8; } + .rm-login form input[type="text"], + .rm-login form input[type="password"] { + box-sizing:border-box; width:100%; font-size:16px; padding:11px 12px; + border-radius:10px; color:#eee; background:rgba(120,120,120,.18); + border:1px solid rgba(160,160,160,.35); } + .rm-login form span.gensmall, .rm-login form a.gensmall { + display:inline-block; margin-top:6px; font-size:12px; opacity:.6; + text-decoration:none !important; color:inherit !important; } + .rm-login form input[type="checkbox"] { width:18px; height:18px; + vertical-align:middle; margin-right:8px; } + .rm-login form input[type="submit"] { + width:100%; font-size:16px; font-weight:650; padding:13px; + margin-top:20px; border-radius:10px; color:#fff; border:0; + background:rgba(120,170,255,.85); } + /* === Seitennavigation, beide Ansichten === */ .rm-nav { position:fixed; left:0; right:0; bottom:0; z-index:9999; @@ -277,6 +325,21 @@ document.body.appendChild(root); } + // Ob eine Sitzung besteht, verraet nur der Menuebalken der Originalseite: + // ausgeloggt verlinkt er ucp.php?mode=login, eingeloggt stattdessen + // mode=logout, ueblicherweise als "Logout [ Name ]". Das Namensmuster ist + // ungetestet, da dem Projekt keine eingeloggte Fixture vorliegt — schlaegt + // es fehl, bleibt die Pille ohne Namen stehen statt zu reissen. + function loginState() { + var out = document.querySelector('a[href*="mode=logout"]'); + if (out) { + var m = txt(out).match(/\[\s*(.+?)\s*\]/); + return { in: true, href: out.href, name: m ? m[1] : '' }; + } + var into = document.querySelector('a[href*="mode=login"]'); + return { in: false, href: into ? into.href : './ucp.php?mode=login' }; + } + // Ohne die Brotkrumen fuehrt aus der neuen Ansicht kein Weg zurueck: das // Original-DOM ist ausgeblendet, und phpBB verlinkt das Elternforum nirgends // sonst. Muss nach replaceBody() laufen, weil die Leiste an body haengt. @@ -308,6 +371,14 @@ log.push('krumen=' + n); if (!n) return; + + var st = loginState(); + var auth = document.createElement('a'); + auth.className = 'rm-auth'; + auth.href = st.href; + auth.textContent = st.in ? (st.name || 'Logout') : 'Login'; + bar.appendChild(auth); + document.body.appendChild(bar); document.body.classList.add('rm-crumbed'); } @@ -1099,18 +1170,67 @@ addCrumbs(); } + // ========================================================================== + // Login + // ========================================================================== + + // Das Original-Formular wird unveraendert wiederverwendet statt neu gebaut + // — wie der Beitragstext im Thread. So bleiben versteckte Felder (sid, + // redirect) und die serverseitige Validierung erhalten; das Script muss + // weder Sitzungs- noch Fehlerzustaende selbst nachbilden. + function buildLogin() { + var userInput = document.querySelector('input[name="username"]'); + var form = userInput && userInput.closest('form'); + if (!form) { log.push('login: kein Formular'); return; } + + var root = document.createElement('div'); + root.className = 'rm-root rm-login'; + + var th = form.querySelector('th'); + var title = document.createElement('div'); + title.className = 'rm-login-title'; + title.textContent = txt(th) || 'Login'; + root.appendChild(title); + + // Der Regeltext steckt als einziger Inhalt in td.row1 und wird separat als + // aufklappbarer Kasten gefuehrt statt mitten im Formular zu stehen. + var note = form.querySelector('.postbody'); + if (note && txt(note)) { + var det = document.createElement('details'); + det.className = 'rm-login-note'; + var sm = document.createElement('summary'); + sm.textContent = 'Regeln'; + det.appendChild(sm); + det.appendChild(note); + root.appendChild(det); + } + + // Zellen ohne Element-Kinder und ohne Text sind reine Layout-Fuellzellen + // (blosses " " als Platzhalter neben Checkboxen) — im gestapelten + // Formular bleiben sie sonst als leere Zeilen stehen. + Array.prototype.forEach.call(form.querySelectorAll('td'), function (td) { + if (!td.children.length && !txt(td)) td.classList.add('rm-hidden'); + }); + + root.appendChild(form); + replaceBody(root); + addCrumbs(); + log.push('login=formular'); + } + // ========================================================================== function run() { try { if (view === 'liste') buildList(); else if (view === 'thread') buildThread(); + else if (view === 'login') buildLogin(); } catch (e) { log.push('build: ' + e.message); } if (DEBUG) { var d = document.createElement('div'); d.className = 'rm-dbg'; - d.textContent = 'RIN Mobile v3.5\n' + log.join('\n'); + d.textContent = 'RIN Mobile v3.6\n' + log.join('\n'); document.body.insertBefore(d, document.body.firstChild); } }