Greasy Fork

Greasy Fork is available in English.

DeepSeek 快捷键

为DeepSeek提供快捷键支持 | 支持自定义快捷键

// ==UserScript==
// @name               DeepSeek ShortCuts
// @name:zh-CN         DeepSeek 快捷键
// @name:zh-TW         DeepSeek 快捷鍵
// @description        Keyboard Shortcuts For DeepSeek | Support Custom Shortcut Keys
// @description:zh-CN  为DeepSeek提供快捷键支持 | 支持自定义快捷键
// @description:zh-TW  為DeepSeek提供快捷鍵支援 | 支援自定義快捷鍵
// @version            1.5.0
// @icon               https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DeepSeekShortcutsIcon.svg
// @author             念柚
// @namespace          https://github.com/MiPoNianYou/UserScripts
// @supportURL         https://github.com/MiPoNianYou/UserScripts/issues
// @license            GPL-3.0
// @match              https://chat.deepseek.com/*
// @grant              GM_addStyle
// @grant              GM_setValue
// @grant              GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  const UI_SETTINGS = {
    FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
    ANIMATION_DURATION_MS: 350,
    ANIMATION_EASING_POP_OUT: "cubic-bezier(0.34, 1.56, 0.64, 1)",
    ANIMATION_EASING_STANDARD_INTERACTIVE: "cubic-bezier(0, 0, 0.58, 1)",
    BREATHING_ANIMATION_DURATION: "2.2s",
    DEBOUNCE_DELAY_MS: 150,
  };

  const STORAGE_KEYS = {
    CUSTOM_SHORTCUTS_PREFIX: "dsk_custom_shortcuts_",
  };

  const ELEMENT_SELECTORS = {
    REGENERATE_BUTTON: {
      selector: ".ds-icon-button",
      filterText: "#重新生成",
    },
    CONTINUE_BUTTON: {
      selector: ".ds-button",
      filterText: "继续生成",
    },
    STOP_GENERATING_BUTTON: {
      selector: "._7436101",
      position: "first",
    },
    LAST_COPY_BUTTON: {
      parentSelector: "div._4f9bf79.d7dc56a8",
      parentPosition: "last",
      selector: "._965abe9 .ds-icon-button",
      childPosition: "first",
    },
    LAST_EDIT_BUTTON: {
      parentSelector: "._9663006",
      parentPosition: "last",
      selector: "._78e0558 .ds-icon-button",
      childPosition: "last",
    },
    DEEP_THINK_MODE_BUTTON: {
      selector: ".ds-button span",
      filterText: "深度思考",
    },
    SEARCH_MODE_BUTTON: {
      selector: ".ds-button span",
      filterText: "联网搜索",
    },
    UPLOAD_FILE_BUTTON: {
      selector: ".f02f0e25",
      position: "first",
    },
    NEW_CHAT_BUTTON: {
      selector: "._217e214",
      position: "first",
    },
    TOGGLE_SIDEBAR_BUTTON: {
      selector: ".ds-icon-button",
      filterText: "svg #打开边栏0730, svg #折叠边栏0730",
    },
    CURRENT_CHAT_MENU_BUTTON: {
      parentSelector: "._83421f9.b64fb9ae",
      parentPosition: "last",
      selector: "._2090548",
      childPosition: "first",
    },
  };

  const ELEMENT_IDS = {
    HELP_PANEL: "dsk-help-panel",
    HELP_PANEL_ANIMATE_IN: "dsk-help-panel-animate-in",
    HELP_PANEL_ANIMATE_OUT: "dsk-help-panel-animate-out",
  };

  const CSS_CLASSES = {
    HELP_PANEL_VISIBLE: "dsk-help-panel--visible",
    HELP_PANEL_CLOSE_BUTTON: "dsk-help-panel-close-button",
    HELP_PANEL_TITLE: "dsk-help-panel-title",
    HELP_PANEL_CONTENT: "dsk-help-panel-content",
    HELP_PANEL_ROW: "dsk-help-panel-row",
    HELP_PANEL_KEY: "dsk-help-panel-key",
    HELP_PANEL_KEY_DISPLAY: "dsk-help-panel-key--display",
    HELP_PANEL_KEY_SETTING: "dsk-help-panel-key--setting",
    HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT:
      "dsk-help-panel-key--configurable-highlight",
    HELP_PANEL_KEY_LISTENING: "dsk-help-panel-key--listening",
    HELP_PANEL_KEY_INVALID_SHAKE: "dsk-key-invalid-shake",
    HELP_PANEL_DESCRIPTION: "dsk-help-panel-description",
    HELP_PANEL_WARNING: "dsk-help-panel-warning",
  };

  const UI_STRINGS = {
    HELP_PANEL_TITLE: "快捷按键指北",
    HELP_PANEL_WARNING_TEXT: "⚠️ 脚本依UA自动适配快捷键 篡改UA或致功能异常",
    CUSTOMIZE_SHORTCUTS_LABEL: "自定义快捷键",
    SETTINGS_BUTTON_TEXT: "设置自定按键",
    FINISH_CUSTOMIZING_BUTTON_TEXT: "完成自定设置",
    PRESS_NEW_SHORTCUT_TEXT: "请按下快捷键",
    KEY_CONFLICT_TEXT_PREFIX: "键 「",
    KEY_CONFLICT_TEXT_SUFFIX: "」 已被使用",
    INVALID_MODIFIER_TEXT_PREFIX: "请按 ",
    INVALID_MODIFIER_TEXT_SUFFIX: " + 字母/数字",
  };

  let currentKeybindingConfig = {
    MODIFIERS: (() => {
      const isMac = /Macintosh|Mac OS X/i.test(navigator.userAgent);
      return {
        CHARACTER_DISPLAY: isMac ? "Control" : "Alt",
        EVENT_PROPERTY: isMac ? "ctrlKey" : "altKey",
      };
    })(),
    SHORTCUTS: [
      {
        id: "regenerate",
        key: "R",
        description: "重新生成回答",
        selectorConfig: ELEMENT_SELECTORS.REGENERATE_BUTTON,
      },
      {
        id: "continueGenerating",
        key: "C",
        description: "继续生成回答",
        selectorConfig: ELEMENT_SELECTORS.CONTINUE_BUTTON,
      },
      {
        id: "stopGenerating",
        key: "Q",
        description: "中断当前生成",
        selectorConfig: ELEMENT_SELECTORS.STOP_GENERATING_BUTTON,
      },
      {
        id: "copyLastResponse",
        key: "K",
        description: "复制末条回答",
        selectorConfig: ELEMENT_SELECTORS.LAST_COPY_BUTTON,
      },
      {
        id: "editLastQuery",
        key: "E",
        description: "编辑末次提问",
        selectorConfig: ELEMENT_SELECTORS.LAST_EDIT_BUTTON,
      },
      {
        id: "deepThinkMode",
        key: "D",
        description: "深度思考模式",
        selectorConfig: ELEMENT_SELECTORS.DEEP_THINK_MODE_BUTTON,
      },
      {
        id: "searchMode",
        key: "S",
        description: "联网搜索模式",
        selectorConfig: ELEMENT_SELECTORS.SEARCH_MODE_BUTTON,
      },
      {
        id: "uploadFile",
        key: "U",
        description: "上传本地文件",
        selectorConfig: ELEMENT_SELECTORS.UPLOAD_FILE_BUTTON,
      },
      {
        id: "newChat",
        key: "N",
        description: "新建对话窗口",
        selectorConfig: ELEMENT_SELECTORS.NEW_CHAT_BUTTON,
      },
      {
        id: "toggleSidebar",
        key: "T",
        description: "切换开关边栏",
        selectorConfig: ELEMENT_SELECTORS.TOGGLE_SIDEBAR_BUTTON,
      },
      {
        id: "currentChatMenu",
        key: "I",
        description: "当前对话菜单",
        selectorConfig: ELEMENT_SELECTORS.CURRENT_CHAT_MENU_BUTTON,
      },
      {
        id: "toggleHelpPanel",
        key: "H",
        description: "快捷按键帮助",
        actionIdentifier: "toggleHelpPanel",
        isSpecialAction: true,
      },
      {
        id: "settingsEntry",
        key: null,
        description: UI_STRINGS.CUSTOMIZE_SHORTCUTS_LABEL,
        isSettingsEntry: true,
        actionIdentifier: "toggleCustomizationMode",
        nonConfigurable: true,
      },
    ],
  };

  let helpPanelElement = null;
  let keydownEventListener = null;
  let isCustomizingShortcuts = false;
  let activeCustomizationTarget = null;
  const shortcutDisplaySpansMap = new Map();
  let panelCloseTimer = null;

  function injectUserInterfaceStyles() {
    const styles = `
      :root {
        --ctp-frappe-rosewater: #f2d5cf;
        --ctp-frappe-flamingo: #eebebe;
        --ctp-frappe-pink: #f4b8e4;
        --ctp-frappe-mauve: #ca9ee6;
        --ctp-frappe-red: #e78284;
        --ctp-frappe-maroon: #ea999c;
        --ctp-frappe-peach: #ef9f76;
        --ctp-frappe-yellow: #e5c890;
        --ctp-frappe-green: #a6d189;
        --ctp-frappe-teal: #81c8be;
        --ctp-frappe-sky: #99d1db;
        --ctp-frappe-sapphire: #85c1dc;
        --ctp-frappe-blue: #8caaee;
        --ctp-frappe-lavender: #babbf1;
        --ctp-frappe-text: #c6d0f5;
        --ctp-frappe-subtext1: #b5bfe2;
        --ctp-frappe-subtext0: #a5adce;
        --ctp-frappe-overlay2: #949cbb;
        --ctp-frappe-overlay1: #838ba7;
        --ctp-frappe-overlay0: #737994;
        --ctp-frappe-surface2: #626880;
        --ctp-frappe-surface1: #51576d;
        --ctp-frappe-surface0: #414559;
        --ctp-frappe-base: #303446;
        --ctp-frappe-mantle: #292c3c;
        --ctp-frappe-crust: #232634;
        --ctp-frappe-crust-rgb: 35, 38, 52;

        --dsk-panel-bg: rgba(41, 44, 60, 0.85);
        --dsk-panel-border: rgba(65, 69, 89, 0.5);
        --dsk-panel-shadow:
            0 1px 3px rgba(var(--ctp-frappe-crust-rgb), 0.12),
            0 6px 16px rgba(var(--ctp-frappe-crust-rgb), 0.10),
            0 12px 28px rgba(var(--ctp-frappe-crust-rgb), 0.08);
        --dsk-text-primary: var(--ctp-frappe-text);
        --dsk-text-secondary: var(--ctp-frappe-subtext0);
        --dsk-key-bg: var(--ctp-frappe-surface0);
        --dsk-key-border: var(--ctp-frappe-surface1);
        --dsk-key-setting-text: var(--ctp-frappe-blue);
        --dsk-key-setting-hover-bg: var(--ctp-frappe-surface1);
        --dsk-key-breathing-highlight-color: var(--ctp-frappe-mauve);
        --dsk-key-listening-border: var(--ctp-frappe-green);
        --dsk-key-listening-bg: color-mix(in srgb, var(--dsk-key-bg) 85%, var(--ctp-frappe-green) 15%);
        --dsk-key-invalid-shake-color: var(--ctp-frappe-red);
        --dsk-warning-bg: rgba(65, 69, 89, 0.5);
        --dsk-warning-border: var(--ctp-frappe-surface1);
        --dsk-warning-text: var(--ctp-frappe-yellow);
        --dsk-scrollbar-thumb: var(--ctp-frappe-overlay0);
        --dsk-scrollbar-thumb-hover: var(--ctp-frappe-overlay1);
        --dsk-close-button-bg: var(--ctp-frappe-red);
        --dsk-close-button-hover-bg: color-mix(in srgb, var(--ctp-frappe-red) 80%, var(--ctp-frappe-crust) 20%);
        --dsk-close-button-symbol: rgba(var(--ctp-frappe-crust-rgb), 0.7);
      }

      @keyframes dsk-opacity-breathing-effect {
        0%, 100% {
          opacity: 0;
        }
        50% {
          opacity: 0.25;
        }
      }

      @keyframes dsk-border-breathing-effect {
        0%, 100% {
          border-color: var(--dsk-key-border);
        }
        50% {
          border-color: var(--dsk-key-breathing-highlight-color);
        }
      }

      @keyframes dsk-invalid-shake-effect {
        0%, 100% { transform: translateX(0); }
        10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
        20%, 40%, 60%, 80% { transform: translateX(3px); }
      }

      @keyframes ${ELEMENT_IDS.HELP_PANEL_ANIMATE_IN} {
        0% {
            transform: translate(-50%, -50%) scale(0.88);
            opacity: 0;
        }
        100% {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
        }
      }

      @keyframes ${ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT} {
        0% {
            transform: translate(-50%, -50%) scale(1);
            opacity: 1;
        }
        100% {
            transform: translate(-50%, -50%) scale(0.9);
            opacity: 0;
        }
      }

      #${ELEMENT_IDS.HELP_PANEL} {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) scale(0.88);
        opacity: 0;
        visibility: hidden;
        z-index: 2147483647;
        min-width: 300px;
        max-width: 480px;
        padding: 24px;
        border: 1px solid var(--dsk-panel-border);
        border-radius: 16px;
        background-color: var(--dsk-panel-bg);
        color: var(--dsk-text-primary);
        font-family: ${UI_SETTINGS.FONT_STACK};
        font-size: 14px;
        font-weight: 500;
        line-height: 1.5;
        box-shadow: var(--dsk-panel-shadow);
        backdrop-filter: blur(20px) saturate(180%);
        -webkit-backdrop-filter: blur(20px) saturate(180%);
        display: flex;
        flex-direction: column;
        pointer-events: none;
      }

      #${ELEMENT_IDS.HELP_PANEL}.${CSS_CLASSES.HELP_PANEL_VISIBLE} {
        pointer-events: auto;
      }

      .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON} {
        position: absolute;
        top: 14px;
        left: 14px;
        width: 12px;
        height: 12px;
        padding: 0;
        border: none;
        border-radius: 50%;
        background-color: var(--dsk-close-button-bg);
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
                    transform 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
        appearance: none;
        -webkit-appearance: none;
        outline: none;
      }

      .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}::before {
        content: '✕';
        display: block;
        color: transparent;
        font-size: 10px;
        font-weight: bold;
        line-height: 12px;
        text-align: center;
        transition: color 0.1s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
      }

      .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover {
        background-color: var(--dsk-close-button-hover-bg);
      }

      .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover::before {
        color: var(--dsk-close-button-symbol);
      }

      .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:active {
        filter: brightness(0.85);
        transform: scale(0.9);
      }

      .${CSS_CLASSES.HELP_PANEL_TITLE} {
        margin: 0 0 18px 0;
        padding-top: 8px;
        color: var(--dsk-text-primary);
        font-size: 17px;
        font-weight: 600;
        text-align: center;
        flex-shrink: 0;
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT} {
        flex-grow: 1;
        overflow-y: auto;
        max-height: 60vh;
        margin-right: -12px;
        padding-right: 12px;
        scrollbar-width: thin;
        scrollbar-color: var(--dsk-scrollbar-thumb) transparent;
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar {
        width: 6px;
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-track {
        background: transparent;
        margin: 4px 0;
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb {
        background-color: var(--dsk-scrollbar-thumb);
        border-radius: 3px;
        transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb:hover {
        background-color: var(--dsk-scrollbar-thumb-hover);
      }

      .${CSS_CLASSES.HELP_PANEL_ROW} {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 12px;
        padding: 6px 2px;
      }

      .${CSS_CLASSES.HELP_PANEL_CONTENT} > .${CSS_CLASSES.HELP_PANEL_ROW}:last-child {
        margin-bottom: 0;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY} {
        min-width: 95px;
        padding: 5px 10px;
        margin-left: 18px;
        background-color: var(--dsk-key-bg);
        border: 1px solid var(--dsk-key-border);
        border-radius: 6px;
        box-shadow: 0 1px 1px rgba(0,0,0,0.08), inset 0 1px 1px rgba(255,255,255,0.03);
        color: var(--dsk-text-primary);
        font-family: inherit;
        font-size: 13px;
        font-weight: 500;
        text-align: center;
        flex-shrink: 0;
        transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
                    border-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
                    color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
        cursor: default;
        position: relative;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_DISPLAY} {
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_SETTING} {
        color: var(--dsk-key-setting-text);
        cursor: pointer;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}:hover {
        background-color: var(--dsk-key-setting-hover-bg);
        border-color: var(--ctp-frappe-overlay0);
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT} {
        animation: dsk-border-breathing-effect ${UI_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
        cursor: pointer;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT}::after {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        border-radius: inherit;
        background-color: var(--dsk-key-breathing-highlight-color);
        opacity: 0;
        z-index: 0;
        pointer-events: none;
        animation: dsk-opacity-breathing-effect ${UI_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_LISTENING} {
        border-color: var(--dsk-key-listening-border) !important;
        background-color: var(--dsk-key-listening-bg) !important;
        color: var(--ctp-frappe-green) !important;
        animation: none !important;
        cursor: default !important;
      }
      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_LISTENING}::after {
        animation: none !important;
        opacity: 0 !important;
      }

      .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE} {
         animation: dsk-invalid-shake-effect 0.5s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_DEFAULT};
         border-color: var(--dsk-key-invalid-shake-color) !important;
         color: var(--dsk-key-invalid-shake-color) !important;
      }


      .${CSS_CLASSES.HELP_PANEL_DESCRIPTION} {
        flex-grow: 1;
        padding-right: 10px;
        color: var(--dsk-text-secondary);
        font-size: 13.5px;
      }

      .${CSS_CLASSES.HELP_PANEL_WARNING} {
        margin-top: 20px;
        padding: 12px 16px;
        background-color: var(--dsk-warning-bg);
        border: 1px solid var(--dsk-warning-border);
        border-radius: 10px;
        color: var(--dsk-warning-text);
        font-size: 12.5px;
        font-weight: 500;
        line-height: 1.45;
        text-align: center;
        flex-shrink: 0;
      }
    `;
    try {
      GM_addStyle(styles);
    } catch (e) {
      const styleElement = document.createElement("style");
      styleElement.textContent = styles;
      (document.head || document.documentElement).appendChild(styleElement);
    }
  }

  function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
      const later = () => {
        clearTimeout(timeout);
        func.apply(this, args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  }

  function getElementByConfig(config) {
    if (!config || !config.selector) return null;
    const {
      selector,
      filterText,
      position = "first",
      parentSelector,
      parentPosition = "last",
      childPosition = "first",
    } = config;

    let targetElements = [];

    if (parentSelector) {
      const parents = Array.from(document.querySelectorAll(parentSelector));
      if (parents.length === 0) return null;
      const parentIndex = parentPosition === "last" ? parents.length - 1 : 0;
      const targetParent = parents[parentIndex];
      if (!targetParent) return null;
      targetElements = Array.from(targetParent.querySelectorAll(selector));
    } else {
      targetElements = Array.from(document.querySelectorAll(selector));
    }

    if (targetElements.length === 0) return null;

    if (filterText) {
      const filters = filterText.split(",").map((f) => f.trim());
      const foundElement = targetElements.find((element) =>
        filters.some(
          (ft) =>
            element.textContent?.includes(ft) ||
            (ft.startsWith("svg #") &&
              element.querySelector(ft.replace("svg ", "")))
        )
      );
      return foundElement || null;
    } else {
      const index =
        position === "last"
          ? targetElements.length - 1
          : childPosition === "last"
          ? targetElements.length - 1
          : 0;
      return targetElements[index] || null;
    }
  }

  function triggerElementClick(elementConfig) {
    const element = getElementByConfig(elementConfig);
    if (element && typeof element.click === "function") {
      element.click();
      return true;
    }
    return false;
  }

  function formatShortcutForDisplay(shortcutKey) {
    if (!shortcutKey) return "---";
    return `${
      currentKeybindingConfig.MODIFIERS.CHARACTER_DISPLAY
    } + ${shortcutKey.toUpperCase()}`;
  }

  function updateShortcutDisplay(shortcutId, newKey) {
    const spanElement = shortcutDisplaySpansMap.get(shortcutId);
    if (spanElement) {
      spanElement.textContent = newKey
        ? formatShortcutForDisplay(newKey)
        : UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
    }
  }

  function saveCustomShortcut(shortcutId, newKey) {
    const shortcutToUpdate = currentKeybindingConfig.SHORTCUTS.find(
      (s) => s.id === shortcutId
    );
    if (shortcutToUpdate) {
      shortcutToUpdate.key = newKey.toUpperCase();
      try {
        GM_setValue(
          `${STORAGE_KEYS.CUSTOM_SHORTCUTS_PREFIX}${shortcutId}`,
          shortcutToUpdate.key
        );
      } catch (e) {}

      if (keydownEventListener) {
        window.removeEventListener("keydown", keydownEventListener, true);
      }
      keydownEventListener = createKeyboardEventHandler();
      window.addEventListener("keydown", keydownEventListener, true);
    }
  }

  function loadCustomShortcuts() {
    currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
      if (shortcut.nonConfigurable || shortcut.isSettingsEntry) return;
      try {
        const savedKey = GM_getValue(
          `${STORAGE_KEYS.CUSTOM_SHORTCUTS_PREFIX}${shortcut.id}`,
          shortcut.key
        );
        if (
          savedKey &&
          typeof savedKey === "string" &&
          savedKey.match(/^[a-zA-Z0-9]$/i)
        ) {
          shortcut.key = savedKey.toUpperCase();
        }
      } catch (e) {}
    });
  }

  function setListeningState(shortcutId, spanElement, isListening) {
    if (isListening) {
      if (
        activeCustomizationTarget &&
        activeCustomizationTarget.spanElement !== spanElement
      ) {
        const prevShortcut = currentKeybindingConfig.SHORTCUTS.find(
          (s) => s.id === activeCustomizationTarget.shortcutId
        );
        activeCustomizationTarget.spanElement.textContent =
          formatShortcutForDisplay(prevShortcut?.key);
        activeCustomizationTarget.spanElement.classList.remove(
          CSS_CLASSES.HELP_PANEL_KEY_LISTENING
        );
        activeCustomizationTarget.spanElement.classList.remove(
          CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
        );
        if (isCustomizingShortcuts) {
          activeCustomizationTarget.spanElement.classList.add(
            CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
          );
        }
      }

      activeCustomizationTarget = { shortcutId, spanElement };
      spanElement.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
      shortcutDisplaySpansMap.forEach((s) => {
        s.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
      });
      spanElement.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
      spanElement.classList.remove(
        CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
      );
    } else {
      if (
        activeCustomizationTarget &&
        activeCustomizationTarget.shortcutId === shortcutId
      ) {
        activeCustomizationTarget = null;
      }
      spanElement.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
      const currentKey = currentKeybindingConfig.SHORTCUTS.find(
        (s) => s.id === shortcutId
      )?.key;
      spanElement.textContent = formatShortcutForDisplay(currentKey);

      if (isCustomizingShortcuts) {
        shortcutDisplaySpansMap.forEach((s, id) => {
          const cfg = currentKeybindingConfig.SHORTCUTS.find(
            (sc) => sc.id === id
          );
          if (cfg && !cfg.nonConfigurable && !cfg.isSettingsEntry) {
            s.classList.add(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
          }
        });
      }
    }
  }

  function toggleCustomizationMode(settingsButtonSpan) {
    isCustomizingShortcuts = !isCustomizingShortcuts;

    if (activeCustomizationTarget) {
      setListeningState(
        activeCustomizationTarget.shortcutId,
        activeCustomizationTarget.spanElement,
        false
      );
    }

    if (isCustomizingShortcuts) {
      settingsButtonSpan.textContent =
        UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT;
      shortcutDisplaySpansMap.forEach((span, id) => {
        const shortcut = currentKeybindingConfig.SHORTCUTS.find(
          (s) => s.id === id
        );
        if (
          shortcut &&
          !shortcut.nonConfigurable &&
          !shortcut.isSettingsEntry
        ) {
          span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
        }
      });
    } else {
      settingsButtonSpan.textContent = UI_STRINGS.SETTINGS_BUTTON_TEXT;
      shortcutDisplaySpansMap.forEach((span) => {
        span.classList.remove(
          CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
        );
        span.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
      });
    }
  }

  function createHelpPanelElement() {
    if (helpPanelElement && document.body.contains(helpPanelElement)) {
      const settingsButton = helpPanelElement.querySelector(
        `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
      );
      if (settingsButton) {
        settingsButton.textContent = isCustomizingShortcuts
          ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
          : UI_STRINGS.SETTINGS_BUTTON_TEXT;
      }
      shortcutDisplaySpansMap.forEach((span, id) => {
        const shortcut = currentKeybindingConfig.SHORTCUTS.find(
          (s) => s.id === id
        );
        if (shortcut) {
          span.textContent = formatShortcutForDisplay(shortcut.key);
          span.classList.remove(
            CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT,
            CSS_CLASSES.HELP_PANEL_KEY_LISTENING
          );
          if (
            isCustomizingShortcuts &&
            !shortcut.isSettingsEntry &&
            !shortcut.nonConfigurable
          ) {
            if (
              activeCustomizationTarget &&
              activeCustomizationTarget.shortcutId === id
            ) {
              span.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
              span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
            } else {
              span.classList.add(
                CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
              );
            }
          }
        }
      });
      return helpPanelElement;
    }

    shortcutDisplaySpansMap.clear();

    const panel = document.createElement("div");
    panel.id = ELEMENT_IDS.HELP_PANEL;

    const closeButton = document.createElement("button");
    closeButton.className = CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON;
    closeButton.setAttribute("aria-label", "Close help panel");
    closeButton.addEventListener("click", (event) => {
      event.stopPropagation();
      if (isCustomizingShortcuts) {
        const settingsBtnInPanel = panel.querySelector(
          `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
        );
        if (settingsBtnInPanel) toggleCustomizationMode(settingsBtnInPanel);
      }
      closeHelpPanel();
    });

    const titleElement = document.createElement("h3");
    titleElement.className = CSS_CLASSES.HELP_PANEL_TITLE;
    titleElement.textContent = UI_STRINGS.HELP_PANEL_TITLE;

    const contentContainer = document.createElement("div");
    contentContainer.className = CSS_CLASSES.HELP_PANEL_CONTENT;

    currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
      const row = document.createElement("div");
      row.className = CSS_CLASSES.HELP_PANEL_ROW;

      const descriptionSpan = document.createElement("span");
      descriptionSpan.className = CSS_CLASSES.HELP_PANEL_DESCRIPTION;
      descriptionSpan.textContent = shortcut.description;

      const keySpan = document.createElement("span");
      keySpan.className = CSS_CLASSES.HELP_PANEL_KEY;

      if (shortcut.isSettingsEntry) {
        keySpan.textContent = isCustomizingShortcuts
          ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
          : UI_STRINGS.SETTINGS_BUTTON_TEXT;
        keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_SETTING);
        keySpan.addEventListener("click", () => {
          toggleCustomizationMode(keySpan);
        });
      } else {
        keySpan.textContent = formatShortcutForDisplay(shortcut.key);
        keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_DISPLAY);
        shortcutDisplaySpansMap.set(shortcut.id, keySpan);

        if (!shortcut.nonConfigurable) {
          keySpan.addEventListener("click", () => {
            if (
              isCustomizingShortcuts &&
              (!activeCustomizationTarget ||
                activeCustomizationTarget.shortcutId !== shortcut.id)
            ) {
              setListeningState(shortcut.id, keySpan, true);
            } else if (
              isCustomizingShortcuts &&
              activeCustomizationTarget &&
              activeCustomizationTarget.shortcutId === shortcut.id
            ) {
              setListeningState(shortcut.id, keySpan, false);
            }
          });
        }

        if (isCustomizingShortcuts && !shortcut.nonConfigurable) {
          if (
            activeCustomizationTarget &&
            activeCustomizationTarget.shortcutId === shortcut.id
          ) {
            keySpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
            keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
          } else {
            keySpan.classList.add(
              CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
            );
          }
        }
      }

      row.appendChild(descriptionSpan);
      row.appendChild(keySpan);
      contentContainer.appendChild(row);
    });

    const warningElement = document.createElement("div");
    warningElement.className = CSS_CLASSES.HELP_PANEL_WARNING;
    warningElement.textContent = UI_STRINGS.HELP_PANEL_WARNING_TEXT;

    panel.appendChild(closeButton);
    panel.appendChild(titleElement);
    panel.appendChild(contentContainer);
    panel.appendChild(warningElement);

    helpPanelElement = panel;
    document.body.appendChild(panel);
    return panel;
  }

  function closeHelpPanel() {
    if (
      helpPanelElement &&
      helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE)
    ) {
      helpPanelElement.style.animation = `${ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT} ${UI_SETTINGS.ANIMATION_DURATION_MS}ms ${UI_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;

      clearTimeout(panelCloseTimer);
      panelCloseTimer = setTimeout(() => {
        if (helpPanelElement) {
          helpPanelElement.classList.remove(CSS_CLASSES.HELP_PANEL_VISIBLE);
          helpPanelElement.style.animation = "";
          helpPanelElement.style.opacity = "0";
          helpPanelElement.style.visibility = "hidden";
        }
        window.removeEventListener(
          "click",
          handlePanelInteractionOutside,
          true
        );
        window.removeEventListener("keydown", handlePanelEscapeKey, true);
      }, UI_SETTINGS.ANIMATION_DURATION_MS);
    }
  }

  function handlePanelInteractionOutside(event) {
    if (
      helpPanelElement &&
      helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE) &&
      !helpPanelElement.contains(event.target) &&
      parseFloat(getComputedStyle(helpPanelElement).opacity) === 1
    ) {
      if (isCustomizingShortcuts) {
        const settingsButton = helpPanelElement.querySelector(
          `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
        );
        if (settingsButton) toggleCustomizationMode(settingsButton);
      }
      closeHelpPanel();
    }
  }

  function handlePanelEscapeKey(event) {
    if (event.key === "Escape") {
      if (
        helpPanelElement &&
        helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE) &&
        parseFloat(getComputedStyle(helpPanelElement).opacity) === 1
      ) {
        event.preventDefault();
        event.stopPropagation();
        if (activeCustomizationTarget) {
          setListeningState(
            activeCustomizationTarget.shortcutId,
            activeCustomizationTarget.spanElement,
            false
          );
        } else if (isCustomizingShortcuts) {
          const settingsButton = helpPanelElement.querySelector(
            `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
          );
          if (settingsButton) toggleCustomizationMode(settingsButton);
        }
        closeHelpPanel();
      }
    }
  }

  function toggleHelpPanelVisibility() {
    if (!helpPanelElement || !document.body.contains(helpPanelElement)) {
      helpPanelElement = createHelpPanelElement();
    } else {
      const settingsButton = helpPanelElement.querySelector(
        `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
      );
      if (settingsButton) {
        settingsButton.textContent = isCustomizingShortcuts
          ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
          : UI_STRINGS.SETTINGS_BUTTON_TEXT;
      }
      shortcutDisplaySpansMap.forEach((span, id) => {
        const shortcut = currentKeybindingConfig.SHORTCUTS.find(
          (s) => s.id === id
        );
        if (shortcut) {
          span.textContent = formatShortcutForDisplay(shortcut.key);
          span.classList.remove(
            CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT,
            CSS_CLASSES.HELP_PANEL_KEY_LISTENING
          );
          if (
            isCustomizingShortcuts &&
            !shortcut.isSettingsEntry &&
            !shortcut.nonConfigurable
          ) {
            if (
              activeCustomizationTarget &&
              activeCustomizationTarget.shortcutId === id
            ) {
              span.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
              span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
            } else {
              span.classList.add(
                CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
              );
            }
          }
        }
      });
    }

    clearTimeout(panelCloseTimer);
    const isCurrentlyVisibleByClass = helpPanelElement.classList.contains(
      CSS_CLASSES.HELP_PANEL_VISIBLE
    );
    let currentOpacity = 0;
    if (
      isCurrentlyVisibleByClass ||
      helpPanelElement.style.animationName ===
        ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT
    ) {
      currentOpacity = parseFloat(getComputedStyle(helpPanelElement).opacity);
    }
    const isAnimatingOut =
      helpPanelElement.style.animationName ===
      ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT;

    if (isCurrentlyVisibleByClass && currentOpacity > 0.01 && !isAnimatingOut) {
      if (isCustomizingShortcuts && !activeCustomizationTarget) {
        const settingsButton = helpPanelElement.querySelector(
          `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
        );
        if (settingsButton) toggleCustomizationMode(settingsButton);
      }
      closeHelpPanel();
    } else {
      helpPanelElement.style.animation = "";
      helpPanelElement.style.opacity = "0";
      helpPanelElement.style.visibility = "hidden";

      requestAnimationFrame(() => {
        helpPanelElement.classList.add(CSS_CLASSES.HELP_PANEL_VISIBLE);
        helpPanelElement.style.visibility = "visible";
        helpPanelElement.style.animation = `${ELEMENT_IDS.HELP_PANEL_ANIMATE_IN} ${UI_SETTINGS.ANIMATION_DURATION_MS}ms ${UI_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;
      });

      setTimeout(() => {
        window.addEventListener("click", handlePanelInteractionOutside, true);
        window.addEventListener("keydown", handlePanelEscapeKey, true);
      }, 0);
    }
  }

  const debouncedToggleHelpPanelVisibility = debounce(
    toggleHelpPanelVisibility,
    UI_SETTINGS.DEBOUNCE_DELAY_MS
  );

  function createKeyboardEventHandler() {
    const specialActionHandlers = {
      toggleHelpPanel: debouncedToggleHelpPanelVisibility,
      toggleCustomizationMode: () => {
        if (helpPanelElement) {
          const settingsButton = helpPanelElement.querySelector(
            `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
          );
          if (settingsButton) toggleCustomizationMode(settingsButton);
        }
      },
    };

    const shortcutActionMap = {};
    currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
      if (shortcut.key && !shortcut.isSettingsEntry) {
        const lowerKey = shortcut.key.toLowerCase();
        if (
          shortcut.actionIdentifier &&
          specialActionHandlers[shortcut.actionIdentifier]
        ) {
          shortcutActionMap[lowerKey] =
            specialActionHandlers[shortcut.actionIdentifier];
        } else if (shortcut.selectorConfig) {
          shortcutActionMap[lowerKey] = () =>
            triggerElementClick(shortcut.selectorConfig);
        }
      }
    });

    return function handleKeyDown(event) {
      if (event.key === "Escape") {
        return;
      }

      if (activeCustomizationTarget) {
        event.preventDefault();
        event.stopPropagation();

        const newKey = event.key;
        const targetSpan = activeCustomizationTarget.spanElement;

        if (
          newKey &&
          newKey.length === 1 &&
          !event.ctrlKey &&
          !event.altKey &&
          !event.shiftKey &&
          !event.metaKey &&
          !["Control", "Alt", "Shift", "Meta"].includes(newKey)
        ) {
          if (newKey.match(/^[a-zA-Z0-9]$/i)) {
            const conflictingShortcut = currentKeybindingConfig.SHORTCUTS.find(
              (s) =>
                s.key &&
                s.key.toLowerCase() === newKey.toLowerCase() &&
                s.id !== activeCustomizationTarget.shortcutId
            );
            if (conflictingShortcut) {
              targetSpan.textContent = `${
                UI_STRINGS.KEY_CONFLICT_TEXT_PREFIX
              }${newKey.toUpperCase()}${UI_STRINGS.KEY_CONFLICT_TEXT_SUFFIX}`;
              setTimeout(() => {
                if (
                  activeCustomizationTarget &&
                  activeCustomizationTarget.spanElement === targetSpan
                ) {
                  targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
                }
              }, 2000);
              return;
            }
            saveCustomShortcut(activeCustomizationTarget.shortcutId, newKey);
            updateShortcutDisplay(activeCustomizationTarget.shortcutId, newKey);
            setListeningState(
              activeCustomizationTarget.shortcutId,
              targetSpan,
              false
            );
          } else {
            targetSpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE);
            setTimeout(() => {
              if (
                activeCustomizationTarget &&
                activeCustomizationTarget.spanElement === targetSpan
              ) {
                targetSpan.classList.remove(
                  CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE
                );
              }
            }, 500);
          }
        } else if (!["Control", "Alt", "Shift", "Meta"].includes(newKey)) {
          if (event[currentKeybindingConfig.MODIFIERS.EVENT_PROPERTY]) {
            const conflictingShortcut = currentKeybindingConfig.SHORTCUTS.find(
              (s) =>
                s.key &&
                s.key.toLowerCase() === newKey.toLowerCase() &&
                s.id !== activeCustomizationTarget.shortcutId
            );
            if (conflictingShortcut) {
              targetSpan.textContent = `${
                UI_STRINGS.KEY_CONFLICT_TEXT_PREFIX
              }${newKey.toUpperCase()}${UI_STRINGS.KEY_CONFLICT_TEXT_SUFFIX}`;
              setTimeout(() => {
                if (
                  activeCustomizationTarget &&
                  activeCustomizationTarget.spanElement === targetSpan
                ) {
                  targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
                }
              }, 2000);
              return;
            }
            saveCustomShortcut(activeCustomizationTarget.shortcutId, newKey);
            updateShortcutDisplay(activeCustomizationTarget.shortcutId, newKey);
            setListeningState(
              activeCustomizationTarget.shortcutId,
              targetSpan,
              false
            );
          } else {
            targetSpan.textContent = `${UI_STRINGS.INVALID_MODIFIER_TEXT_PREFIX}${currentKeybindingConfig.MODIFIERS.CHARACTER_DISPLAY}${UI_STRINGS.INVALID_MODIFIER_TEXT_SUFFIX}`;
            setTimeout(() => {
              if (
                activeCustomizationTarget &&
                activeCustomizationTarget.spanElement === targetSpan
              ) {
                targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
              }
            }, 2000);
          }
        }
        return;
      }

      if (isCustomizingShortcuts) {
        return;
      }

      if (!event[currentKeybindingConfig.MODIFIERS.EVENT_PROPERTY]) {
        return;
      }

      const pressedKey = event.key.toLowerCase();
      const actionToExecute = shortcutActionMap[pressedKey];

      if (typeof actionToExecute === "function") {
        actionToExecute();
        event.preventDefault();
        event.stopPropagation();
      }
    };
  }

  function initializeScript() {
    loadCustomShortcuts();
    injectUserInterfaceStyles();
    keydownEventListener = createKeyboardEventHandler();
    window.addEventListener("keydown", keydownEventListener, true);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initializeScript, {
      once: true,
    });
  } else {
    initializeScript();
  }
})();