mirror of
https://github.com/Andre-Bzk/rin-mobile-userscript.git
synced 2026-11-15 09:33:08 +01:00
fix: recognise forum rows in the list view
A viewforum page is not always a list of topics — a category page lists forums, and a forum page can carry both. Every row was treated as a topic, so on those pages the only link the row offered was the icon in its last-post column: the forum name was dropped, its description ended up in the muted footer line, and nothing was left to tap. Moving between forums on a phone was impossible. Rows are now classified by their first usable link, which relies on document order: a forum row names the forum before it links the last post. Detection prefers phpBB's own classes and falls back to the shape of the row, because the board runs a custom theme whose markup could not be verified — the page requires a login. That fallback needs a brake. The page scaffolding is itself a table, so a link outside any list row resolves to a row containing the whole document, which passed for a forum row and turned the page header, a stylesheet and inline scripts into a forum description. Rows carrying nested tables, forms or scripts are therefore rejected, and descriptions are read only from the title cell's own text.
This commit is contained in:
+219
-63
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name RIN Mobile
|
||||
// @description Mobile Ansicht fuer cs.rin.ru: Themenlisten als Karten, Threads als Beitragsstrom
|
||||
// @version 3.1
|
||||
// @version 3.2
|
||||
// @match *://cs.rin.ru/forum/*
|
||||
// @run-at document-start
|
||||
// ==/UserScript==
|
||||
@@ -86,7 +86,17 @@
|
||||
body.rm-crumbed .rm-dbg {
|
||||
margin-top:calc(45px + env(safe-area-inset-top)); }
|
||||
|
||||
/* === Themenliste === */
|
||||
/* === Themen- und Forenliste === */
|
||||
|
||||
.rm-sec { padding:18px 14px 6px; font-size:12px; font-weight:700;
|
||||
text-transform:uppercase; letter-spacing:.06em; opacity:.55; }
|
||||
.rm-sec a { text-decoration:none !important; color:inherit !important; }
|
||||
.rm-desc { margin-top:6px; font-size:14px; opacity:.72;
|
||||
overflow-wrap:break-word; }
|
||||
.rm-subs { margin-top:9px; display:flex; flex-wrap:wrap; gap:6px; }
|
||||
.rm-subs a { font-size:12px; padding:4px 9px; border-radius:99px;
|
||||
border:1px solid rgba(128,128,128,.4);
|
||||
text-decoration:none !important; color:inherit !important; }
|
||||
|
||||
.rm-card { box-sizing:border-box !important;
|
||||
display:block !important;
|
||||
@@ -284,14 +294,67 @@
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Themenliste
|
||||
// Themenliste und Forenliste
|
||||
// ==========================================================================
|
||||
|
||||
function breakable(s) {
|
||||
return s.replace(/([|\/\\,;:>\]\)])/g, '$1\u200B');
|
||||
}
|
||||
|
||||
function convert(tr) {
|
||||
// Karten teilen sich Titel, Zahlenzeile und Fusszeile; nur die Quellen
|
||||
// dafuer unterscheiden sich zwischen Thema und Forum.
|
||||
function card(titleA, badgeable) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'rm-card';
|
||||
|
||||
var main = document.createElement('a');
|
||||
main.className = 'rm-main';
|
||||
main.href = titleA.href;
|
||||
|
||||
var raw = txt(titleA);
|
||||
var badge = badgeable ? (raw.match(/^\[([^\]]{1,20})\]/) || [])[1] : null;
|
||||
if (badge) {
|
||||
var b = document.createElement('span');
|
||||
b.className = 'rm-badge';
|
||||
b.textContent = badge;
|
||||
main.appendChild(b);
|
||||
raw = raw.replace(/^\[[^\]]{1,20}\]\s*/, '');
|
||||
}
|
||||
main.appendChild(document.createTextNode(breakable(raw)));
|
||||
el.appendChild(main);
|
||||
return el;
|
||||
}
|
||||
|
||||
function metaRow(parts) {
|
||||
var meta = document.createElement('div');
|
||||
meta.className = 'rm-meta';
|
||||
parts.forEach(function (s) {
|
||||
if (!s) return;
|
||||
var e = document.createElement('span');
|
||||
e.textContent = s;
|
||||
meta.appendChild(e);
|
||||
});
|
||||
return meta.children.length ? meta : null;
|
||||
}
|
||||
|
||||
function footRow(last, jump, label) {
|
||||
var foot = document.createElement('div');
|
||||
foot.className = 'rm-foot';
|
||||
var l = document.createElement('div');
|
||||
l.className = 'rm-last';
|
||||
l.textContent = last;
|
||||
foot.appendChild(l);
|
||||
if (jump) {
|
||||
var j = document.createElement('a');
|
||||
j.className = 'rm-jump';
|
||||
j.href = jump.href;
|
||||
j.textContent = label;
|
||||
foot.appendChild(j);
|
||||
}
|
||||
return foot;
|
||||
}
|
||||
|
||||
function convertTopic(tr) {
|
||||
var titleA = tr.querySelector('a.topictitle') ||
|
||||
tr.querySelector('a[href*="viewtopic.php"]');
|
||||
if (!titleA) return null;
|
||||
@@ -312,80 +375,173 @@
|
||||
}
|
||||
});
|
||||
|
||||
var card = document.createElement('div');
|
||||
card.className = 'rm-card';
|
||||
var el = card(titleA, true);
|
||||
var meta = metaRow([author,
|
||||
nums[0] ? nums[0] + ' Antworten' : '',
|
||||
nums[1] ? nums[1] + ' Views' : '']);
|
||||
if (meta) el.appendChild(meta);
|
||||
el.appendChild(footRow(last, jump, 'Neueste ↓'));
|
||||
return el;
|
||||
}
|
||||
|
||||
var main = document.createElement('a');
|
||||
main.className = 'rm-main';
|
||||
main.href = titleA.href;
|
||||
// Forenzeilen tragen ihren Namen in a.forumlink, die Beschreibung und die
|
||||
// Unterforen stehen als weitere Absaetze in derselben Zelle.
|
||||
function convertForum(tr) {
|
||||
var titleA = forumTitle(tr);
|
||||
if (!titleA) return null;
|
||||
|
||||
var raw = txt(titleA);
|
||||
var badge = (raw.match(/^\[([^\]]{1,20})\]/) || [])[1];
|
||||
if (badge) {
|
||||
var b = document.createElement('span');
|
||||
b.className = 'rm-badge';
|
||||
b.textContent = badge;
|
||||
main.appendChild(b);
|
||||
raw = raw.replace(/^\[[^\]]{1,20}\]\s*/, '');
|
||||
var titleCell = titleA.closest('td');
|
||||
var nums = [], last = '', jump = null;
|
||||
|
||||
Array.prototype.forEach.call(tr.children, function (td) {
|
||||
if (td === titleCell) return;
|
||||
var t = txt(td);
|
||||
if (/^\d[\d.,]*$/.test(t)) { nums.push(t); return; }
|
||||
if (t) {
|
||||
last = t;
|
||||
jump = td.querySelector('a[href*="viewtopic.php"]');
|
||||
}
|
||||
});
|
||||
|
||||
var el = card(titleA, false);
|
||||
|
||||
// Beschreibung, Moderatoren und Unterforen benutzen denselben Absatztyp.
|
||||
// Die beiden letzten sind nur an ihrer Einleitung zu erkennen.
|
||||
var desc = '';
|
||||
if (titleCell) {
|
||||
titleCell.querySelectorAll('p.forumdesc, span.forumdesc')
|
||||
.forEach(function (p) {
|
||||
var t = txt(p);
|
||||
if (desc || !t || /^(Moderator|Subforum|Unterforum)/i.test(t)) return;
|
||||
desc = t;
|
||||
});
|
||||
}
|
||||
main.appendChild(document.createTextNode(breakable(raw)));
|
||||
card.appendChild(main);
|
||||
|
||||
var meta = document.createElement('div');
|
||||
meta.className = 'rm-meta';
|
||||
function add(s) {
|
||||
var e = document.createElement('span');
|
||||
e.textContent = s;
|
||||
meta.appendChild(e);
|
||||
// Ohne die Klasse steht die Beschreibung als blosser Text neben dem Namen.
|
||||
// Nur direkte Textknoten der Zelle: der Text der ganzen Zelle einzusammeln
|
||||
// holt bei einer verkannten Zeile den halben Seiteninhalt mit.
|
||||
if (!desc && titleCell) {
|
||||
var loose = '';
|
||||
Array.prototype.forEach.call(titleCell.childNodes, function (n) {
|
||||
if (n.nodeType === 3) loose += ' ' + n.nodeValue;
|
||||
});
|
||||
desc = loose.replace(/\s+/g, ' ').trim()
|
||||
.split(/Moderators?:|Subforums?:|Unterforen?:/)[0].trim();
|
||||
if (desc.length > 300) desc = '';
|
||||
}
|
||||
if (author) add(author);
|
||||
if (nums[0]) add(nums[0] + ' Antworten');
|
||||
if (nums[1]) add(nums[1] + ' Views');
|
||||
if (meta.children.length) card.appendChild(meta);
|
||||
|
||||
var foot = document.createElement('div');
|
||||
foot.className = 'rm-foot';
|
||||
var l = document.createElement('div');
|
||||
l.className = 'rm-last';
|
||||
l.textContent = last;
|
||||
foot.appendChild(l);
|
||||
if (jump) {
|
||||
var j = document.createElement('a');
|
||||
j.className = 'rm-jump';
|
||||
j.href = jump.href;
|
||||
j.textContent = 'Neueste ↓';
|
||||
foot.appendChild(j);
|
||||
if (desc) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'rm-desc';
|
||||
d.textContent = desc;
|
||||
el.appendChild(d);
|
||||
}
|
||||
card.appendChild(foot);
|
||||
|
||||
return card;
|
||||
// Unterforen sind sonst nur ueber den Umweg des Elternforums erreichbar.
|
||||
var subs = document.createElement('div');
|
||||
subs.className = 'rm-subs';
|
||||
if (titleCell) {
|
||||
titleCell.querySelectorAll('a[href*="viewforum.php"]')
|
||||
.forEach(function (a) {
|
||||
if (a === titleA || !txt(a)) return;
|
||||
var s = document.createElement('a');
|
||||
s.href = a.href;
|
||||
s.textContent = txt(a);
|
||||
subs.appendChild(s);
|
||||
});
|
||||
}
|
||||
if (subs.children.length) el.appendChild(subs);
|
||||
|
||||
var meta = metaRow([nums[0] ? nums[0] + ' Themen' : '',
|
||||
nums[1] ? nums[1] + ' Beiträge' : '']);
|
||||
if (meta) el.appendChild(meta);
|
||||
if (last || jump) el.appendChild(footRow(last, jump, 'Letzter ↓'));
|
||||
return el;
|
||||
}
|
||||
|
||||
function forumTitle(tr) {
|
||||
var a = tr.querySelector('a.forumlink');
|
||||
if (a) return a;
|
||||
// Ohne die Klasse bleibt der erste Link auf ein Forum, der Text traegt und
|
||||
// kein Unterforum ist.
|
||||
var found = null;
|
||||
tr.querySelectorAll('a[href*="viewforum.php"]').forEach(function (x) {
|
||||
if (found || !txt(x) || x.classList.contains('subforum') ||
|
||||
x.classList.contains('breadcrumbs') || x.closest('p.breadcrumbs')) return;
|
||||
found = x;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
function section(a) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'rm-sec';
|
||||
var l = document.createElement('a');
|
||||
l.href = a.href;
|
||||
l.textContent = txt(a);
|
||||
el.appendChild(l);
|
||||
return el;
|
||||
}
|
||||
|
||||
// Welche Karte eine Zeile ergibt, entscheidet der erste verwertbare Link
|
||||
// darin. Beiwerk wie die Brotkrumen oder "Mark topics read" verlinkt
|
||||
// ebenfalls auf viewforum.php, steht aber in keiner Zeile, die eine Karte
|
||||
// hergibt — deshalb muss eine Forenzeile als solche erkennbar sein.
|
||||
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';
|
||||
// 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.
|
||||
if (tr.querySelector('table, script, style, form')) return '';
|
||||
if (a.closest('td.cat') || a.closest('h4')) return 'sektion';
|
||||
if (a.classList.contains('forumlink')) return 'forum';
|
||||
if (a.classList.contains('subforum')) return '';
|
||||
// Fehlen die phpBB-Klassen, bleibt die Form der Zeile: zwei rein
|
||||
// numerische Spalten hat nur eine Forenzeile mit Themen- und Beitragszahl.
|
||||
return tr.querySelector('.forumdesc') || numCells(tr) >= 2 ? 'forum' : '';
|
||||
}
|
||||
|
||||
function numCells(tr) {
|
||||
var n = 0;
|
||||
Array.prototype.forEach.call(tr.children, function (td) {
|
||||
if (/^\d[\d.,]*$/.test(txt(td))) n++;
|
||||
});
|
||||
return n;
|
||||
}
|
||||
|
||||
function buildList() {
|
||||
// Dokumentreihenfolge entscheidet: in einer Forenzeile steht der Forenname
|
||||
// vor dem Link auf den letzten Beitrag. Ohne diese Reihenfolge faellt die
|
||||
// Zeile als Thema an, der Forenname verschwindet und nur die Beschreibung
|
||||
// bleibt uebrig.
|
||||
var links = document.querySelectorAll(
|
||||
'a.topictitle,a[href*="viewtopic.php"]');
|
||||
log.push('themenlinks=' + links.length);
|
||||
if (!links.length) return;
|
||||
'a.forumlink,a.topictitle,a[href*="viewforum.php"],a[href*="viewtopic.php"]');
|
||||
|
||||
var rows = [];
|
||||
links.forEach(function (a) {
|
||||
var tr = a.closest('tr');
|
||||
if (!tr || tr.dataset.rm || !tr.closest('table')) return;
|
||||
var kind = rowKind(a, tr);
|
||||
if (!kind) return;
|
||||
tr.dataset.rm = '1';
|
||||
rows.push({ tr: tr, a: a, kind: kind });
|
||||
});
|
||||
log.push('zeilen=' + rows.length);
|
||||
if (!rows.length) return;
|
||||
|
||||
var root = document.createElement('div');
|
||||
root.className = 'rm-root';
|
||||
|
||||
var rows = [], tables = new Set();
|
||||
links.forEach(function (a) {
|
||||
var tr = a.closest('tr');
|
||||
if (!tr || tr.dataset.rm) return;
|
||||
var tbl = tr.closest('table');
|
||||
if (!tbl) return;
|
||||
tr.dataset.rm = '1';
|
||||
rows.push(tr);
|
||||
tables.add(tbl);
|
||||
});
|
||||
|
||||
var built = 0;
|
||||
rows.forEach(function (tr) {
|
||||
rows.forEach(function (r) {
|
||||
try {
|
||||
var c = convert(tr);
|
||||
if (c) { root.appendChild(c); built++; }
|
||||
var c = r.kind === 'thema' ? convertTopic(r.tr)
|
||||
: r.kind === 'forum' ? convertForum(r.tr)
|
||||
: section(r.a);
|
||||
if (!c) return;
|
||||
root.appendChild(c);
|
||||
if (r.kind !== 'sektion') built++;
|
||||
} catch (e) { log.push('convert: ' + e.message); }
|
||||
});
|
||||
log.push('karten=' + built);
|
||||
@@ -790,7 +946,7 @@
|
||||
if (DEBUG) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'rm-dbg';
|
||||
d.textContent = 'RIN Mobile v3.1\n' + log.join('\n');
|
||||
d.textContent = 'RIN Mobile v3.2\n' + log.join('\n');
|
||||
document.body.insertBefore(d, document.body.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user