mirror of
https://github.com/Andre-Bzk/rin-mobile-userscript.git
synced 2026-11-15 09:33:08 +01:00
feat: group the list view by section and let the groups collapse
Every page of a forum repeats its global announcements, announcements and stickies before the first real topic. On a phone that is the whole first screen, scrolled past on every page turn, and the rebuild made it worse: the board separates those blocks with header rows the card stream dropped, so what remained was one undifferentiated run of cards. Each block now carries a header with its name and card count, and tapping it folds the block away. The announcement blocks start folded, which is the point of the change; folding rather than filtering keeps them reachable without editing the script. The state is kept per kind, so a decision taken in one forum holds in all of them. localStorage rather than GM_setValue: no @grant, and identical behaviour in Tampermonkey and the iOS app, against the risk that Safari clears the value after long inactivity and the sections return to their default. That is a reset of one preference, not data loss. Rows are classified by the icon in their first column instead of a title prefix, because the board marks the type nowhere else. The header labels come from the board's own separator rows, so sections nobody anticipated still read correctly. The menu bar links the forum rules and the FAQ as topics. That row has neither a topic title nor numeric columns, but it passed the layout brake and had been yielding a ghost card at the head of every list — visible now that it would claim a section header of its own.
This commit is contained in:
+144
-32
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name RIN Mobile
|
||||
// @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom
|
||||
// @version 3.3
|
||||
// @version 3.4
|
||||
// @match *://cs.rin.ru/forum/*
|
||||
// @run-at document-start
|
||||
// ==/UserScript==
|
||||
@@ -101,12 +101,21 @@
|
||||
/* Foren und Themen stehen auf einer Forenseite in derselben Tabelle
|
||||
untereinander. Im Kartenstrom unterscheiden sie sich sonst nur am Text,
|
||||
der Uebergang von den Unterforen zu den Themen ist nicht mehr zu sehen. */
|
||||
.rm-group { display:flex; align-items:baseline; justify-content:space-between;
|
||||
gap:10px; padding:11px 14px; font-size:12px; font-weight:700;
|
||||
.rm-group { display:flex; align-items:baseline; gap:10px;
|
||||
padding:11px 14px; font-size:12px; font-weight:700;
|
||||
text-transform:uppercase; letter-spacing:.07em; opacity:.75;
|
||||
background:rgba(128,128,128,.16);
|
||||
border-bottom:1px solid rgba(128,128,128,.28); }
|
||||
.rm-group .rm-count { font-weight:600; letter-spacing:0; opacity:.7; }
|
||||
border-bottom:1px solid rgba(128,128,128,.28);
|
||||
cursor:pointer; -webkit-tap-highlight-color:transparent;
|
||||
-webkit-user-select:none; user-select:none; }
|
||||
.rm-group:active { background:rgba(128,128,128,.26); }
|
||||
.rm-group .rm-gname { flex:1; min-width:0; overflow:hidden;
|
||||
text-overflow:ellipsis; white-space:nowrap; }
|
||||
.rm-group .rm-count { flex:none; font-weight:600; letter-spacing:0;
|
||||
opacity:.7; }
|
||||
.rm-caret { flex:none; display:inline-block; font-size:10px; opacity:.7;
|
||||
transition:transform .15s; }
|
||||
.rm-group.rm-closed .rm-caret { transform:rotate(-90deg); }
|
||||
|
||||
.rm-card { box-sizing:border-box !important;
|
||||
display:block !important;
|
||||
@@ -493,20 +502,105 @@
|
||||
return el;
|
||||
}
|
||||
|
||||
// Kopf ueber einem Block gleichartiger Karten. Die Originalseite trennt Foren
|
||||
// und Themen ueber zwei Tabellen mit eigenem Kopf; im Kartenstrom geht diese
|
||||
// Grenze verloren.
|
||||
function groupHead(kind, n) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'rm-group';
|
||||
// Ankuendigungen und Stickies stehen vor jedem Thema jeder Seite eines
|
||||
// Forums. Ihr Typ steht im Symbol der ersten Spalte, nicht am Titel:
|
||||
// global_read.png, announce_read.png, sticky_read.png gegen topic_read.png.
|
||||
function topicGroup(tr) {
|
||||
var im = tr.querySelector('img[src]');
|
||||
var m = im && (im.getAttribute('src') || '')
|
||||
.match(/\b(global|announce|sticky)_(?:un)?read/);
|
||||
return m ? m[1] : 'thema';
|
||||
}
|
||||
|
||||
// phpBB stellt jeder dieser Gruppen eine Trennzeile voran. Sie traegt keinen
|
||||
// Link und faellt deshalb beim Einsammeln durch — ihr Text ist aber die
|
||||
// ehrlichste Beschriftung, die es fuer die Gruppe gibt, und deckt auch
|
||||
// Abschnitte ab, die dieses Script nicht kennt.
|
||||
function sepLabel(tr) {
|
||||
var p = tr.previousElementSibling, n = 0;
|
||||
while (p && n++ < 4) {
|
||||
if (p.dataset.rm) return '';
|
||||
if (p.children.length === 1 && p.children[0].hasAttribute('colspan') &&
|
||||
!p.querySelector('a, table')) {
|
||||
var t = txt(p);
|
||||
if (t && t.length < 40) return t;
|
||||
}
|
||||
p = p.previousElementSibling;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
var GNAME = { forum: 'Foren', thema: 'Themen', sticky: 'Stickies',
|
||||
global: 'Globale Ankündigungen', announce: 'Ankündigungen' };
|
||||
// Genau die Gruppen, die zwischen dem Leser und den eigentlichen Themen
|
||||
// stehen, starten zugeklappt.
|
||||
var GSHUT = { global: 1, announce: 1, sticky: 1 };
|
||||
|
||||
// Der Zustand liegt in localStorage und nicht in GM_setValue: das kommt ohne
|
||||
// @grant aus und verhaelt sich in Tampermonkey und der iOS-App gleich. Der
|
||||
// Schluessel ist die Gruppenart, nicht ihr Name — einmal zugeklappt gilt
|
||||
// fuer jedes Forum.
|
||||
var GKEY = 'rm-groups';
|
||||
|
||||
function groupState() {
|
||||
try { return JSON.parse(localStorage.getItem(GKEY)) || {}; }
|
||||
catch (e) { return {}; }
|
||||
}
|
||||
|
||||
function saveGroupState(group, open) {
|
||||
try {
|
||||
var s = groupState();
|
||||
s[group] = open;
|
||||
localStorage.setItem(GKEY, JSON.stringify(s));
|
||||
} catch (e) { log.push('speichern: ' + e.message); }
|
||||
}
|
||||
|
||||
// Kopf ueber einem Block gleichartiger Karten, dazu der Kasten, in den sie
|
||||
// gehoeren: die Originalseite trennt Foren, Ankuendigungen, Stickies und
|
||||
// Themen ueber eigene Kopfzeilen, im Kartenstrom geht diese Grenze verloren.
|
||||
// Der Kopf ist zugleich der Schalter, der den Block zuklappt.
|
||||
function openGroup(root, group, label, n) {
|
||||
var saved = groupState()[group];
|
||||
var open = saved === undefined ? !GSHUT[group] : !!saved;
|
||||
|
||||
var head = document.createElement('div');
|
||||
head.className = 'rm-group';
|
||||
head.setAttribute('role', 'button');
|
||||
|
||||
var t = document.createElement('span');
|
||||
t.textContent = kind === 'forum' ? 'Foren' : 'Themen';
|
||||
el.appendChild(t);
|
||||
t.className = 'rm-gname';
|
||||
t.textContent = label;
|
||||
head.appendChild(t);
|
||||
|
||||
var c = document.createElement('span');
|
||||
c.className = 'rm-count';
|
||||
c.textContent = n;
|
||||
el.appendChild(c);
|
||||
return el;
|
||||
head.appendChild(c);
|
||||
|
||||
var caret = document.createElement('span');
|
||||
caret.className = 'rm-caret';
|
||||
caret.textContent = '▼';
|
||||
head.appendChild(caret);
|
||||
|
||||
var body = document.createElement('div');
|
||||
body.className = 'rm-gbody';
|
||||
|
||||
function apply() {
|
||||
head.classList.toggle('rm-closed', !open);
|
||||
body.classList.toggle('rm-hidden', !open);
|
||||
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
}
|
||||
apply();
|
||||
|
||||
head.addEventListener('click', function () {
|
||||
open = !open;
|
||||
apply();
|
||||
saveGroupState(group, open);
|
||||
});
|
||||
|
||||
root.appendChild(head);
|
||||
root.appendChild(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
// Welche Karte eine Zeile ergibt, entscheidet der erste verwertbare Link
|
||||
@@ -516,8 +610,14 @@
|
||||
function rowKind(a, tr) {
|
||||
if (!txt(a) || a.classList.contains('breadcrumbs') ||
|
||||
a.closest('p.breadcrumbs')) return '';
|
||||
if (/viewtopic\.php/.test(a.getAttribute('href') || ''))
|
||||
return tr.querySelector('a.forumlink') ? 'forum' : 'thema';
|
||||
if (/viewtopic\.php/.test(a.getAttribute('href') || '')) {
|
||||
if (tr.querySelector('a.forumlink')) return 'forum';
|
||||
// Der Menuebalken der Seite verlinkt "Forum rules" und die FAQ ebenfalls
|
||||
// auf Themen. Die Bremse fuer Layoutzeilen weiter unten greift dort
|
||||
// nicht, weil die Zeile weder Tabelle noch Script enthaelt — es fehlen
|
||||
// ihr aber die Merkmale einer Themenzeile.
|
||||
return tr.querySelector('a.topictitle') || numCells(tr) >= 2 ? 'thema' : '';
|
||||
}
|
||||
// Das Seitengeruest besteht bei phpBB selbst aus Tabellen. Enthaelt eine
|
||||
// Zeile eine weitere Tabelle oder ein Script, ist sie Layout und keine
|
||||
// Forenzeile — ohne diese Bremse wird die halbe Seite zu einer Karte.
|
||||
@@ -552,8 +652,14 @@
|
||||
if (!tr || tr.dataset.rm || !tr.closest('table')) return;
|
||||
var kind = rowKind(a, tr);
|
||||
if (!kind) return;
|
||||
var group = kind === 'sektion' ? ''
|
||||
: kind === 'forum' ? 'forum'
|
||||
: topicGroup(tr);
|
||||
// Vor dem Setzen der Marke: sepLabel liest sie an den Zeilen darueber,
|
||||
// um an einer bereits verwerteten Zeile stehen zu bleiben.
|
||||
var label = sepLabel(tr) || GNAME[group] || GNAME.thema;
|
||||
tr.dataset.rm = '1';
|
||||
rows.push({ tr: tr, a: a, kind: kind });
|
||||
rows.push({ tr: tr, a: a, kind: kind, group: group, label: label });
|
||||
});
|
||||
log.push('zeilen=' + rows.length);
|
||||
if (!rows.length) return;
|
||||
@@ -570,24 +676,30 @@
|
||||
var c = r.kind === 'thema' ? convertTopic(r.tr)
|
||||
: r.kind === 'forum' ? convertForum(r.tr)
|
||||
: section(r.a);
|
||||
if (c) items.push({ kind: r.kind, el: c });
|
||||
if (c) items.push({ kind: r.kind, group: r.group, label: r.label, el: c });
|
||||
} catch (e) { log.push('convert: ' + e.message); }
|
||||
});
|
||||
|
||||
var built = 0, last = '';
|
||||
var built = 0, last = '', box = null;
|
||||
items.forEach(function (it, i) {
|
||||
// Eine Kategoriezeile leitet die Foren darunter bereits ein — auf der
|
||||
// Forenwurzel wuerde ein Gruppenkopf sie nur doppeln.
|
||||
if (it.kind !== 'sektion') {
|
||||
if (it.kind !== last && last !== 'sektion') {
|
||||
var n = 0;
|
||||
while (i + n < items.length && items[i + n].kind === it.kind) n++;
|
||||
root.appendChild(groupHead(it.kind, n));
|
||||
}
|
||||
built++;
|
||||
if (it.kind === 'sektion') {
|
||||
root.appendChild(it.el);
|
||||
last = 'sektion';
|
||||
return;
|
||||
}
|
||||
last = it.kind;
|
||||
root.appendChild(it.el);
|
||||
built++;
|
||||
if (it.group !== last) {
|
||||
// Eine Kategoriezeile leitet die Foren darunter bereits ein — auf der
|
||||
// Forenwurzel wuerde ein Gruppenkopf sie nur doppeln.
|
||||
if (last === 'sektion') box = root;
|
||||
else {
|
||||
var n = 0;
|
||||
while (i + n < items.length && items[i + n].group === it.group) n++;
|
||||
box = openGroup(root, it.group, it.label, n);
|
||||
}
|
||||
last = it.group;
|
||||
}
|
||||
box.appendChild(it.el);
|
||||
});
|
||||
log.push('karten=' + built);
|
||||
if (!built) return;
|
||||
@@ -991,7 +1103,7 @@
|
||||
if (DEBUG) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'rm-dbg';
|
||||
d.textContent = 'RIN Mobile v3.3\n' + log.join('\n');
|
||||
d.textContent = 'RIN Mobile v3.4\n' + log.join('\n');
|
||||
document.body.insertBefore(d, document.body.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user