Reader view

Reader Mode + Highlight with resilient floating button for tricky sites

Version au 18/05/2026. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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);
})();