• An addendum to Rule 3 regarding fan-translated works of things such as Web Novels has been made. Please see here for details.
  • We've issued a clarification on our policy on AI-generated work.
  • Our mod selection process has completed. Please welcome our new moderators.
  • Due to issues with external spam filters, QQ is currently unable to send any mail to Microsoft E-mail addresses. This includes any account at live.com, hotmail.com or msn.com. Signing up to the forum with one of these addresses will result in your verification E-mail never arriving. For best results, please use a different E-mail provider for your QQ address.
  • For prospective new members, a word of warning: don't use common names like Dennis, Simon, or Kenny if you decide to create an account. Spammers have used them all before you and gotten those names flagged in the anti-spam databases. Your account registration will be rejected because of it.
  • Since it has happened MULTIPLE times now, I want to be very clear about this. You do not get to abandon an account and create a new one. You do not get to pass an account to someone else and create a new one. If you do so anyway, you will be banned for creating sockpuppets.
  • Due to the actions of particularly persistent spammers and trolls, we will be banning disposable email addresses from today onward.
  • The rules regarding NSFW links have been updated. See here for details.

A convenient toggleable reader mode for the forums

tronax

Not too sore, are you?
Joined
Nov 16, 2019
Messages
326
Likes received
2,157
It is entirely possible to use a browser extension or a user script to style everything in whatever way a particular user desires. However, it takes time and effort to set up, so most don't and won't. Which means they don't get the best possible experience. All the while, it would be so easy to enhance it. Just a single button added, a bit of messing with styles, and that's all it'd take. Please, consider that.

To show a practical example of how it could be implemented, I'll post the script I'm using for my own "Reader Mode". To test it, you can either paste&enter it into the browser console (Ctrl+Shift+J in Chrome, which will only work on the specific page where you did that) or install through Greasemonkey/Violentmonkey browser add-on to make it persistent. It adds a button to the right of "Search" at the top panel. Click it once - it'll convert the page. Click it again - it'll revert everything (in reader mode, the top panel is unsticked, so to disable it you'll have to scroll to the top). If used as a user script, it will remember the current on/off state between pages.

JavaScript:
// ==UserScript==
// @name        QQ_ReaderMode
// @namespace   tronax
// @match       https://forum.questionablequesting.com/threads/*
// @grant       none
// @version     1.0
// @author      tronax
// @description Reader mode for QQ
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. DEFINE THE STYLES ---
    // The first two rules are to preserve existing values in case of future design alterations, rather than to change anything
    const readableCss = `
        body.readable-mode-active .p-body {
            background-color: #f1f3f6!important;
        }
        body.readable-mode-active article.message {
            border: 1px solid #c0c0c0 !important;
            border-radius: 4px !important;
            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
        }

        body.readable-mode-active .p-body-inner {
            max-width: 860px !important;
        }
        body.readable-mode-active .message-userContent > article > div:nth-child(1) > div {
            width: 680px !important;
            margin: auto !important;
            text-align: justify !important;
            line-height: 1.5 !important;
            font-family: Arial !important;
            font-size: 28px !important;
        }
        body.readable-mode-active .message-inner > .message-cell--user {
            display: none !important;
        }
        body.readable-mode-active .message-inner > .message-cell--main {
            background-color: white !important;
            color: #222 !important;
        }
        body.readable-mode-active .message-body span[style*="font-size"] {
            font-size: inherit !important;
            color: inherit !important;
        }
        body.readable-mode-active .p-navSticky.is-sticky {
            position: relative !important;
        }
    `;

    // --- 2. INJECT THE STYLESHEET INTO THE PAGE ---
    const styleElement = document.createElement('style');
    styleElement.id = 'readable-mode-styles';
    styleElement.textContent = readableCss;
    document.head.appendChild(styleElement);

    // --- 3. LOCATE THE NAV BAR AND PREPARE IT FOR POSITIONING ---
    const navBar = document.querySelector('nav.p-nav');
    if (!navBar) {
        console.log('Readable Mode Script: Could not find the <nav class="p-nav"> element.');
        return;
    }

    // Set the nav bar's position to 'relative'.
    // This is essential for placing a child element absolutely within it.
    navBar.style.position = 'relative';

    // --- 4. CREATE THE TOGGLE BUTTON ---
    const readableButton = document.createElement('button');
    readableButton.id = 'readable-mode-toggle';
    readableButton.title = 'Toggle Readable Mode';

    // Button styles for absolute positioning.
    readableButton.style.cssText = `
        position: absolute;
        top: 50%;
        right: 16px;
        transform: translateY(-50%);
        background: transparent;
        border: none;
        color: white;
        cursor: pointer;
        padding: 5px;
        border-radius: 5px;
        line-height: 0;
    `;

    // --- 5. CREATE THE SVG ICON ---
    const bookIconSvg = `
        <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24" fill="currentColor">
            <path d="M0 0h24v24H0z" fill="none"/>
            <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
        </svg>
    `;
    readableButton.innerHTML = bookIconSvg;


    // --- 6. APPEND THE BUTTON DIRECTLY TO THE NAV BAR ---
    navBar.appendChild(readableButton);


    // --- 7. ADD CLICK EVENT AND PERSISTENCE ---
    const toggleFunction = () => {
        const isActive = document.body.classList.toggle('readable-mode-active');
        localStorage.setItem('readableMode', isActive ? 'enabled' : 'disabled');
    };

    readableButton.addEventListener('click', toggleFunction);

    // --- 8. APPLY ON PAGE LOAD IF IT WAS PREVIOUSLY ENABLED ---
    if (localStorage.getItem('readableMode') === 'enabled') {
        document.body.classList.add('readable-mode-active');
    }

})();
 
It is entirely possible to use a browser extension or a user script to style everything in whatever way a particular user desires. However, it takes time and effort to set up, so most don't and won't. Which means they don't get the best possible experience. All the while, it would be so easy to enhance it. Just a single button added, a bit of messing with styles, and that's all it'd take. Please, consider that.

To show a practical example of how it could be implemented, I'll post the script I'm using for my own "Reader Mode". To test it, you can either paste&enter it into the browser console (Ctrl+Shift+J in Chrome, which will only work on the specific page where you did that) or install through Greasemonkey/Violentmonkey browser add-on to make it persistent. It adds a button to the right of "Search" at the top panel. Click it once - it'll convert the page. Click it again - it'll revert everything (in reader mode, the top panel is unsticked, so to disable it you'll have to scroll to the top). If used as a user script, it will remember the current on/off state between pages.

JavaScript:
// ==UserScript==
// @name        QQ_ReaderMode
// @namespace   tronax
// @match       https://forum.questionablequesting.com/threads/*
// @grant       none
// @version     1.0
// @author      tronax
// @description Reader mode for QQ
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. DEFINE THE STYLES ---
    // The first two rules are to preserve existing values in case of future design alterations, rather than to change anything
    const readableCss = `
        body.readable-mode-active .p-body {
            background-color: #f1f3f6!important;
        }
        body.readable-mode-active article.message {
            border: 1px solid #c0c0c0 !important;
            border-radius: 4px !important;
            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
        }

        body.readable-mode-active .p-body-inner {
            max-width: 860px !important;
        }
        body.readable-mode-active .message-userContent > article > div:nth-child(1) > div {
            width: 680px !important;
            margin: auto !important;
            text-align: justify !important;
            line-height: 1.5 !important;
            font-family: Arial !important;
            font-size: 28px !important;
        }
        body.readable-mode-active .message-inner > .message-cell--user {
            display: none !important;
        }
        body.readable-mode-active .message-inner > .message-cell--main {
            background-color: white !important;
            color: #222 !important;
        }
        body.readable-mode-active .message-body span[style*="font-size"] {
            font-size: inherit !important;
            color: inherit !important;
        }
        body.readable-mode-active .p-navSticky.is-sticky {
            position: relative !important;
        }
    `;

    // --- 2. INJECT THE STYLESHEET INTO THE PAGE ---
    const styleElement = document.createElement('style');
    styleElement.id = 'readable-mode-styles';
    styleElement.textContent = readableCss;
    document.head.appendChild(styleElement);

    // --- 3. LOCATE THE NAV BAR AND PREPARE IT FOR POSITIONING ---
    const navBar = document.querySelector('nav.p-nav');
    if (!navBar) {
        console.log('Readable Mode Script: Could not find the <nav class="p-nav"> element.');
        return;
    }

    // Set the nav bar's position to 'relative'.
    // This is essential for placing a child element absolutely within it.
    navBar.style.position = 'relative';

    // --- 4. CREATE THE TOGGLE BUTTON ---
    const readableButton = document.createElement('button');
    readableButton.id = 'readable-mode-toggle';
    readableButton.title = 'Toggle Readable Mode';

    // Button styles for absolute positioning.
    readableButton.style.cssText = `
        position: absolute;
        top: 50%;
        right: 16px;
        transform: translateY(-50%);
        background: transparent;
        border: none;
        color: white;
        cursor: pointer;
        padding: 5px;
        border-radius: 5px;
        line-height: 0;
    `;

    // --- 5. CREATE THE SVG ICON ---
    const bookIconSvg = `
        <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24" fill="currentColor">
            <path d="M0 0h24v24H0z" fill="none"/>
            <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
        </svg>
    `;
    readableButton.innerHTML = bookIconSvg;


    // --- 6. APPEND THE BUTTON DIRECTLY TO THE NAV BAR ---
    navBar.appendChild(readableButton);


    // --- 7. ADD CLICK EVENT AND PERSISTENCE ---
    const toggleFunction = () => {
        const isActive = document.body.classList.toggle('readable-mode-active');
        localStorage.setItem('readableMode', isActive ? 'enabled' : 'disabled');
    };

    readableButton.addEventListener('click', toggleFunction);

    // --- 8. APPLY ON PAGE LOAD IF IT WAS PREVIOUSLY ENABLED ---
    if (localStorage.getItem('readableMode') === 'enabled') {
        document.body.classList.add('readable-mode-active');
    }

})();
We already have Reader Mode. The button's at the top of the thread or the bottom of any threadmarked post.
 
We already have Reader Mode. The button's at the top of the thread or the bottom of any threadmarked post.
Yes, I know.

This local "reader mode" is, however, about collecting and stitching together the scattered chapters, it has nothing to do with making the text visually appealing and comfortable to read (which just so happens to be the common meaning for "reader mode").

Either way, I guess there is no demand for that around the place, given the observable reactions, so whatever.
 
Hey @tronax I just used it and I really like it. Thanks for the script.

Now the next step for me is to figure out how to get the QQ dark mode to persist even after activating this script.
 
Hey @tronax I just used it and I really like it. Thanks for the script.

Now the next step for me is to figure out how to get the QQ dark mode to persist even after activating this script.
That's probably an outdated version. The one I use today (I don't quite remember the exact changes, pretty sure one feature I added is autocorrecting screen positioning when mode change is triggered):
// ==UserScript==
// @Name QQ_ReaderMode
// @namespace tronax
// @match https://forum.questionablequesting.com/threads/*
// @Grant none
// @Version 1.2
// @author tronax
// @description Reader mode for QQ
// ==/UserScript==

(function() {
'use strict';

// --- 1. DEFINE THE STYLES ---
// The first two rules are to preserve existing values in case of future design alterations, rather than to change anything
const readableCss = `
body.readable-mode-active .p-body {
background-color: #f1f3f6!important;
}
body.readable-mode-active article.message {
border: 1px solid #c0c0c0 !important;
border-radius: 4px !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
}

body.readable-mode-active .p-body-inner {
max-width: 860px !important;
}
body.readable-mode-active .message-userContent > article > div:nth-child(1) > div {
width: 680px !important;
margin: auto !important;
text-align: justify !important;
line-height: 1.5 !important;
font-family: Arial !important;
font-size: 28px !important;
}
body.readable-mode-active .message-inner > .message-cell--user {
display: none !important;
}
body.readable-mode-active .message-inner > .message-cell--main {
background-color: white !important;
color: #222 !important;
}
body.readable-mode-active .message-body span[style*="font-size"] {
font-size: inherit !important;
color: inherit !important;
}
body.readable-mode-active .p-navSticky.is-sticky {
position: relative !important;
}
`;

// --- 2. INJECT THE STYLESHEET ---
const styleElement = document.createElement('style');
styleElement.id = 'readable-mode-styles';
styleElement.textContent = readableCss;
document.head.appendChild(styleElement);


// --- 3. CREATE THE TOGGLE BUTTON ---
const readableButton = document.createElement('button');
readableButton.id = 'readable-mode-toggle';
readableButton.title = 'Toggle Readable Mode (Ctrl+Shift+R)';

readableButton.style.cssText = `
position: fixed;
top: 10px;
right: 15px;
z-index: 9999;
background: rgba(0, 0, 0, 0.2);
border-width: 0;
color: white;
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: background-color 0.2s;
line-height: 0;
`;
readableButton.onmouseover = () => readableButton.style.backgroundColor = 'rgba(0, 0, 0, 0.4)';
readableButton.onmouseout = () => readableButton.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';


// --- 4. CREATE THE ICON (SVG) ---
const bookIconSvg = `
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
</svg>
`;
readableButton.innerHTML = bookIconSvg;
document.body.appendChild(readableButton);


// --- 5. BUTTON "UNSTICKING" LOGIC ---
// This function checks the scroll position and adjusts the button's positioning.
const unstickThresholdY = 122;

const updateButtonPosition = () => {
if (window.scrollY < unstickThresholdY) {
// When at the top, position absolutely relative to the page.
readableButton.style.position = 'absolute';
readableButton.style.top = `${unstickThresholdY}px`;
} else {
// When scrolled down, fix it to the viewport.
readableButton.style.position = 'fixed';
readableButton.style.top = '10px';
}
};


// --- 6. THE CORE TOGGLE FUNCTION WITH SCROLL PRESERVATION ---
const toggleReadableMode = () => {
/* ────────────────────────────── constants ─────────────────────────────── */
// Pixels of sticky header hanging on top of the layout in non-reader mode.
// Must be taken into account when adjusting for both ways.
// Going in we aim for pixels below it; Going out, we offset the first visible element by its height.
const HEADER_HEIGHT = 66;
const PIN_ID = 'readerModePin';
const TAP_MARGIN = 2;

/* ─────────────────────── determine current state ──────────────────────── */
const wasReadable = document.body.classList.contains('readable-mode-active');

/* ────────────────────── 1) place an invisible pin ─────────────────────── */
const x = Math.floor(window.innerWidth / 2);
const y = (wasReadable ? 0 : HEADER_HEIGHT) + TAP_MARGIN;

// Obtain a Range that corresponds to (x, y)
let range;
if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (document.caretPositionFromPoint) { // Firefox
const pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}

let pin;
if (range && range.startContainer) {
pin = document.createElement('span');
pin.id = PIN_ID;
pin.style.cssText = `
display:inline-block;
width:0;
height:0;
line-height:0;
opacity:0;
pointer-events:none;`;
range.insertNode(pin);
}

/* ───────────────────── 2) Toggle the mode & persist ───────────────────── */
document.body.classList.toggle('readable-mode-active');
localStorage.setItem('readableMode', !wasReadable ? 'enabled' : 'disabled');

/* ────────────── 3) Wait for layout stability, then scroll ─────────────── */
if (pin && pin.parentNode) {
let lastHeight = -1;
let stableCount = 0;

const checkStabilityAndScroll = () => {
const currentHeight = document.body.scrollHeight;

if (currentHeight === lastHeight) {
stableCount++;
if (stableCount >= 3) { // Layout stable for 3 checks
// Now scroll to the pin
const pinRect = pin.getBoundingClientRect();
const pinTop = pinRect.top + window.scrollY;

const nowReadable = !wasReadable;
const targetOffset = (nowReadable ? 0 : HEADER_HEIGHT) + TAP_MARGIN;
const targetScrollY = pinTop - targetOffset;

window.scrollTo({ top: targetScrollY, behavior: 'instant' });

// Clean up
pin.parentNode.removeChild(pin);
return;
}
} else {
stableCount = 0;
}

lastHeight = currentHeight;
requestAnimationFrame(checkStabilityAndScroll);
};

requestAnimationFrame(checkStabilityAndScroll);
}
};

// --- 7. ADD EVENT LISTENERS ---
// For the button click
readableButton.addEventListener('click', (event) => {
toggleReadableMode();
event.currentTarget.blur();
});

// For the keyboard shortcut
document.addEventListener('keydown', (event) => {
if (event.ctrlKey && event.shiftKey && event.key.toUpperCase() === 'R') {
event.preventDefault();
toggleReadableMode();
}
});

// For the "unsticking" button behavior
document.addEventListener('scroll', updateButtonPosition, { passive: true });


// --- 8. APPLY INITIAL STATE ON PAGE LOAD ---
// Apply readable mode if it was enabled previously
if (localStorage.getItem('readableMode') === 'enabled') {
document.body.classList.add('readable-mode-active');
}
// Set the initial button position correctly
updateButtonPosition();

})();

Glad it's useful to someone else. Enjoy.

P.S. I never use dark more for reading personally, so you'll have to fit it for yourself. It's entirely in the first section of the script, play with it and see what happens. Or maybe ask an AI. Keep tweaking until you like the outcome.
 
... did you try the reader mode extension bro? It does exactly "bring together and stitch scattered chapters together", presenting a view that removes the intervening, non-threadmarked user posts in between.


There is no cosmetic alteration made to the text.
 

Users who are viewing this thread

Back
Top