diff --git a/CLAUDE.md b/CLAUDE.md index aa777c6..746dcc2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,11 +26,13 @@ Ein IIFE, streng der Reihe nach: der komplette Style-Block als Template-Literal 4. `replaceBody(root)` blendet alle bisherigen `body`-Kinder aus und haengt die neue Ansicht an -5. `buildList()` beziehungsweise `buildThread()` lesen das Original-DOM und +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 -6. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an -7. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'` +7. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an +8. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'` Auf Seiten ausserhalb der Weiche (`posting.php`, Profile, …) passiert nichts, insbesondere wird kein Stylesheet injiziert. @@ -44,11 +46,14 @@ Gleich benannte Regeln kollidierten mit identischer Spezifitaet, und die Ladereihenfolge der Userscripts entschied ueber das Layout. - Geteilt: `.rm-root`, `.rm-hidden`, `.rm-dbg`, die globale `img`-Regel +- Geteilt sind ausserdem die Leiste und ihr Sheet: `.rm-nav`, `.rm-sheet*`, + `.rm-goto`, `.rm-grid` - Unterschiede zwischen den Ansichten ueber einen Modifier am Root loesen, - nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-thread` haelt - so die 64px hinter der fixierten Leiste frei. -- Namen sind ansichtsspezifisch zu halten: `.rm-jump`/`.rm-pages` gehoeren der - Liste, `.rm-goto`/`.rm-grid` dem Thread-Sheet. + nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-paged` haelt + so die 64px hinter der fixierten Leiste frei — gesetzt von `pageNav()` + selbst, weil es die Leiste nicht auf jeder Liste gibt. +- Namen sind ansichtsspezifisch zu halten: `.rm-jump` und `.rm-group` gehoeren + der Liste, `.rm-post` und `.rm-sig` dem Thread. - `.rm-av` braucht `!important` auf Breite und Hoehe, sonst gewinnt die globale `img { height:auto !important }`-Regel gegen die Avatarmasse. @@ -112,6 +117,13 @@ Brotkrumen im Markup kein leerer Streifen bleibt. 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 @@ -150,6 +162,13 @@ Sinnvolle Faelle: erste Seite, zweite Seite, Mitte, letzte Seite, dazu die Degradation ohne `per_page` und ein Thread ohne Paginierung. Fixture-Eckdaten: `f=10&t=68689`, `per_page = 15`, 1114 Seiten, letzte Seite `start=16695`. +Dieselben Faelle gelten seit 3.5 fuer die Liste, die sich die Leiste mit dem +Thread teilt. Eckdaten von `dev/viewforum.php`: `f=10`, `per_page = 100`, 610 +Seiten, letzte Seite `start=60900`, dazu 4 Unterforen, 7 globale +Ankuendigungen, 2 Ankuendigungen, 8 Stickies und 92 Themen. Achtung beim +Nachbauen einer einseitigen Liste: die Seitenlinks zu entfernen genuegt nicht, +`Page 1 of 610` steht auch im Text und traegt die Gesamtzahl allein. + Fuer die Weiche und die Listenansicht genuegt eine handgeschriebene Tabelle mit `a.topictitle`-Zeilen. Zu pruefen ist dabei auch eine Seite ausserhalb der Weiche wie `posting.php`: dort darf weder eine Ansicht gebaut noch ein diff --git a/README.md b/README.md index b874bd1..9609d25 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,15 @@ 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. -The thread navigation bar holds 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. +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. 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 @@ -238,14 +240,15 @@ 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. -Die Navigationsleiste im Thread enthält 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. +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. Beide Ansichten haben oben eine fixierte Brotkrumenleiste, übernommen aus dem `Board index » … » Main Forum`-Pfad des Forums. Ohne sie führt aus einem Thread diff --git a/rin-mobile.user.js b/rin-mobile.user.js index 7712ee6..5e84601 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.4 +// @version 3.5 // @match *://cs.rin.ru/forum/* // @run-at document-start // ==/UserScript== @@ -57,8 +57,9 @@ width:100vw !important; max-width:100vw !important; margin:0 !important; padding:0 !important; overflow:hidden !important; } - /* Freihaltung hinter der fixierten Leiste der Threadansicht. */ - .rm-root.rm-thread { padding-bottom:64px !important; } + /* Freihaltung hinter der fixierten Seitenleiste. Die Klasse setzt die + 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 overflow:hidden und wuerde als Scrollport einer sticky-Leiste dienen, @@ -152,11 +153,6 @@ border:1px solid rgba(128,128,128,.4); border-radius:99px; text-decoration:none !important; color:inherit !important; white-space:nowrap; } - .rm-pages { padding:14px; display:flex; flex-wrap:wrap; gap:8px; } - .rm-pages a { padding:6px 11px; border-radius:8px; - border:1px solid rgba(128,128,128,.35); - text-decoration:none !important; color:inherit !important; } - /* === Thread === */ .rm-topic { box-sizing:border-box; padding:16px 14px 12px; @@ -204,6 +200,8 @@ .rm-sig { margin-top:12px; font-size:13px; opacity:.5; } .rm-sig summary { cursor:pointer; opacity:.8; } + /* === Seitennavigation, beide Ansichten === */ + .rm-nav { position:fixed; left:0; right:0; bottom:0; z-index:9999; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:10px 12px calc(10px + env(safe-area-inset-bottom)); @@ -314,6 +312,223 @@ document.body.classList.add('rm-crumbed'); } + // ========================================================================== + // Seitennavigation, beide Ansichten + // ========================================================================== + + // Foren- und Themenseiten paginieren gleich: derselbe start-Parameter, + // dieselbe jumpto()-Funktion mit demselben per_page darin. Der Block gilt + // deshalb fuer beide Ansichten. + + // --- Seitenzustand --- + // Der Block wird aus dem Original-DOM gelesen, bevor es ausgeblendet wird. + // phpBB zeigt nie alle Seitenzahlen an, deshalb wird nicht auf gerenderte + // Links gesetzt, sondern jede Ziel-URL aus start = (Seite - 1) * per_page + // selbst gebaut. + function pageState() { + var s = { cur: 1, total: 1, per: 0, prev: null, next: null }; + + // Der Paginierungsblock ist der einzige Ort, an dem Ziffern-Links + // Seitenzahlen bedeuten. Beitragstexte enthalten ebenfalls Links mit + // start=, die hier nicht mitgezaehlt werden duerfen. + var box = null; + var jumpA = document.querySelector('a[onclick*="jumpto"]'); + if (jumpA) box = jumpA.parentElement; + if (!box) { + document.querySelectorAll('a[href*="start="]').forEach(function (a) { + if (!box && /^(Next|Weiter|Nächste)$/i.test(txt(a))) + box = a.parentElement; + }); + } + + // per_page steht als Literal in der jumpto()-Funktion der Seite. + document.querySelectorAll('script').forEach(function (el) { + if (s.per) return; + var m2 = (el.textContent || '').match(/per_page\s*=\s*'?(\d+)/); + if (m2) s.per = parseInt(m2[1], 10); + }); + + if (box) { + box.querySelectorAll('a[href*="start="]').forEach(function (a) { + var t = txt(a); + if (!/^\d+$/.test(t)) return; + var n = parseInt(t, 10); + if (n > s.total) s.total = n; + var ms = a.href.match(/[?&]start=(\d+)/); + if (!s.per && ms && n > 1) { + var per = parseInt(ms[1], 10) / (n - 1); + if (per > 0 && per === Math.floor(per)) s.per = per; + } + }); + } + + var cs = location.search.match(/[?&]start=(\d+)/); + if (s.per) s.cur = Math.floor((cs ? parseInt(cs[1], 10) : 0) / s.per) + 1; + else if (box) { + var strong = box.querySelector('strong, b > strong'); + if (strong && /^\d+$/.test(txt(strong))) s.cur = parseInt(txt(strong), 10); + } + + // Aeltere Themes geben stattdessen "Page X of Y" aus. + var pos = txt(document.body).match(/Page\s+(\d+)\s+of\s+(\d+)/i); + if (pos) { + if (!s.per) s.cur = parseInt(pos[1], 10); + s.total = Math.max(s.total, parseInt(pos[2], 10)); + } + if (s.cur > s.total) s.total = s.cur; + + s.url = function (n) { + if (n === s.cur || n < 1 || n > s.total) return null; + if (n !== 1 && !s.per) return null; + var u = location.href.split('#')[0]; + var start = (n - 1) * s.per; + if (/[?&]start=\d+/.test(u)) + return u.replace(/([?&]start=)\d+/, '$1' + start); + return u + (u.indexOf('?') === -1 ? '?' : '&') + 'start=' + start; + }; + + s.prev = s.url(s.cur - 1); + s.next = s.url(s.cur + 1); + // Ohne bekanntes per_page bleibt nur, die Original-Links zu uebernehmen. + if (!s.per) { + document.querySelectorAll('a').forEach(function (a) { + var t = txt(a); + if (!s.prev && /^(Previous|Vorherige)$/i.test(t)) s.prev = a.href; + if (!s.next && /^(Next|Weiter|Nächste)$/i.test(t)) s.next = a.href; + }); + } + + log.push('page=' + s.cur + '/' + s.total + ' per=' + s.per); + return s; + } + + // Seitenwahl als Sheet ueber der Leiste. Wird erst beim ersten Antippen + // gebaut: bei 1000+ Seiten waere das Raster sonst umsonst im DOM. + function pageSheet(s) { + var bg = null, sheet = null; + + function go(n) { + n = Math.min(Math.max(n, 1), s.total); + var u = s.url(n); + if (u) location.href = u; + else close(); + } + + function close() { + if (!bg) return; + bg.classList.remove('rm-open'); + sheet.classList.remove('rm-open'); + document.body.style.overflow = ''; + } + + function make() { + bg = document.createElement('div'); + bg.className = 'rm-sheet-bg'; + bg.addEventListener('click', close); + + sheet = document.createElement('div'); + sheet.className = 'rm-sheet'; + + var grip = document.createElement('div'); + grip.className = 'rm-grip'; + sheet.appendChild(grip); + + var row = document.createElement('div'); + row.className = 'rm-goto'; + var inp = document.createElement('input'); + inp.type = 'text'; + inp.inputMode = 'numeric'; + inp.pattern = '[0-9]*'; + inp.placeholder = 'Seite 1 – ' + s.total; + inp.setAttribute('aria-label', 'Seitenzahl'); + var btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = 'Los'; + function submit() { + var n = parseInt(inp.value, 10); + if (n > 0) go(n); + } + btn.addEventListener('click', submit); + inp.addEventListener('keydown', function (e) { + if (e.key === 'Enter') { e.preventDefault(); submit(); } + }); + row.appendChild(inp); + row.appendChild(btn); + sheet.appendChild(row); + + var grid = document.createElement('div'); + grid.className = 'rm-grid'; + var curEl = null; + for (var i = 1; i <= s.total; i++) { + var a = document.createElement('a'); + a.textContent = i; + if (i === s.cur) { a.className = 'rm-cur'; curEl = a; } + else { + var u = s.url(i); + if (u) a.href = u; + } + grid.appendChild(a); + } + sheet.appendChild(grid); + + document.body.appendChild(bg); + document.body.appendChild(sheet); + return curEl; + } + + return function open() { + var curEl = bg ? null : make(); + document.body.style.overflow = 'hidden'; + // Ein Frame Abstand, sonst faellt der Uebergang beim ersten Oeffnen aus. + requestAnimationFrame(function () { + bg.classList.add('rm-open'); + sheet.classList.add('rm-open'); + if (curEl) curEl.scrollIntoView({ block: 'center' }); + }); + }; + } + + // Die fixierte Leiste am unteren Rand. Der Aufrufer reicht den Zustand + // herein, weil er allein entscheidet, ob die Leiste ueberhaupt lohnt: im + // Thread gehoert sie zum Geruest, eine einseitige Liste braucht sie nicht. + function pageNav(root, pg) { + var nav = document.createElement('div'); + nav.className = 'rm-nav'; + + function navBtn(href, label, aria) { + var e = document.createElement('a'); + e.className = 'rm-step' + (href ? '' : ' rm-off'); + e.textContent = label; + e.setAttribute('aria-label', aria); + if (href) e.href = href; + return e; + } + nav.appendChild(navBtn(pg.url(1), '«', 'Erste Seite')); + nav.appendChild(navBtn(pg.prev, '‹', 'Vorherige Seite')); + + var pos = document.createElement('span'); + pos.className = 'rm-pos'; + pos.textContent = pg.cur + ' / ' + pg.total; + // Ohne bekanntes per_page laesst sich kein Sprungziel bauen, dann bleibt + // die Anzeige reine Beschriftung. + if (pg.total > 1 && pg.per) { + pos.className += ' rm-pos-btn'; + pos.textContent += ' ▾'; + pos.setAttribute('role', 'button'); + pos.setAttribute('aria-label', 'Seite wählen'); + pos.addEventListener('click', pageSheet(pg)); + } + nav.appendChild(pos); + + nav.appendChild(navBtn(pg.next, '›', 'Nächste Seite')); + nav.appendChild(navBtn(pg.url(pg.total), '»', 'Letzte Seite')); + + // Die Freihaltung haengt an der Leiste selbst: ohne sie stuende unten ein + // leerer Streifen. + root.classList.add('rm-paged'); + root.appendChild(nav); + } + // ========================================================================== // Themenliste und Forenliste // ========================================================================== @@ -704,18 +919,10 @@ log.push('karten=' + built); if (!built) return; - var seen = {}, pages = document.createElement('div'); - pages.className = 'rm-pages'; - document.querySelectorAll('a[href*="start="]').forEach(function (a) { - var t = txt(a); - if (!t || t.length > 4 || seen[t]) return; - seen[t] = 1; - var n = document.createElement('a'); - n.href = a.href; - n.textContent = t; - pages.appendChild(n); - }); - if (pages.children.length) root.appendChild(pages); + // Muss vor replaceBody() laufen: pageState() liest das Original-DOM, und + // "Page X of Y" wuerde sonst im Text der eigenen Karten gesucht. + var pg = pageState(); + if (pg.total > 1) pageNav(root, pg); replaceBody(root); addCrumbs(); @@ -747,181 +954,13 @@ return null; } - // --- Seitenzustand --- - // Der Block wird aus dem Original-DOM gelesen, bevor es ausgeblendet wird. - // phpBB zeigt nie alle Seitenzahlen an, deshalb wird nicht auf gerenderte - // Links gesetzt, sondern jede Ziel-URL aus start = (Seite - 1) * per_page - // selbst gebaut. - function pageState() { - var s = { cur: 1, total: 1, per: 0, prev: null, next: null }; - - // Der Paginierungsblock ist der einzige Ort, an dem Ziffern-Links - // Seitenzahlen bedeuten. Beitragstexte enthalten ebenfalls Links mit - // start=, die hier nicht mitgezaehlt werden duerfen. - var box = null; - var jumpA = document.querySelector('a[onclick*="jumpto"]'); - if (jumpA) box = jumpA.parentElement; - if (!box) { - document.querySelectorAll('a[href*="start="]').forEach(function (a) { - if (!box && /^(Next|Weiter|Nächste)$/i.test(txt(a))) - box = a.parentElement; - }); - } - - // per_page steht als Literal in der jumpto()-Funktion der Seite. - document.querySelectorAll('script').forEach(function (el) { - if (s.per) return; - var m2 = (el.textContent || '').match(/per_page\s*=\s*'?(\d+)/); - if (m2) s.per = parseInt(m2[1], 10); - }); - - if (box) { - box.querySelectorAll('a[href*="start="]').forEach(function (a) { - var t = txt(a); - if (!/^\d+$/.test(t)) return; - var n = parseInt(t, 10); - if (n > s.total) s.total = n; - var ms = a.href.match(/[?&]start=(\d+)/); - if (!s.per && ms && n > 1) { - var per = parseInt(ms[1], 10) / (n - 1); - if (per > 0 && per === Math.floor(per)) s.per = per; - } - }); - } - - var cs = location.search.match(/[?&]start=(\d+)/); - if (s.per) s.cur = Math.floor((cs ? parseInt(cs[1], 10) : 0) / s.per) + 1; - else if (box) { - var strong = box.querySelector('strong, b > strong'); - if (strong && /^\d+$/.test(txt(strong))) s.cur = parseInt(txt(strong), 10); - } - - // Aeltere Themes geben stattdessen "Page X of Y" aus. - var pos = txt(document.body).match(/Page\s+(\d+)\s+of\s+(\d+)/i); - if (pos) { - if (!s.per) s.cur = parseInt(pos[1], 10); - s.total = Math.max(s.total, parseInt(pos[2], 10)); - } - if (s.cur > s.total) s.total = s.cur; - - s.url = function (n) { - if (n === s.cur || n < 1 || n > s.total) return null; - if (n !== 1 && !s.per) return null; - var u = location.href.split('#')[0]; - var start = (n - 1) * s.per; - if (/[?&]start=\d+/.test(u)) - return u.replace(/([?&]start=)\d+/, '$1' + start); - return u + (u.indexOf('?') === -1 ? '?' : '&') + 'start=' + start; - }; - - s.prev = s.url(s.cur - 1); - s.next = s.url(s.cur + 1); - // Ohne bekanntes per_page bleibt nur, die Original-Links zu uebernehmen. - if (!s.per) { - document.querySelectorAll('a').forEach(function (a) { - var t = txt(a); - if (!s.prev && /^(Previous|Vorherige)$/i.test(t)) s.prev = a.href; - if (!s.next && /^(Next|Weiter|Nächste)$/i.test(t)) s.next = a.href; - }); - } - - log.push('page=' + s.cur + '/' + s.total + ' per=' + s.per); - return s; - } - - // Seitenwahl als Sheet ueber der Leiste. Wird erst beim ersten Antippen - // gebaut: bei 1000+ Seiten waere das Raster sonst umsonst im DOM. - function pageSheet(s) { - var bg = null, sheet = null; - - function go(n) { - n = Math.min(Math.max(n, 1), s.total); - var u = s.url(n); - if (u) location.href = u; - else close(); - } - - function close() { - if (!bg) return; - bg.classList.remove('rm-open'); - sheet.classList.remove('rm-open'); - document.body.style.overflow = ''; - } - - function make() { - bg = document.createElement('div'); - bg.className = 'rm-sheet-bg'; - bg.addEventListener('click', close); - - sheet = document.createElement('div'); - sheet.className = 'rm-sheet'; - - var grip = document.createElement('div'); - grip.className = 'rm-grip'; - sheet.appendChild(grip); - - var row = document.createElement('div'); - row.className = 'rm-goto'; - var inp = document.createElement('input'); - inp.type = 'text'; - inp.inputMode = 'numeric'; - inp.pattern = '[0-9]*'; - inp.placeholder = 'Seite 1 – ' + s.total; - inp.setAttribute('aria-label', 'Seitenzahl'); - var btn = document.createElement('button'); - btn.type = 'button'; - btn.textContent = 'Los'; - function submit() { - var n = parseInt(inp.value, 10); - if (n > 0) go(n); - } - btn.addEventListener('click', submit); - inp.addEventListener('keydown', function (e) { - if (e.key === 'Enter') { e.preventDefault(); submit(); } - }); - row.appendChild(inp); - row.appendChild(btn); - sheet.appendChild(row); - - var grid = document.createElement('div'); - grid.className = 'rm-grid'; - var curEl = null; - for (var i = 1; i <= s.total; i++) { - var a = document.createElement('a'); - a.textContent = i; - if (i === s.cur) { a.className = 'rm-cur'; curEl = a; } - else { - var u = s.url(i); - if (u) a.href = u; - } - grid.appendChild(a); - } - sheet.appendChild(grid); - - document.body.appendChild(bg); - document.body.appendChild(sheet); - return curEl; - } - - return function open() { - var curEl = bg ? null : make(); - document.body.style.overflow = 'hidden'; - // Ein Frame Abstand, sonst faellt der Uebergang beim ersten Oeffnen aus. - requestAnimationFrame(function () { - bg.classList.add('rm-open'); - sheet.classList.add('rm-open'); - if (curEl) curEl.scrollIntoView({ block: 'center' }); - }); - }; - } - function buildThread() { var bodies = document.querySelectorAll('span.postbody, .postbody'); log.push('postbodies=' + bodies.length); if (!bodies.length) return; var root = document.createElement('div'); - root.className = 'rm-root rm-thread'; + root.className = 'rm-root'; var tt = document.querySelector('a.maintitle, h1, .maintitle'); if (tt) { @@ -1054,39 +1093,7 @@ log.push('posts=' + built); if (!built) return; - // --- Navigationsleiste --- - var pg = pageState(); - var nav = document.createElement('div'); - nav.className = 'rm-nav'; - - function navBtn(href, label, aria) { - var e = document.createElement('a'); - e.className = 'rm-step' + (href ? '' : ' rm-off'); - e.textContent = label; - e.setAttribute('aria-label', aria); - if (href) e.href = href; - return e; - } - nav.appendChild(navBtn(pg.url(1), '«', 'Erste Seite')); - nav.appendChild(navBtn(pg.prev, '‹', 'Vorherige Seite')); - - var pos = document.createElement('span'); - pos.className = 'rm-pos'; - pos.textContent = pg.cur + ' / ' + pg.total; - // Ohne bekanntes per_page laesst sich kein Sprungziel bauen, dann bleibt - // die Anzeige reine Beschriftung. - if (pg.total > 1 && pg.per) { - pos.className += ' rm-pos-btn'; - pos.textContent += ' ▾'; - pos.setAttribute('role', 'button'); - pos.setAttribute('aria-label', 'Seite wählen'); - pos.addEventListener('click', pageSheet(pg)); - } - nav.appendChild(pos); - - nav.appendChild(navBtn(pg.next, '›', 'Nächste Seite')); - nav.appendChild(navBtn(pg.url(pg.total), '»', 'Letzte Seite')); - root.appendChild(nav); + pageNav(root, pageState()); replaceBody(root); addCrumbs(); @@ -1103,7 +1110,7 @@ if (DEBUG) { var d = document.createElement('div'); d.className = 'rm-dbg'; - d.textContent = 'RIN Mobile v3.4\n' + log.join('\n'); + d.textContent = 'RIN Mobile v3.5\n' + log.join('\n'); document.body.insertBefore(d, document.body.firstChild); } }