Local TXT Reader

Enhanced reading experience for local txt files with Element UI style and improved themes

Versión del día 17/9/2024. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Local TXT Reader
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  Enhanced reading experience for local txt files with Element UI style and improved themes
// @author       JiuYou2020
// @license      MIT
// @match        file:///*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // Ensure the script runs only on .txt files
    if (window.location.pathname.endsWith('.txt')) {
        // Set up styles
        const style = document.createElement('style');
        style.innerHTML = `
            body {
                margin: 0;
                padding: 0;
                display: flex;
                height: 100vh;
                overflow: hidden;
                transition: background-color 0.3s, color 0.3s;
                background-size: cover;
                background-position: center;
            }
            .outer-container {
                flex: 1;
                overflow-y: auto;
                scroll-behavior: smooth;
                padding: 20px;
            }
            .content-container {
                width: 55%;
                margin: 0 auto;
                transition: font-size 0.3s, line-height 0.3s, background-color 0.3s, color 0.3s;
                padding: 20px;
                border-radius: 10px;
            }
            .content-container p {
                text-indent: 2em;
                margin-top: 1em;
            }
            #settingsBtn {
                position: fixed;
                right: 20px;
                bottom: 20px;
                z-index: 1000;
                padding: 12px;
                background-color: #409EFF;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
                box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
                transition: background-color 0.3s;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #settingsBtn:hover {
                background-color: #66b1ff;
            }
            #settingsPanel {
                display: none;
                position: fixed;
                right: 20px;
                bottom: 80px;
                background-color: white;
                border: 1px solid #EBEEF5;
                border-radius: 4px;
                padding: 20px;
                z-index: 1000;
                box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
            }
            #settingsPanel div {
                margin-bottom: 15px;
            }
            #settingsPanel button {
                margin-right: 5px;
                padding: 8px 15px;
                border: 1px solid #DCDFE6;
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.3s;
                background-color: white;
                color: #606266;
            }
            #settingsPanel button:hover {
                color: #409EFF;
                border-color: #c6e2ff;
                background-color: #ecf5ff;
            }
            .theme-btn {
                width: 30px;
                height: 30px;
                border-radius: 4px;
            }
            #classicTheme { background-color: #e7e3d8; }
            #whiteTheme { background-color: #ffffff; border: 1px solid #DCDFE6 !important; }
            #darkTheme { background-color: #1e1e1e; }
            .font-btn, .line-height-btn {
                background-color: #409EFF !important;
                color: white !important;
                border: none !important;
            }
            .font-btn:hover, .line-height-btn:hover {
                background-color: #66b1ff !important;
                color: white !important;
            }
            #resetDefault {
                background-color: #F56C6C !important;
                color: white !important;
                border: none !important;
            }
            #resetDefault:hover {
                background-color: #f78989 !important;
            }
            #customBgInput {
                display: none;
            }
            #customBgLabel {
                display: inline-block;
                padding: 8px 15px;
                background-color: #67C23A;
                color: white;
                border-radius: 4px;
                cursor: pointer;
                transition: background-color 0.3s;
            }
            #customBgLabel:hover {
                background-color: #85ce61;
            }
        `;
        document.head.appendChild(style);

        // Get the text content
        const text = document.body.textContent.trim();

        // Split the text into paragraphs by newlines
        const paragraphs = text.split(/\n+/).map(paragraph => paragraph.trim());

        // Clear the body content
        document.body.innerHTML = '';

        // Create the outer container
        const outerContainer = document.createElement('div');
        outerContainer.className = 'outer-container';
        document.body.appendChild(outerContainer);

        // Create the content container
        const contentContainer = document.createElement('div');
        contentContainer.className = 'content-container';
        outerContainer.appendChild(contentContainer);

        // Add paragraphs to the content container
        paragraphs.forEach(paragraph => {
            if (paragraph) { // Only add non-empty paragraphs
                const p = document.createElement('p');
                p.textContent = paragraph;
                contentContainer.appendChild(p);
            }
        });

        // Create settings button
        const settingsBtn = document.createElement('button');
        settingsBtn.id = 'settingsBtn';
        settingsBtn.textContent = '设置';
        document.body.appendChild(settingsBtn);

        // Create settings panel
        const settingsPanel = document.createElement('div');
        settingsPanel.id = 'settingsPanel';
        settingsPanel.innerHTML = `
            <div>
                大小:
                <button id="decreaseFont" class="font-btn">-</button>
                <span id="currentFontSize"></span>
                <button id="increaseFont" class="font-btn">+</button>
            </div>
            <div>
                行高:
                <button id="decreaseLineHeight" class="line-height-btn">-</button>
                <span id="currentLineHeight"></span>
                <button id="increaseLineHeight" class="line-height-btn">+</button>
            </div>
            <div>
                主题:
                <button id="classicTheme" class="theme-btn" title="经典"></button>
                <button id="whiteTheme" class="theme-btn" title="纯白"></button>
                <button id="darkTheme" class="theme-btn" title="暗黑"></button>
            </div>
            <div>
                <label for="customBgInput" id="customBgLabel">自定义背景</label>
                <input type="file" id="customBgInput" accept="image/*">
            </div>
            <div>
                <button id="resetDefault">恢复默认设置</button>
            </div>
        `;
        document.body.appendChild(settingsPanel);

        // Define themes
        const themes = {
            classic: { bg: '#e7e3d8', text: '#333333', contentBg: '#f1ede4' },
            white: { bg: '#ffffff', text: '#333333', contentBg: '#ffffff' },
            dark: { bg: '#1e1e1e', text: '#ffffff', contentBg: 'rgba(30, 30, 30, 0.8)' }
        };

        // Function to save settings
        function saveSettings() {
            const settings = {
                fontSize: contentContainer.style.fontSize,
                lineHeight: contentContainer.style.lineHeight,
                theme: currentTheme,
                customBg: document.body.style.backgroundImage,
                btnPosition: {
                    right: settingsBtn.style.right,
                    bottom: settingsBtn.style.bottom
                }
            };
            GM_setValue('readerSettings', JSON.stringify(settings));
        }

        // Function to load settings
        function loadSettings() {
            const savedSettings = GM_getValue('readerSettings', null);
            if (savedSettings) {
                const settings = JSON.parse(savedSettings);
                contentContainer.style.fontSize = settings.fontSize || '19px';
                contentContainer.style.lineHeight = settings.lineHeight || '1.5';
                applyTheme(settings.theme || 'classic');
                if (settings.customBg) {
                    document.body.style.backgroundImage = settings.customBg;
                }
                if (settings.btnPosition) {
                    settingsBtn.style.right = settings.btnPosition.right;
                    settingsBtn.style.bottom = settings.btnPosition.bottom;
                }
            } else {
                resetToDefault();
            }
        }

        // Function to apply theme
        function applyTheme(theme) {
            if (theme !== 'custom') {
                document.body.style.backgroundColor = themes[theme].bg;
                document.body.style.color = themes[theme].text;
                contentContainer.style.backgroundColor = themes[theme].contentBg;
                document.body.style.backgroundImage = '';
            }
            currentTheme = theme;
        }

        // Function to reset to default settings
        function resetToDefault() {
            contentContainer.style.fontSize = '19px';
            contentContainer.style.lineHeight = '1.5';
            applyTheme('classic');
            document.body.style.backgroundImage = '';
            settingsBtn.style.right = '20px';
            settingsBtn.style.bottom = '20px';
            updateSettingsDisplay();
            saveSettings();
        }

        // Load settings
        let currentTheme = 'classic';
        loadSettings();

        // Function to save the current scroll position
        function saveScrollPosition() {
            const scrollPosition = outerContainer.scrollTop;
            const fileName = window.location.pathname.split('/').pop();
            GM_setValue(fileName, scrollPosition);
        }

        // Function to load the saved scroll position
        function loadScrollPosition() {
            const fileName = window.location.pathname.split('/').pop();
            const savedPosition = GM_getValue(fileName, 0);
            outerContainer.scrollTop = savedPosition;
        }

        // Load the saved position when the page loads
        loadScrollPosition();

        // Save the position when the user scrolls
        outerContainer.addEventListener('scroll', saveScrollPosition);

        // Handle arrow key navigation
        document.addEventListener('keydown', function(e) {
            const scrollAmount = 100; // Adjust this value to change scroll speed
            if (e.key === 'ArrowUp') {
                outerContainer.scrollBy({top: -scrollAmount, behavior: 'smooth'});
                e.preventDefault();
            } else if (e.key === 'ArrowDown') {
                outerContainer.scrollBy({top: scrollAmount, behavior: 'smooth'});
                e.preventDefault();
            }
        });

        // Toggle settings panel
        settingsBtn.addEventListener('click', () => {
            settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
            updatePanelPosition();
        });

        // Make settings button draggable
        let isDragging = false;
        let dragOffsetX, dragOffsetY;

        settingsBtn.addEventListener('mousedown', (e) => {
            isDragging = true;
            dragOffsetX = e.clientX - settingsBtn.offsetLeft;
            dragOffsetY = e.clientY - settingsBtn.offsetTop;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const newX = e.clientX - dragOffsetX;
                const newY = e.clientY - dragOffsetY;
                settingsBtn.style.right = `${document.body.clientWidth - newX - settingsBtn.offsetWidth}px`;
                settingsBtn.style.bottom = `${document.body.clientHeight - newY - settingsBtn.offsetHeight}px`;
                updatePanelPosition();
            }
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                saveSettings();
            }
        });

        // Update settings panel position
        function updatePanelPosition() {
            const btnRect = settingsBtn.getBoundingClientRect();
            settingsPanel.style.right = settingsBtn.style.right;
            settingsPanel.style.bottom = `${parseFloat(settingsBtn.style.bottom) + btnRect.height + 10}px`;
        }

        // Font size controls
        document.getElementById('decreaseFont').addEventListener('click', () => {
            const currentSize = parseInt(contentContainer.style.fontSize);
            if (currentSize > 10) {
                contentContainer.style.fontSize = (currentSize - 1) + 'px';
                updateSettingsDisplay();
                saveSettings();
            }
        });

        document.getElementById('increaseFont').addEventListener('click', () => {
            const currentSize = parseInt(contentContainer.style.fontSize);
            if (currentSize < 30) {
                contentContainer.style.fontSize = (currentSize + 1) + 'px';
                updateSettingsDisplay();
                saveSettings();
            }
        });

        // Line height controls
        document.getElementById('decreaseLineHeight').addEventListener('click', () => {
            const currentHeight = parseFloat(contentContainer.style.lineHeight);
            if (currentHeight > 1) {
                contentContainer.style.lineHeight = (currentHeight - 0.1).toFixed(1);
                updateSettingsDisplay();
                saveSettings();
            }
        });

        document.getElementById('increaseLineHeight').addEventListener('click', () => {
            const currentHeight = parseFloat(contentContainer.style.lineHeight);
            if (currentHeight < 3) {
                contentContainer.style.lineHeight = (currentHeight + 0.1).toFixed(1);
                updateSettingsDisplay();
                saveSettings();
            }
        });

        // Theme controls
        document.getElementById('classicTheme').addEventListener('click', () => {
            applyTheme('classic');
            saveSettings();
        });

        document.getElementById('whiteTheme').addEventListener('click', () => {
            applyTheme('white');
            saveSettings();
        });

        document.getElementById('darkTheme').addEventListener('click', () => {
            applyTheme('dark');
            saveSettings();
        });

        // Custom background
        document.getElementById('customBgInput').addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    document.body.style.backgroundImage = `url(${e.target.result})`;
                    currentTheme = 'custom';
                    saveSettings();
                }
                reader.readAsDataURL(file);
            }
        });

        // Reset to default
        document.getElementById('resetDefault').addEventListener('click', resetToDefault);

        // Update display of current settings
        function updateSettingsDisplay() {
            document.getElementById('currentFontSize').textContent = contentContainer.style.fontSize;
            document.getElementById('currentLineHeight').textContent = contentContainer.style.lineHeight;
        }

        updateSettingsDisplay();
    }
})();