Sort posts by staff
unknown
plain_text
2 days ago
12 kB
7
No Index
// ==UserScript==
// @name On3 Staff Posts Filter
// @namespace https://www.on3.com
// @version 1.4.0
// @description Toggle between all posts and staff-only posts on On3 threads
// @match https://www.on3.com/boards/threads/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
console.log('[On3 StaffFilter] Script loaded');
// Only run on thread pages like /boards/threads/slug.1234567/
if (!/\/boards\/threads\/[^.]+\.\d+/.test(location.pathname)) {
console.log('[On3 StaffFilter] Not a thread page');
return;
}
console.log('[On3 StaffFilter] Thread page detected, initializing...');
// Inject CSS
const css = `
#xfStaffOnlyOff:checked ~ .block.block--messages .message {
display: block !important;
}
#xfStaffOnlyOn:checked ~ .block.block--messages .message:not(:has(.userBanner--staff)) {
display: none !important;
}
#xfStaffOnlyOff:checked ~ .block.block--messages .buttonGroup #btnAllPosts { font-weight: 600; text-decoration: underline; }
#xfStaffOnlyOff:checked ~ .block.block--messages .buttonGroup #btnStaffOnly { opacity: .6; }
#xfStaffOnlyOn:checked ~ .block.block--messages .buttonGroup #btnStaffOnly { font-weight: 600; text-decoration: underline; }
#xfStaffOnlyOn:checked ~ .block.block--messages .buttonGroup #btnAllPosts { opacity: .6; }
`;
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function injectControls() {
const messagesBlock = document.querySelector('.block.block--messages');
if (!messagesBlock) {
console.log('[On3 StaffFilter] Waiting for .block.block--messages...');
return false;
}
// Don't double-inject
if (document.getElementById('xfStaffOnlyOn') || document.getElementById('xfStaffOnlyOff')) {
console.log('[On3 StaffFilter] Controls already exist');
return true;
}
console.log('[On3 StaffFilter] Injecting controls...');
const blockOuter = messagesBlock.querySelector('.block-outer');
if (!blockOuter) return false;
let outerOpp = blockOuter.querySelector('.block-outer-opposite');
if (!outerOpp) {
outerOpp = document.createElement('div');
outerOpp.className = 'block-outer-opposite';
blockOuter.appendChild(outerOpp);
}
let buttonGroup = outerOpp.querySelector('.buttonGroup');
if (!buttonGroup) {
buttonGroup = document.createElement('div');
buttonGroup.className = 'buttonGroup';
outerOpp.appendChild(buttonGroup);
}
const staffLabel = document.createElement('label');
staffLabel.id = 'btnStaffOnly';
staffLabel.className = 'button xf-staffOnlyBtn';
staffLabel.htmlFor = 'xfStaffOnlyOn';
staffLabel.textContent = 'Staff posts';
const allLabel = document.createElement('label');
allLabel.id = 'btnAllPosts';
allLabel.className = 'button';
allLabel.htmlFor = 'xfStaffOnlyOff';
allLabel.textContent = 'All posts';
buttonGroup.appendChild(staffLabel);
buttonGroup.appendChild(allLabel);
// Create radios as direct siblings of messagesBlock (required for CSS ~ selector)
const offRadio = document.createElement('input');
offRadio.type = 'radio';
offRadio.name = 'xfStaffOnlyMode';
offRadio.id = 'xfStaffOnlyOff';
offRadio.className = 'u-hidden';
offRadio.checked = true;
offRadio.style.display = 'none';
const onRadio = document.createElement('input');
onRadio.type = 'radio';
onRadio.name = 'xfStaffOnlyMode';
onRadio.id = 'xfStaffOnlyOn';
onRadio.className = 'u-hidden';
onRadio.style.display = 'none';
// Insert radios as direct siblings before messagesBlock
messagesBlock.parentNode.insertBefore(offRadio, messagesBlock);
messagesBlock.parentNode.insertBefore(onRadio, messagesBlock);
console.log('[On3 StaffFilter] ✓ Controls injected successfully!');
return true;
}
function setupBehavior() {
function parseTidFromCurrent() {
const m = location.pathname.match(/\/boards\/threads\/[^.]+\.(\d+)(?:\/|$)/i);
return m ? m[1] : null;
}
function parseTid(href) {
try {
const u = new URL(href, location.href);
const m = u.pathname.match(/\/boards\/threads\/[^.]+\.(\d+)(?:\/|$)/i);
return m ? m[1] : null;
} catch { return null; }
}
const tid = parseTidFromCurrent();
if (!tid) return;
const KEY = 'xfStaffOnly:' + tid; // per-thread stored preference
const HINT = '__xfWithinThread:' + tid; // marks pagination within this thread
const on = () => document.getElementById('xfStaffOnlyOn');
const off = () => document.getElementById('xfStaffOnlyOff');
function supportsHas() {
try { return CSS.supports('selector(:has(*))'); } catch { return false; }
}
function isOn() {
const r = on();
return !!(r && r.checked);
}
function applyFallback() {
if (supportsHas()) return;
const hide = isOn();
document.querySelectorAll('.block--messages .message').forEach(m => {
const staff = !!m.querySelector('.userBanner--staff');
m.style.display = (hide && !staff) ? 'none' : '';
});
}
function cameFromSameThread() {
const ref = document.referrer;
return ref && parseTid(ref) === String(tid);
}
function resetToAll() {
try { sessionStorage.removeItem(KEY); } catch {}
const onEl = on();
const offEl = off();
if (offEl) offEl.checked = true;
if (onEl) onEl.checked = false;
// Clear inline fallback styles
document.querySelectorAll('.block--messages .message[style]').forEach(m => {
m.style.display = '';
});
}
function applyFromStorage() {
let prefOn = false;
try { prefOn = sessionStorage.getItem(KEY) === '1'; } catch {}
const onEl = on();
const offEl = off();
if (onEl && offEl) {
onEl.checked = prefOn;
offEl.checked = !prefOn;
}
applyFallback();
}
function save() {
try { sessionStorage.setItem(KEY, isOn() ? '1' : '0'); } catch {}
applyFallback();
}
function entry() {
let within = false;
try { within = sessionStorage.getItem(HINT) === '1'; } catch {}
// If we didn't come from this same thread (i.e., not pagination), default to All posts
if (!cameFromSameThread() && !within) {
resetToAll();
}
try { sessionStorage.removeItem(HINT); } catch {}
applyFromStorage(); // apply stored pref if any (or Off if we just reset)
}
// Set up event handlers
function attachEvents() {
const onRadio = document.getElementById('xfStaffOnlyOn');
const offRadio = document.getElementById('xfStaffOnlyOff');
const staffBtn = document.getElementById('btnStaffOnly');
const allBtn = document.getElementById('btnAllPosts');
if (!onRadio || !offRadio) {
console.warn('[On3 StaffFilter] Radios not found');
return;
}
// Change listeners on radios
onRadio.addEventListener('change', () => {
console.log('[On3 StaffFilter] Staff only selected');
save();
});
offRadio.addEventListener('change', () => {
console.log('[On3 StaffFilter] All posts selected');
save();
});
// Click handlers on labels (as backup, though htmlFor should handle it)
if (staffBtn) {
staffBtn.addEventListener('click', () => {
console.log('[On3 StaffFilter] Staff button clicked');
setTimeout(save, 0);
});
}
if (allBtn) {
allBtn.addEventListener('click', () => {
console.log('[On3 StaffFilter] All button clicked');
setTimeout(save, 0);
});
}
}
// Mark intra-thread pagination vs leaving the thread
document.addEventListener('click', function (e) {
const a = e.target && e.target.closest && e.target.closest('a[href]');
if (!a) return;
// ignore new-tab/modified or in-page links
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
if (a.target && a.target !== '' && a.target !== '_self') return;
const href = a.getAttribute('href');
if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
const destTid = parseTid(href);
if (destTid && String(destTid) === String(tid)) {
// pagination inside this thread
try { sessionStorage.setItem(HINT, '1'); } catch {}
} else {
// leaving the thread -> clear so returning defaults to All posts
resetToAll();
try { sessionStorage.removeItem(HINT); } catch {}
}
}, true);
// Set up event handlers
attachEvents();
// Initial entry - check if we came from same thread or should reset
entry();
// Also handle XenForo's page-load event (AJAX navigation)
document.addEventListener('xf:page-load', entry);
// Handle bfcache/page restoration
window.addEventListener('pageshow', function (ev) {
let within = false;
try { within = sessionStorage.getItem(HINT) === '1'; } catch {}
if (ev.persisted && !within) resetToAll();
try { sessionStorage.removeItem(HINT); } catch {}
applyFromStorage();
});
console.log('[On3 StaffFilter] Behavior setup complete');
}
// Try injection with retries
function tryInject() {
if (injectControls()) {
setupBehavior();
return true;
}
return false;
}
// Try immediately
if (tryInject()) {
console.log('[On3 StaffFilter] ✓ Injected immediately');
return;
}
// Listen for XenForo's page-load event (fires on AJAX navigation)
document.addEventListener('xf:page-load', () => {
console.log('[On3 StaffFilter] xf:page-load event fired');
setTimeout(tryInject, 200);
});
// Retry on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
console.log('[On3 StaffFilter] DOMContentLoaded fired');
setTimeout(tryInject, 200);
});
} else {
setTimeout(tryInject, 300);
}
// Also try on window load
window.addEventListener('load', () => {
console.log('[On3 StaffFilter] Window load fired');
if (!document.getElementById('xfStaffOnlyOn')) {
setTimeout(tryInject, 200);
}
});
// Fallback: MutationObserver (watches for DOM changes)
const observer = new MutationObserver(() => {
if (!document.getElementById('xfStaffOnlyOn')) {
if (tryInject()) {
console.log('[On3 StaffFilter] ✓ Injected via MutationObserver');
observer.disconnect();
}
}
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
console.log('[On3 StaffFilter] MutationObserver started');
} else {
const bodyObserver = new MutationObserver(() => {
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
console.log('[On3 StaffFilter] MutationObserver started (after body created)');
bodyObserver.disconnect();
}
});
bodyObserver.observe(document.documentElement, { childList: true });
}
// Cleanup observer after 15s
setTimeout(() => {
observer.disconnect();
if (!document.getElementById('xfStaffOnlyOn')) {
console.warn('[On3 StaffFilter] ⚠ Failed to inject after 15s timeout');
}
}, 15000);
})();
Editor is loading...
Leave a Comment