Reader view

Reader Mode + Highlight with resilient floating button for tricky sites

Verze ze dne 18. 05. 2026. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Reader view
// @namespace    http://tampermonkey.net/
// @version      16.10.2025
// @description  Reader Mode + Highlight with resilient floating button for tricky sites
// @author       Copilot
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let readerOpened = false; // flag to prevent reinjection after Reader Mode

    // ✅ Menu command added
    GM_registerMenuCommand("Open Reader Mode", () => {
        const btn = document.getElementById('tm-reader-btn');
        if (btn) {
            btn.click();
        } else {
            alert("Reader button not available yet.");
        }
    });

    function injectButton() {
        if (readerOpened) return; // stop reinjecting once Reader Mode is active
        if (document.getElementById('tm-reader-box')) return;

        const readerBox = document.createElement('div');
        readerBox.id = 'tm-reader-box';
        const readerBtn = document.createElement('button');
        readerBtn.id = 'tm-reader-btn';
        readerBtn.textContent = 'Reader';
        readerBox.appendChild(readerBtn);

        // Strong CSS
        const style = document.createElement('style');
        style.textContent = `
            #tm-reader-box {
                position: fixed !important;
                bottom: 10px !important;
                right: 60px !important;
                padding: 2px 6px !important;
                background: transparent !important;
                border: 1px solid transparent !important;
                border-radius: 6px !important;
                z-index: 2147483647 !important;
            }
            #tm-reader-btn {
                border: none !important;
                background: transparent !important;
                color: #696969 !important;
                font-size: 12px !important;
                cursor: pointer !important;
                font-family: system-ui, sans-serif !important;

                padding: 0 !important;
                margin: 0 !important;
            }
            #tm-reader-btn:hover { text-decoration: underline !important; }
        `;
        document.head.appendChild(style);

        // Append to both html and body
        if (document.body) document.body.appendChild(readerBox);
        document.documentElement.appendChild(readerBox);

        // Reader Mode logic
        readerBtn.addEventListener('click', async function() {
            const loadScript = url => new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = url;
                script.onload = resolve;
                script.onerror = reject;
                document.head.appendChild(script);
            });

            try {
                if (!window.Readability) {
                    await loadScript('https://cdn.jsdelivr.net/npm/@mozilla/[email protected]/Readability.js');
                }
                if (!window.DOMPurify) {
                    await loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js');
                }

                const clone = document.cloneNode(true);
                const article = new Readability(clone).parse();
                if (!article) {
                    alert('Reader Mode: No article found on this page.');
                    return;
                }

                // Remove the floating Reader button completely
                const readerBox = document.getElementById('tm-reader-box');
                if (readerBox) readerBox.remove();
                readerOpened = true; // prevent reinjection

                const css = `
                    html, body { margin:0; padding:0; background:#fff; }
                    body {
                        font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif !important;
                        max-width: 48rem;
                        margin: 0 auto;
                        padding: 2rem;
                        color: #111;
                        line-height: 1.6;
                    }
                    article *, article {
                        font-size: 28px !important;
                        font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif !important;
                    }
                    p, div, span, li { margin-bottom:1.5rem; word-break:normal; overflow-wrap:break-word; }
                    h1,h2,h3,h4,h5,h6 { line-height:1.3; margin:1.5rem 0 0.5rem; font-weight:700; }
                    a { color:#0b57d0; text-decoration:none; cursor:pointer; }
                    a:hover { text-decoration:underline; }
                    img, video { max-width:100%; height:auto; border-radius:8px; display:block; margin:2rem auto; }
                    pre, code { font-family: ui-monospace, monospace !important; font-size:22px !important; background:#f5f5f5; padding:0.2em 0.4em; border-radius:3px; }
                    @media (prefers-color-scheme: dark) {
                        html, body { background:#111; }
                        body { color:#eee; }
                        pre, code { background:#222; }
                        a { color:#4a90e2; }
                    }
                `;

                const html = `
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <meta charset="utf-8">
                        <meta name="viewport" content="width=device-width, initial-scale=1.0">
                        <title>${article.title || 'Reader Mode'}</title>
                        <style>${css}</style>
                    </head>
                    <body>
                        <article>
                            <h1>${article.title || ''}</h1>
                            <div>${window.DOMPurify ? DOMPurify.sanitize(article.content) : article.content}</div>
                        </article>
                    </body>
                    </html>
                `;

                document.open();
                document.write(html);
                document.close();

                // Highlight button
                const btn = document.createElement('button');
                btn.innerText = '🖍️';
                btn.id = 'tm-highlight-btn';
                btn.style.cssText = `
                    position: fixed;
                    bottom: 20px;
                    right: 20px;
                    z-index: 10000;
                    padding: 10px;
                    border-radius: 50%;
                    background-color: #ffeb3b;
                    border: 2px solid #000;
                    font-size: 20px;
                    display: none;
                `;
                document.body.appendChild(btn);

                document.addEventListener('selectionchange', () => {
                    const selection = window.getSelection();
                    btn.style.display = (selection.toString().length > 0) ? 'block' : 'none';
                });

                btn.addEventListener('click', () => {
                    const selection = window.getSelection();
                    if (!selection.rangeCount) return;
                    const range = selection.getRangeAt(0);
                    const mark = document.createElement('mark');
                    mark.style.backgroundColor = 'yellow';
                    mark.style.color = 'black';
                    try {
                        range.surroundContents(mark);
                        selection.removeAllRanges();
                        btn.style.display = 'none';
                    } catch (e) {
                        alert("Can't highlight across multiple complex elements. Try a smaller selection!");
                    }
                });
            } catch (err) {
                alert('Reader Mode failed: ' + err.message);
            }
        });
    }

    // Inject after load
    window.addEventListener('load', injectButton);

    // Reinjection checks
    const observer = new MutationObserver(() => injectButton());
    observer.observe(document.documentElement, { childList: true, subtree: true });
    setInterval(injectButton, 2000);
})();