Multi-Column Layout for printing (Print Only)

Convert single column layout to multi-column layout only when printing. Press Ctrl+S to open settings.

// ==UserScript==
// @name         Multi-Column Layout for printing (Print Only)
// @namespace    http://tampermonkey.net/
// @license MIT
// @version      2.5
// @description  Convert single column layout to multi-column layout only when printing. Press Ctrl+S to open settings.
// @author       KQ yang
// @match        *://*/*
// @match        file:///*
// @match        http://127.0.0.1:*/*
// @match        http://localhost:*/*
// @grant        GM_addStyle
// @run-at       document-start
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    // Add colorful title and usage instructions right after initialization
    console.log('%c Multi-Column Layout for Printing v2.3 ',
        'background: #4A90E2; color: white; font-size: 14px; font-weight: bold; padding: 8px; border-radius: 4px; text-shadow: 1px 1px 1px rgba(0,0,0,0.2);');
    console.log('%c 📖 Usage: Press Ctrl+S to open settings, customize your layout, then Ctrl+P to print! ',
        'background: #2ECC71; color: white; font-size: 12px; padding: 6px; border-radius: 4px;');


    // Default configuration
    const DEFAULT_CONFIG = {
        columns: 1,
        columnGap: '30px',
        fontSize: '16px',
        paragraphSpacing: '1em',
        enablePageBreak: true,
        lineHeight: '1.5',
    };

    // Load config from localStorage or use defaults
    let CONFIG = loadConfig();

    function loadConfig() {
        const savedConfig = localStorage.getItem('printLayoutConfig');
        return savedConfig ? {...DEFAULT_CONFIG, ...JSON.parse(savedConfig)} : DEFAULT_CONFIG;
    }

    function saveConfig(config) {
        localStorage.setItem('printLayoutConfig', JSON.stringify(config));
        CONFIG = config;
        updateStyles();
    }

    let configModal = null; // 将configModal提升为全局变量

    // Create and inject the configuration UI
    function createConfigUI() {

        // 如果已经存在modal,先移除
        if (configModal) {
            configModal.remove();
        }

        configModal = document.createElement('div');
        configModal.id = 'print-layout-config-modal';
        configModal.setAttribute('style', `
            all: initial;
            position: fixed !important;
            top: 50% !important;
            left: 50% !important;
            transform: translate(-50%, -50%) !important;
            background: white !important;
            padding: 30px !important;
            border-radius: 12px !important;
            box-shadow: 0 8px 24px rgba(0,0,0,0.15) !important;
            z-index: 2147483647 !important;
            display: none !important;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
            min-width: 320px !important;
            max-width: 90vw !important;
            animation: modalFadeIn 0.3s ease-out !important;
            color: black !important;
        `);

        const styleElement = document.createElement('style');
        styleElement.textContent = `
            @keyframes modalFadeIn {
                from {
                    opacity: 0;
                    transform: translate(-50%, -48%);
                }
                to {
                    opacity: 1;
                    transform: translate(-50%, -50%);
                }
            }
            .settings-row {
                margin-bottom: 20px;
                display: flex;
                align-items: center;
                justify-content: space-between;
            }
            .settings-row label {
                color: #333;
                font-size: 14px;
                margin-right: 15px;
            }
            .settings-row input[type="number"],
            .settings-row input[type="text"] {
                width: 100px;
                padding: 8px;
                border: 1px solid #ddd;
                border-radius: 6px;
                font-size: 14px;
                transition: border-color 0.2s;
            }
            .settings-row input:focus {
                outline: none;
                border-color: #4A90E2;
                box-shadow: 0 0 0 2px rgba(74,144,226,0.2);
            }
            .settings-row input[type="checkbox"] {
                width: 18px;
                height: 18px;
                cursor: pointer;
            }
            .modal-title {
                color: #333;
                margin: 0 0 25px 0;
                font-size: 18px;
                font-weight: 600;
                border-bottom: 2px solid #eee;
                padding-bottom: 15px;
            }
            .modal-footer {
                margin-top: 25px;
                padding-top: 20px;
                border-top: 2px solid #eee;
                text-align: right;
            }
            .save-button {
                background: #4A90E2;
                color: white;
                border: none;
                padding: 10px 20px;
                border-radius: 6px;
                font-size: 14px;
                cursor: pointer;
                transition: background-color 0.2s;
            }
            .save-button:hover {
                background: #357ABD;
            }
            .close-button {
                position: absolute;
                top: 15px;
                right: 15px;
                background: none;
                border: none;
                font-size: 20px;
                cursor: pointer;
                color: #666;
                padding: 5px;
                line-height: 1;
            }
            .close-button:hover {
                color: #333;
            }
            #print-layout-config-modal {
                visibility: visible !important;
                display: block !important;
            }
        `;
        document.head.appendChild(styleElement);

        configModal.innerHTML = ''; // 清空现有内容
        const modalContent = document.createElement('div');
        modalContent.style.cssText = `
            all: initial;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
            color: black !important;
        `;
        modalContent.innerHTML = `
            <h3 class="modal-title">Print Layout Settings</h3>
            <button class="close-button" title="Close">×</button>
            <div class="settings-row">
                <label>Columns (1-4):</label>
                <input type="number" id="columns" min="1" max="4" value="${CONFIG.columns}">
            </div>
            <div class="settings-row">
                <label>Column Gap:</label>
                <input type="text" id="columnGap" value="${CONFIG.columnGap}">
            </div>
            <div class="settings-row">
                <label>Font Size:</label>
                <input type="text" id="fontSize" value="${CONFIG.fontSize}">
            </div>
            <div class="settings-row">
                <label>Paragraph Spacing:</label>
                <input type="text" id="paragraphSpacing" value="${CONFIG.paragraphSpacing}">
            </div>
            <div class="settings-row">
                <label>Line Height:</label>
                <input type="text" id="lineHeight" value="${CONFIG.lineHeight}">
            </div>
            <div class="settings-row">
                <label>Enable Page Break:</label>
                <input type="checkbox" id="enablePageBreak" ${CONFIG.enablePageBreak ? 'checked' : ''}>
            </div>
            <div class="modal-footer">
                <button class="save-button">Save Changes</button>
            </div>
        `;

        configModal.appendChild(modalContent);
        document.documentElement.appendChild(configModal);

        // Save button handler
        const saveButton = configModal.querySelector('.save-button');
        if (saveButton) {
            saveButton.addEventListener('click', () => {
                const newConfig = {
                    columns: parseInt(configModal.querySelector('#columns').value, 10),
                    columnGap: configModal.querySelector('#columnGap').value,
                    fontSize: configModal.querySelector('#fontSize').value,
                    paragraphSpacing: configModal.querySelector('#paragraphSpacing').value,
                    lineHeight: configModal.querySelector('#lineHeight').value,
                    enablePageBreak: configModal.querySelector('#enablePageBreak').checked
                };
                saveConfig(newConfig);
                configModal.style.setProperty('display', 'none', 'important');
            });
        }

        // Close button handler
        const closeButton = configModal.querySelector('.close-button');
        if (closeButton) {
            closeButton.addEventListener('click', () => {
                configModal.style.setProperty('display', 'none', 'important');
            });
        }

        // Click outside to close
        configModal.addEventListener('click', (e) => {
            if (e.target === configModal) {
                configModal.style.setProperty('display', 'none', 'important');
            }
        });

        return configModal;
    }

    function showConfigModal() {
        console.log('Showing config modal'); // 调试日志
        if (!configModal) {
            configModal = createConfigUI();
        }
        configModal.style.setProperty('display', 'block', 'important');
        configModal.style.setProperty('visibility', 'visible', 'important');
    }

    function hideConfigModal() {
        if (configModal) {
            configModal.style.setProperty('display', 'none', 'important');
        }
    }

    function toggleConfigModal() {
        if (!configModal || configModal.style.display === 'none') {
            showConfigModal();
        } else {
            hideConfigModal();
        }
    }

    // Create and update styles based on current config
    function updateStyles() {
        const styleSheet = `
            @media print {
                html, body {
                    margin: 0 !important;
                    padding: 0 !important;
                    min-height: 0 !important;
                    height: auto !important;
                }
                .print-column-container {
                    column-count: ${CONFIG.columns} !important;
                    column-gap: ${CONFIG.columnGap} !important;
                    column-rule: 1px solid #ddd !important;
                    width: 100% !important;
                    margin: 0 !important;
                    padding: 0 !important;
                    min-height: 0 !important;
                    height: auto !important;
                    overflow: visible !important;
                    box-sizing: border-box !important;
                    font-size: ${CONFIG.fontSize} !important;
                    line-height: ${CONFIG.lineHeight} !important;
                    ${CONFIG.enablePageBreak ? '' : 'page-break-inside: avoid !important;'}
                }
                .print-column-container > * {
                    break-inside: avoid !important;
                    margin-bottom: ${CONFIG.paragraphSpacing} !important;
                    max-width: 100% !important;
                    box-sizing: border-box !important;
                    page-break-inside: avoid !important;
                }
                .print-column-container img {
                    max-width: 100% !important;
                    height: auto !important;
                    page-break-inside: avoid !important;
                }
            }
        `;

        const existingStyle = document.getElementById('print-layout-style');
        if (existingStyle) {
            existingStyle.remove();
        }

        const style = document.createElement('style');
        style.id = 'print-layout-style';
        style.textContent = styleSheet;
        document.head.appendChild(style);
    }

    // Apply columns to main content
    function applyPrintColumns() {
        const mainContent = document.querySelector('.target-content') || document.body;
        mainContent.classList.add('print-column-container');

        const printStyle = document.createElement('style');
        printStyle.media = 'print';
        printStyle.textContent = `
            @page {
                margin: 1cm !important;
                padding: 0 !important;
                size: auto !important;
            }
        `;
        document.head.appendChild(printStyle);
    }

    // Initialize modal globally
    configModal = createConfigUI();

    // Handle Ctrl+S shortcut with improved event handling
    document.addEventListener('keydown', function(e) {
        if (e.ctrlKey && (e.key === 's' || e.key === 'S' || e.keyCode === 83)) {
            console.log('Ctrl+S detected'); // 调试日志
            e.stopPropagation();
            e.preventDefault();
            toggleConfigModal();
            return false;
        }
    }, true);

    // Initial style application
    updateStyles();

    // Handle DOMContentLoaded
    function onDOMContentLoaded() {
        applyPrintColumns();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
    } else {
        onDOMContentLoaded();
    }

    // Add a global function for testing
    window.togglePrintLayoutConfig = toggleConfigModal;


})();