Disable YouTube Hotkeys with Modern Settings Page

Disable various YouTube hotkeys, including frame skip, with a modern settings page

// ==UserScript==
// @name         Disable YouTube Hotkeys with Modern Settings Page
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Disable various YouTube hotkeys, including frame skip, with a modern settings page
// @author       You
// @match        *://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    // Load saved settings or default to enabling all keys
    let settings = GM_getValue('hotkeySettings', {
        disableNumericKeys: false,
        disableSpacebar: false,
        disableArrowKeys: false,
        disableFKey: false,
        disableMKey: false,
        disableSpeedControl: false,
        disableFrameSkip: false
    });

    // Function to handle keydown events and disable selected hotkeys
    window.addEventListener('keydown', function(e) {
        // Disable numeric keys (0-9)
        if (settings.disableNumericKeys && e.key >= '0' && e.key <= '9') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable spacebar
        if (settings.disableSpacebar && e.code === 'Space') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable arrow keys (left, right, up, down)
        if (settings.disableArrowKeys && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.code)) {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable F (for fullscreen)
        if (settings.disableFKey && e.key.toLowerCase() === 'f') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable M (for mute)
        if (settings.disableMKey && e.key.toLowerCase() === 'm') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable speed control (Shift + > or Shift + <)
        if (settings.disableSpeedControl && (e.shiftKey && (e.key === '>' || e.key === '<'))) {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable frame skip (`,` for backward, `.` for forward)
        if (settings.disableFrameSkip && (e.key === ',' || e.key === '.')) {
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);

    // Create and display the settings modal using safe DOM manipulation
    function openSettings() {
        // Remove any existing modal or overlay
        let existingModal = document.getElementById('yt-hotkey-settings-modal');
        let existingOverlay = document.getElementById('yt-hotkey-settings-overlay');
        if (existingModal) existingModal.remove();
        if (existingOverlay) existingOverlay.remove();

        // Create the modal container
        let modal = document.createElement('div');
        modal.id = 'yt-hotkey-settings-modal';
        modal.className = 'modal-card';

        // Create modal header
        let header = document.createElement('div');
        header.className = 'modal-header';

        let title = document.createElement('h2');
        title.textContent = 'YouTube Hotkey Settings';
        header.appendChild(title);

        let closeButton = document.createElement('span');
        closeButton.id = 'closeSettingsBtn';
        closeButton.className = 'close-btn';
        closeButton.textContent = '×';
        header.appendChild(closeButton);

        modal.appendChild(header);

        // Create modal content (checkboxes)
        let content = document.createElement('div');
        content.className = 'modal-content';

        let checkboxes = [
            { id: 'disableNumericKeys', label: 'Disable Numeric Keys (0-9)', checked: settings.disableNumericKeys },
            { id: 'disableSpacebar', label: 'Disable Spacebar (Play/Pause)', checked: settings.disableSpacebar },
            { id: 'disableArrowKeys', label: 'Disable Arrow Keys (Rewind/FF, Volume)', checked: settings.disableArrowKeys },
            { id: 'disableFKey', label: 'Disable F Key (Fullscreen)', checked: settings.disableFKey },
            { id: 'disableMKey', label: 'Disable M Key (Mute)', checked: settings.disableMKey },
            { id: 'disableSpeedControl', label: 'Disable Speed Control (Shift + > / <)', checked: settings.disableSpeedControl },
            { id: 'disableFrameSkip', label: 'Disable Frame Skip (`,` and `.`)', checked: settings.disableFrameSkip }
        ];

        checkboxes.forEach(hotkey => {
            let label = document.createElement('label');
            label.className = 'custom-checkbox';

            let checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = hotkey.id;
            checkbox.checked = hotkey.checked;

            let checkmark = document.createElement('span');
            checkmark.className = 'checkmark';

            label.appendChild(checkbox);
            label.appendChild(checkmark);
            label.appendChild(document.createTextNode(` ${hotkey.label}`));
            content.appendChild(label);
        });

        modal.appendChild(content);

        // Create modal footer (save button)
        let footer = document.createElement('div');
        footer.className = 'modal-footer';

        let saveButton = document.createElement('button');
        saveButton.id = 'saveSettingsBtn';
        saveButton.className = 'primary-btn';
        saveButton.textContent = 'Save Settings';
        footer.appendChild(saveButton);

        modal.appendChild(footer);

        // Append modal to the document body
        document.body.appendChild(modal);

        // Create the overlay (dark background behind modal)
        let overlay = document.createElement('div');
        overlay.id = 'yt-hotkey-settings-overlay';
        overlay.className = 'modal-overlay';
        document.body.appendChild(overlay);

        // Close modal on clicking the close button or overlay
        closeButton.addEventListener('click', closeSettings);
        overlay.addEventListener('click', closeSettings);

        // Save settings on clicking the save button
        saveButton.addEventListener('click', function() {
            settings.disableNumericKeys = document.getElementById('disableNumericKeys').checked;
            settings.disableSpacebar = document.getElementById('disableSpacebar').checked;
            settings.disableArrowKeys = document.getElementById('disableArrowKeys').checked;
            settings.disableFKey = document.getElementById('disableFKey').checked;
            settings.disableMKey = document.getElementById('disableMKey').checked;
            settings.disableSpeedControl = document.getElementById('disableSpeedControl').checked;
            settings.disableFrameSkip = document.getElementById('disableFrameSkip').checked;
            GM_setValue('hotkeySettings', settings);

            // Show a success message and close modal after a short delay
            showNotification('Settings saved successfully!', modal);
            setTimeout(closeSettings, 1500);
        });

        // Function to close the settings modal
        function closeSettings() {
            modal.remove();
            overlay.remove();
        }
    }

    // Function to show a notification banner
    function showNotification(message, parentElement) {
        let banner = document.createElement('div');
        banner.className = 'notification-banner';
        banner.textContent = message;
        parentElement.appendChild(banner);

        setTimeout(() => banner.remove(), 3000);
    }

    // Register the settings menu command
    GM_registerMenuCommand('YouTube Hotkey Settings', openSettings);

    // Add styles for the modal and modern UI
    GM_addStyle(`
        /* General Modal Styling */
        .modal-card {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            width: 400px;
            max-width: 90%;
            z-index: 10001;
            overflow: hidden;
            animation: slide-down 0.3s ease-out;
        }

        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 10000;
        }

        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background-color: #007bff;
            color: white;
            padding: 15px;
        }

        .modal-content {
            padding: 20px;
        }

        .modal-footer {
            display: flex;
            justify-content: flex-end;
            padding: 10px;
            border-top: 1px solid #ddd;
        }

        /* Checkbox Styling */
        .custom-checkbox {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
            font-size: 16px;
        }

        .custom-checkbox input[type="checkbox"] {
            display: none;
        }

        .custom-checkbox .checkmark {
            display: inline-block;
            width: 18px;
            height: 18px;
            border: 2px solid #007bff;
            border-radius: 3px;
            margin-right: 10px;
            transition: all 0.2s;
        }

        .custom-checkbox input[type="checkbox"]:checked + .checkmark {
            background-color: #007bff;
            border-color: #007bff;
        }

        /* Button Styling */
        .primary-btn {
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            padding: 10px 20px;
            cursor: pointer;
            transition: background-color 0.2s;
        }

        .primary-btn:hover {
            background-color: #0056b3;
        }

        /* Close Button */
        .close-btn {
            font-size: 24px;
            color: white;
            cursor: pointer;
            padding: 0 10px;
        }

        /* Notification Banner */
        .notification-banner {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            padding: 10px;
            background-color: #28a745;
            color: white;
            text-align: center;
            animation: fade-in-out 3s ease-out;
        }

        /* Animations */
        @keyframes slide-down {
            from { transform: translate(-50%, -60%); opacity: 0; }
            to { transform: translate(-50%, -50%); opacity: 1; }
        }

        @keyframes fade-in-out {
            0%, 100% { opacity: 0; }
            20%, 80% { opacity: 1; }
        }
    `);
})();