DeepL UI Cleaner

Streamlines DeepL's interface for translation-focused use by removing footers, cookie banners, and adding toggle buttons for sidebar and header

当前为 2024-08-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         DeepL UI Cleaner
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Streamlines DeepL's interface for translation-focused use by removing footers, cookie banners, and adding toggle buttons for sidebar and header
// @match        https://www.deepl.com/*
// @license      Unlicense
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
	"use strict";

	// Define selectors for target elements
	const TARGETS = {
		remove: {
			footer: [
				"footer",
				".relative.bg-neutral-next-50 > .mobile\\:hidden",
				'.bg-white[class*="px-0"][class*="xl:px-"][class*="md:px-"][class*="min-"][class*="px-"]',
			],
			cookieBanner: ['[id*="cookieBanner"]', '[class*="cookieBanner"]'],
			writePageElements: [
				'.bg-white.px-0[class*="xl:px-"][class*="px"][class*="md:px-"][class*="px"][class*="min-"][class*="px"][class*="px"]',
				'.mobile\\:hidden.p-8.px-0[class*="xl:px-"][class*="px"][class*="md:px-"][class*="px"][class*="min-"][class*="px"][class*="px"]',
			],
		},
		toggle: {
			leftSidebar:
				'[class*="md:block"].bg-white.border-e[class*="w-["][class*="px]"].h-full.hidden[class*="start-0"].border-neutral-next-100',
			topHeader: '[class*="BasePageHeader-module--container"]',
		},
	};

	// CSS styles for toggle buttons
	const BUTTON_STYLES = `
        .toggle-button {
            position: fixed;
            z-index: 10000;
            width: 2em;
            height: 2em;
            background-color: rgba(240, 240, 240, 0.7);
            border: 1px solid #ccc;
            border-radius: 50%;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
            transition: all 0.3s;
        }
        .toggle-button:hover {
            background-color: rgba(220, 220, 220, 0.9);
        }
    `;

	// Function to inject CSS styles into the page
	function injectStyles() {
		const style = document.createElement("style");
		const hideSelectors = [
			...Object.values(TARGETS.remove).flat(),
			...Object.values(TARGETS.toggle),
		].join(", ");
		style.textContent = `
            ${hideSelectors} { display: none !important; }
            ${BUTTON_STYLES}
        `;
		(document.head || document.documentElement).appendChild(style);
	}

	// Function to remove unwanted elements from the page
	function removeElements() {
		Object.values(TARGETS.remove)
			.flat()
			.forEach((selector) => {
				document
					.querySelectorAll(selector)
					.forEach((element) => element.remove());
			});
	}

	// Function to create a toggle button
	function createToggleButton(
		showIcon,
		hideIcon,
		onClick,
		position,
		ariaLabel
	) {
		const button = document.createElement("button");
		button.className = "toggle-button";
		button.textContent = showIcon;
		button.setAttribute("aria-label", ariaLabel); // Accessibility improvement
		button.addEventListener("click", onClick);
		Object.assign(button.style, position);
		return button;
	}

	// Function to adjust button positions based on header visibility
	function adjustButtonPositions() {
		const header = document.querySelector(TARGETS.toggle.topHeader);
		const topPosition =
			header && getComputedStyle(header).display !== "none"
				? `${header.offsetHeight}px`
				: "0.5em";
		document.querySelectorAll(".toggle-button").forEach((button) => {
			button.style.top = topPosition;
		});
	}

	// Function to toggle visibility of elements
	function toggleElementVisibility(selector, button, showIcon, hideIcon) {
		const elements = document.querySelectorAll(selector);
		const isVisible = [...elements].some(
			(el) => getComputedStyle(el).display !== "none"
		);
		elements.forEach((el) =>
			el.style.setProperty("display", isVisible ? "none" : "block", "important")
		);
		button.textContent = isVisible ? showIcon : hideIcon;
		adjustButtonPositions();
	}

	// Function to initialize the script
	function init() {
		try {
			removeElements();

			const buttons = [
				{
					selector: TARGETS.toggle.leftSidebar,
					icons: ["≡", "×"],
					position: { left: "0.5em" },
					ariaLabel: "Toggle sidebar visibility",
				},
				{
					selector: TARGETS.toggle.topHeader,
					icons: ["▼", "▲"],
					position: { left: "3em" },
					ariaLabel: "Toggle header visibility",
				},
			];

			buttons.forEach(
				({ selector, icons: [showIcon, hideIcon], position, ariaLabel }) => {
					const button = createToggleButton(
						showIcon,
						hideIcon,
						() => toggleElementVisibility(selector, button, showIcon, hideIcon),
						position,
						ariaLabel
					);
					document.body.appendChild(button);
				}
			);

			adjustButtonPositions();

			// Set up MutationObserver to handle dynamically added elements
			const observer = new MutationObserver(() => {
				removeElements();
				adjustButtonPositions();
			});
			observer.observe(document.body, { childList: true, subtree: true });
		} catch (error) {
			console.error("DeepL UI Cleaner encountered an error:", error);
		}
	}

	// Inject styles immediately
	injectStyles();

	// Run initialization when DOM is ready
	if (document.readyState === "loading") {
		document.addEventListener("DOMContentLoaded", init);
	} else {
		init();
	}
})();