The Printliminator (Lean)

Activate, click to remove, Alt+Click (keep only this section), Shift+Click (make full width), BACKSPACE to undo, ESC to deactivate (keeps changes). Inspired by The Printliminator by CSS-Tricks.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         The Printliminator (Lean)
// @namespace    https://greasyfork.org/en/users/1462137-piknockyou
// @version      1.0
// @author       Piknockyou (vibe-coded)
// @license      AGPL-3.0
// @description  Activate, click to remove, Alt+Click (keep only this section), Shift+Click (make full width), BACKSPACE to undo, ESC to deactivate (keeps changes). Inspired by The Printliminator by CSS-Tricks.
// @match        *://*/*
// @match        file:///*
// @icon         https://css-tricks.github.io/The-Printliminator/src/icons/favicon.ico
// @grant        GM.registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    // Prevents the script from running multiple times on the same page if re-injected.
    if (window.isPrintliminatorCoreLoaded) {
        return;
    }
    window.isPrintliminatorCoreLoaded = true;

    // This is the main object that holds all the logic for The Printliminator.
    const Printliminator = {
        isActive: false,         // Is the tool currently active and listening for clicks/keys?
        actionsHistory: [],      // Keeps a list of changes made, so we can "undo" them.

        // Configuration settings for the tool
        config: {
            highlightClass: "_pe_highlight",    // CSS class for the red highlight border on mouseover
            hiddenClass: "_pe_hidden",          // CSS class used to hide elements
            fullWidthClass: "_pe_full_width",   // CSS class for making an element take full page width
            stylesheetId: "_pe_styles",         // ID for the custom stylesheet added to the page

            // Keyboard shortcut settings
            keys: {
                undo: 8,                      // Backspace key code for "undo"
                deactivate: 27,               // Escape key code for "deactivate"
                keepOnlyModifier: "altKey",   // Modifier key (Alt) for "keep only this element's section"
                fullWidthModifier: "shiftKey" // Modifier key (Shift) for "make element full width"
            },

            // HTML tag types that the tool should ignore (e.g., script tags, invisible elements)
            ignoredElementTagNames: /^(SCRIPT|STYLE|LINK|META|HEAD|BR|HR)$/i
        },

        /**
         * Activates The Printliminator.
         * Sets up event listeners for mouse clicks, movements, and key presses.
         */
        activate: function() {
            if (Printliminator.isActive) {
                return; // Already active, do nothing.
            }

            Printliminator.actionsHistory = []; // Clear any previous history for a fresh start.
            Printliminator.injectStyleSheet();  // Add custom styles to the page.

            // Start listening for user interactions on the page.
            const bodyElement = document.body;
            Printliminator.listenTo(bodyElement, "click", Printliminator.handleClickOnPageElement, true); // `true` for capture phase
            Printliminator.listenTo(bodyElement, "mouseover", Printliminator.highlightElementOnMouseOver);
            Printliminator.listenTo(bodyElement, "mouseout", Printliminator.removeHighlightOnMouseOut);
            Printliminator.listenTo(document, "keydown", Printliminator.handleKeyboardShortcut);

            Printliminator.isActive = true;
        },

        /**
         * Deactivates The Printliminator.
         * Removes event listeners, but keeps any changes made (like hidden elements).
         */
        deactivate: function() {
            if (!Printliminator.isActive) {
                return; // Already inactive.
            }

            Printliminator.removeAllHighlights(); // Clean up any leftover highlights.

            // Stop listening to user interactions.
            const bodyElement = document.body;
            Printliminator.stopListeningTo(bodyElement, "click", Printliminator.handleClickOnPageElement, true);
            Printliminator.stopListeningTo(bodyElement, "mouseover", Printliminator.highlightElementOnMouseOver);
            Printliminator.stopListeningTo(bodyElement, "mouseout", Printliminator.removeHighlightOnMouseOut);
            Printliminator.stopListeningTo(document, "keydown", Printliminator.handleKeyboardShortcut);

            // Note: The stylesheet is intentionally NOT removed here, so hidden elements remain hidden.
            // The history for the current "session" is cleared.
            Printliminator.actionsHistory = [];

            Printliminator.isActive = false;
        },

        /**
         * Handles what happens when an element on the page is clicked.
         * @param {MouseEvent} event - The mouse click event object.
         */
        handleClickOnPageElement: function(event) {
            if (!Printliminator.isActive || event.target === document.body) {
                return; // Tool not active or click was on the page background.
            }

            event.preventDefault();         // Stop the browser's default action for the click (e.g., following a link).
            event.stopImmediatePropagation(); // Stop other click listeners on this element from running.

            const clickedElement = event.target;
            Printliminator.removeHighlightFromElement(clickedElement); // Remove highlight before action.

            if (event[Printliminator.config.keys.fullWidthModifier]) { // Shift+Click
                // Toggle full-width for the element.
                if (!Printliminator.isElementStyledWith(clickedElement, Printliminator.config.fullWidthClass)) {
                    Printliminator.applyCssClass(clickedElement, Printliminator.config.fullWidthClass);
                    Printliminator.actionsHistory.push(function() { // Action to undo: remove full width
                        Printliminator.removeCssClass(clickedElement, Printliminator.config.fullWidthClass);
                    });
                } else {
                    Printliminator.removeCssClass(clickedElement, Printliminator.config.fullWidthClass);
                    Printliminator.actionsHistory.push(function() { // Action to undo: re-apply full width
                        Printliminator.applyCssClass(clickedElement, Printliminator.config.fullWidthClass);
                    });
                }
            } else if (event[Printliminator.config.keys.keepOnlyModifier]) { // Alt+Click
                // Hide elements *around* the clicked one to "keep only" its section.
                const elementsToHide = Printliminator.getSurroundingElements(clickedElement);
                if (elementsToHide.length > 0) {
                    Printliminator.hideElements(elementsToHide);
                    Printliminator.actionsHistory.push({ type: 'show', elements: elementsToHide }); // Action to undo: show these elements
                }
            } else { // Normal Click
                // Hide the clicked element.
                Printliminator.hideElements(clickedElement);
                Printliminator.actionsHistory.push({ type: 'show', elements: clickedElement }); // Action to undo: show this element
            }
        },

        /**
         * Highlights an element when the mouse moves over it.
         * @param {MouseEvent} event - The mouse event object.
         */
        highlightElementOnMouseOver: function(event) {
            if (!Printliminator.isActive || event.target === document.body || Printliminator.isElementStyledWith(event.target, Printliminator.config.hiddenClass)) {
                return; // Tool not active, or hovering body, or element is already hidden.
            }
            Printliminator.applyCssClass(event.target, Printliminator.config.highlightClass);
        },

        /**
         * Removes the highlight from an element when the mouse moves out of it.
         * @param {MouseEvent} event - The mouse event object.
         */
        removeHighlightOnMouseOut: function(event) {
            // No need to check isActive; removing a non-existent class is harmless.
            Printliminator.removeHighlightFromElement(event.target);
        },

        /**
         * Handles keyboard shortcuts like Backspace (undo) and Escape (deactivate).
         * @param {KeyboardEvent} event - The keyboard event object.
         */
        handleKeyboardShortcut: function(event) {
            if (!Printliminator.isActive) {
                return;
            }

            const keyPressed = event.which || event.keyCode;

            switch (keyPressed) {
                case Printliminator.config.keys.undo: // Backspace
                    event.preventDefault(); // Stop browser from going back a page.
                    Printliminator.undoLastAction();
                    break;
                case Printliminator.config.keys.deactivate: // Escape
                    event.preventDefault(); // Stop other Escape key actions.
                    Printliminator.deactivate();
                    break;
            }
        },

        /**
         * Undoes the last action performed (hiding, making full-width).
         */
        undoLastAction: function() {
            if (!Printliminator.isActive || Printliminator.actionsHistory.length === 0) {
                 return; // Nothing to undo or tool not active.
            }
            const actionToUndo = Printliminator.actionsHistory.pop(); // Get the last action from our list.

            if (actionToUndo) {
                if (typeof actionToUndo === 'function') {
                    // This was a full-width toggle, so call the stored undo function.
                    actionToUndo();
                } else if (actionToUndo.type === 'show') {
                    // This was hiding elements, so now show them again.
                    Printliminator.showElements(actionToUndo.elements);
                }
            }
        },

        /**
         * Creates and adds the custom CSS stylesheet to the page.
         * This stylesheet contains rules for highlighting, hiding, and full-width elements.
         */
        injectStyleSheet: function() {
            if (document.getElementById(Printliminator.config.stylesheetId)) {
                return; // Stylesheet already exists.
            }

            const cssRules = `
                .${Printliminator.config.hiddenClass} { display: none !important; }
                .${Printliminator.config.highlightClass} {
                    outline: 2px solid red !important;
                    cursor: default !important;
                    box-shadow: 0 0 8px rgba(255, 0, 0, 0.7) !important;
                    position: relative;
                    z-index: 2147483647 !important; /* Max z-index */
                }
                .${Printliminator.config.fullWidthClass} {
                    width: 100vw !important; /* Viewport width */
                    min-width: 100vw !important;
                    max-width: 100vw !important;
                    position: relative !important;
                    left: 50% !important;
                    transform: translateX(-50%) !important; /* Center the element */
                    margin-left: 0 !important; margin-right: 0 !important;
                    padding-left: 0 !important; padding-right: 0 !important;
                    float: none !important;
                    box-sizing: border-box !important;
                    z-index: 2147483646 !important; /* High, but below highlight */
                }
                .${Printliminator.config.highlightClass}.${Printliminator.config.fullWidthClass} {
                    outline-color: blue !important;
                    box-shadow: 0 0 8px rgba(0, 0, 255, 0.7) !important;
                }
            `;
            const styleElement = document.createElement("style");
            styleElement.id = Printliminator.config.stylesheetId;
            styleElement.textContent = cssRules;
            document.head.insertBefore(styleElement, document.head.firstChild);
        },

        /**
         * Checks if an element is a valid target for editing (e.g., not a script tag, not already hidden).
         * @param {HTMLElement} element - The HTML element to check.
         * @returns {boolean} True if the element is valid, false otherwise.
         */
        isEditableElement: function(element) {
            return element &&
                   element.nodeType === 1 && // Is an actual element node
                   !Printliminator.config.ignoredElementTagNames.test(element.nodeName) &&
                   !Printliminator.isElementStyledWith(element, Printliminator.config.hiddenClass);
        },

        /**
         * Finds all sibling elements of a given element.
         * @param {HTMLElement} element - The element whose siblings to find.
         * @returns {Array<HTMLElement>} An array of sibling elements.
         */
        getSiblingElements: function(element) {
            const siblings = [];
            if (!element || !element.parentNode) return siblings;

            let sibling = element.parentNode.firstChild;
            while (sibling) {
                if (sibling !== element && Printliminator.isEditableElement(sibling)) {
                    siblings.push(sibling);
                }
                sibling = sibling.nextSibling;
            }
            return siblings;
        },

        /**
         * Finds elements surrounding a given element by looking at its siblings,
         * then its parent's siblings, and so on, up the page structure.
         * Used for the "Alt+Click to keep only" feature.
         * @param {HTMLElement} element - The central element.
         * @returns {Array<HTMLElement>} An array of surrounding elements to potentially hide.
         */
        getSurroundingElements: function(element) {
            let elementsToConsiderHiding = [];
            let currentElement = element;

            while (currentElement && currentElement.parentNode && currentElement.nodeName.toUpperCase() !== 'BODY') {
                elementsToConsiderHiding = elementsToConsiderHiding.concat(Printliminator.getSiblingElements(currentElement));
                currentElement = currentElement.parentNode;
                if (!currentElement || currentElement === document.documentElement) break;
            }
            // Filter out any elements that might themselves contain the original target element.
            return elementsToConsiderHiding.filter(el => el && !el.contains(element) && Printliminator.isEditableElement(el));
        },

        /**
         * Hides one or more elements by applying the hidden CSS class.
         * @param {HTMLElement|Array<HTMLElement>} elements - A single element or an array of elements to hide.
         */
        hideElements: function(elements) {
            if (!elements) return;
            const elementsArray = Array.isArray(elements) ? elements : [elements];
            elementsArray.forEach(el => {
                if (Printliminator.isEditableElement(el)) { // Double check before hiding
                    Printliminator.applyCssClass(el, Printliminator.config.hiddenClass);
                }
            });
        },

        /**
         * Shows one or more elements by removing the hidden CSS class.
         * @param {HTMLElement|Array<HTMLElement>} elements - A single element or an array of elements to show.
         */
        showElements: function(elements) {
             if (!elements) return;
            const elementsArray = Array.isArray(elements) ? elements : [elements];
            elementsArray.forEach(el => Printliminator.removeCssClass(el, Printliminator.config.hiddenClass));
        },

        /** Helper: Adds a CSS class to an element. Uses modern `classList`. */
        applyCssClass: function(element, cssClass) {
            if (element && element.classList) {
                element.classList.add(cssClass);
            }
        },

        /** Helper: Removes a CSS class from an element. Uses modern `classList`. */
        removeCssClass: function(element, cssClass) {
            if (element && element.classList) {
                element.classList.remove(cssClass);
            }
        },

        /** Helper: Checks if an element has a specific CSS class. Uses modern `classList`. */
        isElementStyledWith: function(element, cssClass) {
            return element && element.classList && element.classList.contains(cssClass);
        },

        /** Helper: Removes the highlight class from a specific element. */
        removeHighlightFromElement: function(element) {
            if (element && Printliminator.isElementStyledWith(element, Printliminator.config.highlightClass)) {
                 Printliminator.removeCssClass(element, Printliminator.config.highlightClass);
            }
        },

        /** Helper: Removes highlight from all currently highlighted elements on the page. */
        removeAllHighlights: function() {
            const highlightedElements = document.querySelectorAll('.' + Printliminator.config.highlightClass);
            highlightedElements.forEach(el => Printliminator.removeHighlightFromElement(el));
        },

        /**
         * Helper: Attaches an event listener to an element.
         * @param {EventTarget} element - The element to listen on (e.g., document.body).
         * @param {string} eventType - The type of event (e.g., "click", "mouseover").
         * @param {Function} callbackFunction - The function to call when the event occurs.
         * @param {boolean} [useCapturePhase=false] - Whether to listen during the capture phase.
         */
        listenTo: function(element, eventType, callbackFunction, useCapturePhase = false) {
            if (element) {
                element.addEventListener(eventType, callbackFunction, useCapturePhase);
            }
        },

        /**
         * Helper: Removes an event listener from an element.
         * @param {EventTarget} element - The element to stop listening on.
         * @param {string} eventType - The type of event.
         * @param {Function} callbackFunction - The function that was originally attached.
         * @param {boolean} [useCapturePhase=false] - Must match the useCapture value from addEventListener.
         */
        stopListeningTo: function(element, eventType, callbackFunction, useCapturePhase = false) {
            if (element) {
                element.removeEventListener(eventType, callbackFunction, useCapturePhase);
            }
        }
    };

    // Register menu commands for the userscript manager (e.g., Violentmonkey)
    if (typeof GM !== 'undefined' && typeof GM.registerMenuCommand === 'function') {
        GM.registerMenuCommand("Activate The Printliminator", () => {
            Printliminator.activate();
        }, "a"); // "a" is an example access key

        GM.registerMenuCommand("Deactivate The Printliminator (ESC)", () => {
            Printliminator.deactivate();
        }, "d"); // "d" is an example access key
    }

})();