mirror of
https://github.com/Andre-Bzk/rin-mobile-userscript.git
synced 2026-11-15 15:43:07 +01:00
feat: give the list view the same page navigation as the thread
The list built its own page bar from every link on the page carrying a start parameter. Topic rows bring their own "Go to page: 1 … 42, 43" along, so on the main forum that bar came to 103 entries, most of them jumping into the middle of some unrelated thread rather than to a page of the list. Even the correct entries were only the handful phpBB renders, which for 610 pages means there is no way to reach page 300. The thread already solved this: read per_page out of the page's own jumpto function, compute every target, and offer them through a fixed bar and a sheet. Both views paginate over the same parameter, so the bar, the sheet and the state behind them now sit in one place and serve both. The list leaves the bar out where a single page holds everything, which a thread of posts never does but a category page always does. Bottom clearance moved onto the bar for that reason — tied to the view, it would reserve an empty strip under lists that have no bar.
This commit is contained in:
@@ -26,11 +26,13 @@ Ein IIFE, streng der Reihe nach:
|
|||||||
der komplette Style-Block als Template-Literal
|
der komplette Style-Block als Template-Literal
|
||||||
4. `replaceBody(root)` blendet alle bisherigen `body`-Kinder aus und haengt die
|
4. `replaceBody(root)` blendet alle bisherigen `body`-Kinder aus und haengt die
|
||||||
neue Ansicht an
|
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
|
bauen ein `div.rm-root`; beide haengen zum Schluss ueber `addCrumbs()` die
|
||||||
fixierte Brotkrumenleiste an
|
fixierte Brotkrumenleiste an
|
||||||
6. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an
|
7. `run()` waehlt die Ansicht und haengt bei `DEBUG` das gruene Overlay an
|
||||||
7. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'`
|
8. Start ueber `DOMContentLoaded`, falls `readyState === 'loading'`
|
||||||
|
|
||||||
Auf Seiten ausserhalb der Weiche (`posting.php`, Profile, …) passiert nichts,
|
Auf Seiten ausserhalb der Weiche (`posting.php`, Profile, …) passiert nichts,
|
||||||
insbesondere wird kein Stylesheet injiziert.
|
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.
|
Ladereihenfolge der Userscripts entschied ueber das Layout.
|
||||||
|
|
||||||
- Geteilt: `.rm-root`, `.rm-hidden`, `.rm-dbg`, die globale `img`-Regel
|
- 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,
|
- Unterschiede zwischen den Ansichten ueber einen Modifier am Root loesen,
|
||||||
nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-thread` haelt
|
nicht ueber zwei Regeln mit demselben Selektor. `.rm-root.rm-paged` haelt
|
||||||
so die 64px hinter der fixierten Leiste frei.
|
so die 64px hinter der fixierten Leiste frei — gesetzt von `pageNav()`
|
||||||
- Namen sind ansichtsspezifisch zu halten: `.rm-jump`/`.rm-pages` gehoeren der
|
selbst, weil es die Leiste nicht auf jeder Liste gibt.
|
||||||
Liste, `.rm-goto`/`.rm-grid` dem Thread-Sheet.
|
- 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
|
- `.rm-av` braucht `!important` auf Breite und Hoehe, sonst gewinnt die globale
|
||||||
`img { height:auto !important }`-Regel gegen die Avatarmasse.
|
`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
|
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.
|
`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**
|
**`per_page` kommt aus der `jumpto()`-Funktion der Seite**
|
||||||
(`var per_page = '15';`). Nur damit lassen sich Seiten-URLs als
|
(`var per_page = '15';`). Nur damit lassen sich Seiten-URLs als
|
||||||
`start = (Seite - 1) * per_page` selbst bauen. Die gerenderten Seitenlinks
|
`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:
|
Degradation ohne `per_page` und ein Thread ohne Paginierung. Fixture-Eckdaten:
|
||||||
`f=10&t=68689`, `per_page = 15`, 1114 Seiten, letzte Seite `start=16695`.
|
`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
|
Fuer die Weiche und die Listenansicht genuegt eine handgeschriebene Tabelle mit
|
||||||
`a.topictitle`-Zeilen. Zu pruefen ist dabei auch eine Seite ausserhalb der
|
`a.topictitle`-Zeilen. Zu pruefen ist dabei auch eine Seite ausserhalb der
|
||||||
Weiche wie `posting.php`: dort darf weder eine Ansicht gebaut noch ein
|
Weiche wie `posting.php`: dort darf weder eine Ansicht gebaut noch ein
|
||||||
|
|||||||
@@ -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
|
script-writable storage after long inactivity, which resets the sections to
|
||||||
their default.
|
their default.
|
||||||
|
|
||||||
The thread navigation bar holds first / previous / next / last and a page
|
Both views share the same fixed navigation bar at the bottom: first / previous /
|
||||||
indicator. Tapping the indicator opens a sheet with a number field and a grid of
|
next / last and a page indicator. Tapping the indicator opens a sheet with a
|
||||||
all pages. Targets are computed as `start = (page - 1) × per_page` rather than
|
number field and a grid of all pages. Targets are computed as
|
||||||
taken from the rendered page links, because phpBB only ever shows a handful of
|
`start = (page - 1) × per_page` rather than taken from the rendered page links,
|
||||||
them. `per_page` is read from the `jumpto()` function the forum embeds in the
|
because phpBB only ever shows a handful of them. `per_page` is read from the
|
||||||
page; without it, only first page and the original previous/next links remain
|
`jumpto()` function the forum embeds in the page; without it, only first page
|
||||||
available.
|
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
|
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
|
`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
|
schreibbaren Speicher nach längerer Nichtnutzung räumen, dann stehen die
|
||||||
Abschnitte wieder auf ihrer Voreinstellung.
|
Abschnitte wieder auf ihrer Voreinstellung.
|
||||||
|
|
||||||
Die Navigationsleiste im Thread enthält erste / vorherige / nächste / letzte
|
Beide Ansichten teilen sich dieselbe fixierte Navigationsleiste am unteren
|
||||||
Seite und eine Positionsanzeige. Ein Tipp darauf öffnet ein Sheet mit
|
Rand: erste / vorherige / nächste / letzte Seite und eine Positionsanzeige. Ein
|
||||||
Zahlenfeld und einem Raster aller Seiten. Die Ziele werden als
|
Tipp darauf öffnet ein Sheet mit Zahlenfeld und einem Raster aller Seiten. Die
|
||||||
`start = (Seite - 1) × per_page` berechnet und nicht aus den gerenderten
|
Ziele werden als `start = (Seite - 1) × per_page` berechnet und nicht aus den
|
||||||
Seitenlinks übernommen, weil phpBB davon immer nur eine Handvoll anzeigt.
|
gerenderten Seitenlinks übernommen, weil phpBB davon immer nur eine Handvoll
|
||||||
`per_page` stammt aus der `jumpto()`-Funktion, die das Forum in die Seite
|
anzeigt. `per_page` stammt aus der `jumpto()`-Funktion, die das Forum in die
|
||||||
einbettet; fehlt sie, bleiben nur die erste Seite und die ursprünglichen
|
Seite einbettet; fehlt sie, bleiben nur die erste Seite und die ursprünglichen
|
||||||
Zurück-/Weiter-Links nutzbar.
|
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
|
Beide Ansichten haben oben eine fixierte Brotkrumenleiste, übernommen aus dem
|
||||||
`Board index » … » Main Forum`-Pfad des Forums. Ohne sie führt aus einem Thread
|
`Board index » … » Main Forum`-Pfad des Forums. Ohne sie führt aus einem Thread
|
||||||
|
|||||||
+230
-223
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name RIN Mobile
|
// @name RIN Mobile
|
||||||
// @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom
|
// @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom
|
||||||
// @version 3.4
|
// @version 3.5
|
||||||
// @match *://cs.rin.ru/forum/*
|
// @match *://cs.rin.ru/forum/*
|
||||||
// @run-at document-start
|
// @run-at document-start
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
@@ -57,8 +57,9 @@
|
|||||||
width:100vw !important; max-width:100vw !important;
|
width:100vw !important; max-width:100vw !important;
|
||||||
margin:0 !important; padding:0 !important;
|
margin:0 !important; padding:0 !important;
|
||||||
overflow:hidden !important; }
|
overflow:hidden !important; }
|
||||||
/* Freihaltung hinter der fixierten Leiste der Threadansicht. */
|
/* Freihaltung hinter der fixierten Seitenleiste. Die Klasse setzt die
|
||||||
.rm-root.rm-thread { padding-bottom:64px !important; }
|
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, beide Ansichten. Fixiert statt sticky: .rm-root traegt
|
||||||
overflow:hidden und wuerde als Scrollport einer sticky-Leiste dienen,
|
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;
|
border:1px solid rgba(128,128,128,.4); border-radius:99px;
|
||||||
text-decoration:none !important; color:inherit !important;
|
text-decoration:none !important; color:inherit !important;
|
||||||
white-space:nowrap; }
|
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 === */
|
/* === Thread === */
|
||||||
|
|
||||||
.rm-topic { box-sizing:border-box; padding:16px 14px 12px;
|
.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 { margin-top:12px; font-size:13px; opacity:.5; }
|
||||||
.rm-sig summary { cursor:pointer; opacity:.8; }
|
.rm-sig summary { cursor:pointer; opacity:.8; }
|
||||||
|
|
||||||
|
/* === Seitennavigation, beide Ansichten === */
|
||||||
|
|
||||||
.rm-nav { position:fixed; left:0; right:0; bottom:0; z-index:9999;
|
.rm-nav { position:fixed; left:0; right:0; bottom:0; z-index:9999;
|
||||||
display:flex; align-items:center; justify-content:space-between;
|
display:flex; align-items:center; justify-content:space-between;
|
||||||
gap:8px; padding:10px 12px calc(10px + env(safe-area-inset-bottom));
|
gap:8px; padding:10px 12px calc(10px + env(safe-area-inset-bottom));
|
||||||
@@ -314,6 +312,223 @@
|
|||||||
document.body.classList.add('rm-crumbed');
|
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
|
// Themenliste und Forenliste
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -704,18 +919,10 @@
|
|||||||
log.push('karten=' + built);
|
log.push('karten=' + built);
|
||||||
if (!built) return;
|
if (!built) return;
|
||||||
|
|
||||||
var seen = {}, pages = document.createElement('div');
|
// Muss vor replaceBody() laufen: pageState() liest das Original-DOM, und
|
||||||
pages.className = 'rm-pages';
|
// "Page X of Y" wuerde sonst im Text der eigenen Karten gesucht.
|
||||||
document.querySelectorAll('a[href*="start="]').forEach(function (a) {
|
var pg = pageState();
|
||||||
var t = txt(a);
|
if (pg.total > 1) pageNav(root, pg);
|
||||||
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);
|
|
||||||
|
|
||||||
replaceBody(root);
|
replaceBody(root);
|
||||||
addCrumbs();
|
addCrumbs();
|
||||||
@@ -747,181 +954,13 @@
|
|||||||
return null;
|
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() {
|
function buildThread() {
|
||||||
var bodies = document.querySelectorAll('span.postbody, .postbody');
|
var bodies = document.querySelectorAll('span.postbody, .postbody');
|
||||||
log.push('postbodies=' + bodies.length);
|
log.push('postbodies=' + bodies.length);
|
||||||
if (!bodies.length) return;
|
if (!bodies.length) return;
|
||||||
|
|
||||||
var root = document.createElement('div');
|
var root = document.createElement('div');
|
||||||
root.className = 'rm-root rm-thread';
|
root.className = 'rm-root';
|
||||||
|
|
||||||
var tt = document.querySelector('a.maintitle, h1, .maintitle');
|
var tt = document.querySelector('a.maintitle, h1, .maintitle');
|
||||||
if (tt) {
|
if (tt) {
|
||||||
@@ -1054,39 +1093,7 @@
|
|||||||
log.push('posts=' + built);
|
log.push('posts=' + built);
|
||||||
if (!built) return;
|
if (!built) return;
|
||||||
|
|
||||||
// --- Navigationsleiste ---
|
pageNav(root, pageState());
|
||||||
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);
|
|
||||||
|
|
||||||
replaceBody(root);
|
replaceBody(root);
|
||||||
addCrumbs();
|
addCrumbs();
|
||||||
@@ -1103,7 +1110,7 @@
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
var d = document.createElement('div');
|
var d = document.createElement('div');
|
||||||
d.className = 'rm-dbg';
|
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);
|
document.body.insertBefore(d, document.body.firstChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user