Multi-Column Layout for printing (Print Only)

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(У мене вже є менеджер скриптів, дайте мені встановити його!)

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.

(I already have a user style manager, let me install it!)

// ==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;


})();