您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为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.6.0
- // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/main/Icons/DeepSeek-Shortcuts-Icon.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 Config = {
- SCRIPT_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,
- },
- STORAGE_KEYS: {
- CUSTOM_SHORTCUTS_PREFIX: "dsk_custom_shortcuts_",
- },
- ELEMENT_SELECTORS: {
- REGENERATE_BUTTON: {
- parentSelector: "div._4f9bf79.d7dc56a8",
- parentPosition: "last",
- 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",
- },
- },
- 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",
- },
- 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",
- },
- 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: " + 字母/数字",
- },
- DEFAULT_SHORTCUT_CONFIG: {
- 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: "REGENERATE_BUTTON",
- },
- {
- id: "continueGenerating",
- key: "C",
- description: "继续生成回答",
- selectorConfig: "CONTINUE_BUTTON",
- },
- {
- id: "stopGenerating",
- key: "Q",
- description: "中断当前生成",
- selectorConfig: "STOP_GENERATING_BUTTON",
- },
- {
- id: "copyLastResponse",
- key: "K",
- description: "复制末条回答",
- selectorConfig: "LAST_COPY_BUTTON",
- },
- {
- id: "editLastQuery",
- key: "E",
- description: "编辑末次提问",
- selectorConfig: "LAST_EDIT_BUTTON",
- },
- {
- id: "deepThinkMode",
- key: "D",
- description: "深度思考模式",
- selectorConfig: "DEEP_THINK_MODE_BUTTON",
- },
- {
- id: "searchMode",
- key: "S",
- description: "联网搜索模式",
- selectorConfig: "SEARCH_MODE_BUTTON",
- },
- {
- id: "uploadFile",
- key: "U",
- description: "上传本地文件",
- selectorConfig: "UPLOAD_FILE_BUTTON",
- },
- {
- id: "newChat",
- key: "N",
- description: "新建对话窗口",
- selectorConfig: "NEW_CHAT_BUTTON",
- },
- {
- id: "toggleSidebar",
- key: "T",
- description: "切换开关边栏",
- selectorConfig: "TOGGLE_SIDEBAR_BUTTON",
- },
- {
- id: "currentChatMenu",
- key: "I",
- description: "当前对话菜单",
- selectorConfig: "CURRENT_CHAT_MENU_BUTTON",
- },
- {
- id: "toggleHelpPanel",
- key: "H",
- description: "快捷按键帮助",
- actionIdentifier: "toggleHelpPanel",
- isSpecialAction: true,
- },
- {
- id: "settingsEntry",
- key: null,
- description: "placeholder_settings_label",
- isSettingsEntry: true,
- actionIdentifier: "toggleCustomizationMode",
- nonConfigurable: true,
- },
- ],
- },
- };
- Config.DEFAULT_SHORTCUT_CONFIG.SHORTCUTS.find(
- (s) => s.id === "settingsEntry"
- ).description = Config.UI_STRINGS.CUSTOMIZE_SHORTCUTS_LABEL;
- const State = {
- isCustomizingShortcuts: false,
- activeCustomizationTarget: null,
- currentShortcutConfig: JSON.parse(
- JSON.stringify(Config.DEFAULT_SHORTCUT_CONFIG)
- ),
- initialize() {
- this.loadCustomShortcuts();
- },
- loadCustomShortcuts() {
- this.currentShortcutConfig.SHORTCUTS.forEach((shortcut) => {
- if (shortcut.nonConfigurable || shortcut.isSettingsEntry) return;
- try {
- const savedKey = GM_getValue(
- `${Config.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) {}
- });
- },
- saveCustomShortcut(shortcutId, newKey) {
- const shortcutToUpdate = this.currentShortcutConfig.SHORTCUTS.find(
- (s) => s.id === shortcutId
- );
- if (shortcutToUpdate) {
- shortcutToUpdate.key = newKey.toUpperCase();
- try {
- GM_setValue(
- `${Config.STORAGE_KEYS.CUSTOM_SHORTCUTS_PREFIX}${shortcutId}`,
- shortcutToUpdate.key
- );
- } catch (e) {}
- EventManager.reinitializeGlobalKeyListener();
- }
- },
- getActiveModifierProperty() {
- return this.currentShortcutConfig.MODIFIERS.EVENT_PROPERTY;
- },
- getActiveModifierCharDisplay() {
- return this.currentShortcutConfig.MODIFIERS.CHARACTER_DISPLAY;
- },
- };
- const UserInterface = {
- injectStyles() {
- const styles = `
- :root {
- --ctp-frappe-rosewater: rgb(242, 213, 207);
- --ctp-frappe-flamingo: rgb(238, 190, 190);
- --ctp-frappe-pink: rgb(244, 184, 228);
- --ctp-frappe-mauve: rgb(202, 158, 230);
- --ctp-frappe-red: rgb(231, 130, 132);
- --ctp-frappe-maroon: rgb(234, 153, 156);
- --ctp-frappe-peach: rgb(239, 159, 118);
- --ctp-frappe-yellow: rgb(229, 200, 144);
- --ctp-frappe-green: rgb(166, 209, 137);
- --ctp-frappe-teal: rgb(129, 200, 190);
- --ctp-frappe-sky: rgb(153, 209, 219);
- --ctp-frappe-sapphire: rgb(133, 193, 220);
- --ctp-frappe-blue: rgb(140, 170, 238);
- --ctp-frappe-lavender: rgb(186, 187, 241);
- --ctp-frappe-text: rgb(198, 208, 245);
- --ctp-frappe-subtext1: rgb(181, 191, 226);
- --ctp-frappe-subtext0: rgb(165, 173, 206);
- --ctp-frappe-overlay2: rgb(148, 156, 187);
- --ctp-frappe-overlay1: rgb(131, 139, 167);
- --ctp-frappe-overlay0: rgb(115, 121, 148);
- --ctp-frappe-surface2: rgb(98, 104, 128);
- --ctp-frappe-surface1: rgb(81, 87, 109);
- --ctp-frappe-surface0: rgb(65, 69, 89);
- --ctp-frappe-base: rgb(48, 52, 70);
- --ctp-frappe-mantle: rgb(41, 44, 60);
- --ctp-frappe-crust: rgb(35, 38, 52);
- --ctp-frappe-crust-rgb: 35, 38, 52;
- --dsk-panel-bg: rgb(from var(--ctp-frappe-mantle) r g b / 0.85);
- --dsk-panel-border: rgb(from var(--ctp-frappe-surface0) r g b / 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: rgb(from var(--ctp-frappe-surface0) r g b / 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 ${Config.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 ${Config.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;
- }
- }
- #${Config.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: ${Config.SCRIPT_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;
- }
- #${Config.ELEMENT_IDS.HELP_PANEL}.${Config.CSS_CLASSES.HELP_PANEL_VISIBLE} {
- pointer-events: auto;
- }
- .${Config.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 ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
- transform 0.15s ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
- appearance: none;
- -webkit-appearance: none;
- outline: none;
- }
- .${Config.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 ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover {
- background-color: var(--dsk-close-button-hover-bg);
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover::before {
- color: var(--dsk-close-button-symbol);
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:active {
- filter: brightness(0.85);
- transform: scale(0.9);
- }
- .${Config.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;
- }
- .${Config.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;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar {
- width: 6px;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-track {
- background: transparent;
- margin: 4px 0;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb {
- background-color: var(--dsk-scrollbar-thumb);
- border-radius: 3px;
- transition: background-color 0.15s ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb:hover {
- background-color: var(--dsk-scrollbar-thumb-hover);
- }
- .${Config.CSS_CLASSES.HELP_PANEL_ROW} {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- padding: 6px 2px;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_CONTENT} > .${Config.CSS_CLASSES.HELP_PANEL_ROW}:last-child {
- margin-bottom: 0;
- }
- .${Config.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 ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
- border-color 0.15s ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
- color 0.15s ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
- cursor: default;
- position: relative;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_DISPLAY} {
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING} {
- color: var(--dsk-key-setting-text);
- cursor: pointer;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}:hover {
- background-color: var(--dsk-key-setting-hover-bg);
- border-color: var(--ctp-frappe-overlay0);
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT} {
- animation: dsk-border-breathing-effect ${Config.SCRIPT_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
- cursor: pointer;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.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 ${Config.SCRIPT_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.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;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_LISTENING}::after {
- animation: none !important;
- opacity: 0 !important;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_KEY}.${Config.CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE} {
- animation: dsk-invalid-shake-effect 0.5s ease;
- border-color: var(--dsk-key-invalid-shake-color) !important;
- color: var(--dsk-key-invalid-shake-color) !important;
- }
- .${Config.CSS_CLASSES.HELP_PANEL_DESCRIPTION} {
- flex-grow: 1;
- padding-right: 10px;
- color: var(--dsk-text-secondary);
- font-size: 13.5px;
- }
- .${Config.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);
- }
- },
- };
- const ShortcutManager = {
- getElementByConfig(selectorKeyName) {
- const config = Config.ELEMENT_SELECTORS[selectorKeyName];
- if (!config || !config.selector) return null;
- const {
- selector,
- filterText,
- position = "first",
- parentSelector,
- parentPosition = "last",
- childPosition = "first",
- } = config;
- let baseElements = [];
- 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;
- baseElements = Array.from(targetParent.querySelectorAll(selector));
- } else {
- baseElements = Array.from(document.querySelectorAll(selector));
- }
- if (baseElements.length === 0) return null;
- let targetElements = baseElements;
- if (filterText) {
- const filters = filterText.split(",").map((f) => f.trim());
- targetElements = baseElements.filter((element) =>
- filters.some((ft) => {
- if (ft.startsWith("#")) {
- return (
- !!element.querySelector(ft) || element.id === ft.substring(1)
- );
- }
- return (
- element.textContent?.includes(ft) ||
- (ft.startsWith("svg #") &&
- element.querySelector(ft.replace("svg ", "")))
- );
- })
- );
- if (targetElements.length === 0) return null;
- }
- const index = position === "last" ? targetElements.length - 1 : 0;
- return targetElements[index] || null;
- },
- triggerElementClick(selectorKeyName) {
- const element = this.getElementByConfig(selectorKeyName);
- if (element && typeof element.click === "function") {
- element.click();
- return true;
- }
- return false;
- },
- formatShortcutDisplay(shortcutKey) {
- if (!shortcutKey) return "---";
- return `${State.getActiveModifierCharDisplay()} + ${shortcutKey.toUpperCase()}`;
- },
- };
- const HelpPanelManager = {
- helpPanelElement: null,
- shortcutDisplaySpansMap: new Map(),
- panelCloseTimer: null,
- createPanel() {
- if (
- this.helpPanelElement &&
- document.body.contains(this.helpPanelElement)
- ) {
- this.updatePanelContentDynamic();
- return this.helpPanelElement;
- }
- this.shortcutDisplaySpansMap.clear();
- const panel = document.createElement("div");
- panel.id = Config.ELEMENT_IDS.HELP_PANEL;
- const closeButton = this.createCloseButton();
- const titleElement = this.createTitle();
- const contentContainer = this.createContentContainer();
- const warningElement = this.createWarning();
- panel.appendChild(closeButton);
- panel.appendChild(titleElement);
- panel.appendChild(contentContainer);
- panel.appendChild(warningElement);
- this.helpPanelElement = panel;
- document.body.appendChild(panel);
- return panel;
- },
- createCloseButton() {
- const button = document.createElement("button");
- button.className = Config.CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON;
- button.setAttribute("aria-label", "Close help panel");
- button.addEventListener("click", (event) => {
- event.stopPropagation();
- if (State.isCustomizingShortcuts) {
- const settingsBtn = this.helpPanelElement?.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsBtn) this.toggleCustomizationModeUI(settingsBtn);
- }
- this.closePanel();
- });
- return button;
- },
- createTitle() {
- const title = document.createElement("h3");
- title.className = Config.CSS_CLASSES.HELP_PANEL_TITLE;
- title.textContent = Config.UI_STRINGS.HELP_PANEL_TITLE;
- return title;
- },
- createContentContainer() {
- const container = document.createElement("div");
- container.className = Config.CSS_CLASSES.HELP_PANEL_CONTENT;
- State.currentShortcutConfig.SHORTCUTS.forEach((shortcut) => {
- const row = document.createElement("div");
- row.className = Config.CSS_CLASSES.HELP_PANEL_ROW;
- const descriptionSpan = document.createElement("span");
- descriptionSpan.className = Config.CSS_CLASSES.HELP_PANEL_DESCRIPTION;
- descriptionSpan.textContent = shortcut.description;
- const keySpan = document.createElement("span");
- keySpan.className = Config.CSS_CLASSES.HELP_PANEL_KEY;
- if (shortcut.isSettingsEntry) {
- keySpan.textContent = State.isCustomizingShortcuts
- ? Config.UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
- : Config.UI_STRINGS.SETTINGS_BUTTON_TEXT;
- keySpan.classList.add(Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING);
- keySpan.addEventListener("click", () =>
- this.toggleCustomizationModeUI(keySpan)
- );
- } else {
- keySpan.textContent = ShortcutManager.formatShortcutDisplay(
- shortcut.key
- );
- keySpan.classList.add(Config.CSS_CLASSES.HELP_PANEL_KEY_DISPLAY);
- this.shortcutDisplaySpansMap.set(shortcut.id, keySpan);
- if (!shortcut.nonConfigurable) {
- keySpan.addEventListener("click", () => {
- if (
- State.isCustomizingShortcuts &&
- (!State.activeCustomizationTarget ||
- State.activeCustomizationTarget.shortcutId !== shortcut.id)
- ) {
- this.setListeningStateUI(shortcut.id, keySpan, true);
- } else if (
- State.isCustomizingShortcuts &&
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.shortcutId === shortcut.id
- ) {
- this.setListeningStateUI(shortcut.id, keySpan, false);
- }
- });
- }
- this.updateKeySpanAppearance(keySpan, shortcut);
- }
- row.appendChild(descriptionSpan);
- row.appendChild(keySpan);
- container.appendChild(row);
- });
- return container;
- },
- createWarning() {
- const warning = document.createElement("div");
- warning.className = Config.CSS_CLASSES.HELP_PANEL_WARNING;
- warning.textContent = Config.UI_STRINGS.HELP_PANEL_WARNING_TEXT;
- return warning;
- },
- updateKeySpanAppearance(keySpan, shortcut) {
- keySpan.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT,
- Config.CSS_CLASSES.HELP_PANEL_KEY_LISTENING
- );
- if (
- State.isCustomizingShortcuts &&
- !shortcut.isSettingsEntry &&
- !shortcut.nonConfigurable
- ) {
- if (
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.shortcutId === shortcut.id
- ) {
- keySpan.textContent = Config.UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
- keySpan.classList.add(Config.CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
- } else {
- keySpan.classList.add(
- Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
- );
- }
- }
- },
- updatePanelContentDynamic() {
- if (!this.helpPanelElement) return;
- const settingsButton = this.helpPanelElement.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsButton) {
- settingsButton.textContent = State.isCustomizingShortcuts
- ? Config.UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
- : Config.UI_STRINGS.SETTINGS_BUTTON_TEXT;
- }
- this.shortcutDisplaySpansMap.forEach((span, id) => {
- const shortcut = State.currentShortcutConfig.SHORTCUTS.find(
- (s) => s.id === id
- );
- if (shortcut) {
- span.textContent = ShortcutManager.formatShortcutDisplay(
- shortcut.key
- );
- this.updateKeySpanAppearance(span, shortcut);
- }
- });
- },
- closePanel() {
- if (
- this.helpPanelElement &&
- this.helpPanelElement.classList.contains(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- )
- ) {
- this.helpPanelElement.style.animation = `${Config.ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT} ${Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS}ms ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;
- clearTimeout(this.panelCloseTimer);
- this.panelCloseTimer = setTimeout(() => {
- if (this.helpPanelElement) {
- this.helpPanelElement.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- );
- this.helpPanelElement.style.animation = "";
- this.helpPanelElement.style.opacity = "0";
- this.helpPanelElement.style.visibility = "hidden";
- }
- window.removeEventListener(
- "click",
- EventManager.handlePanelInteractionOutsideProxy,
- true
- );
- window.removeEventListener(
- "keydown",
- EventManager.handlePanelEscapeKeyProxy,
- true
- );
- }, Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS);
- }
- },
- toggleVisibility() {
- if (
- !this.helpPanelElement ||
- !document.body.contains(this.helpPanelElement)
- ) {
- this.createPanel();
- } else {
- this.updatePanelContentDynamic();
- }
- clearTimeout(this.panelCloseTimer);
- const isCurrentlyVisibleByClass =
- this.helpPanelElement.classList.contains(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- );
- let currentOpacity = 0;
- if (
- isCurrentlyVisibleByClass ||
- this.helpPanelElement.style.animationName ===
- Config.ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT
- ) {
- currentOpacity = parseFloat(
- getComputedStyle(this.helpPanelElement).opacity
- );
- }
- const isAnimatingOut =
- this.helpPanelElement.style.animationName ===
- Config.ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT;
- if (
- isCurrentlyVisibleByClass &&
- currentOpacity > 0.01 &&
- !isAnimatingOut
- ) {
- if (State.isCustomizingShortcuts && !State.activeCustomizationTarget) {
- const settingsButton = this.helpPanelElement.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsButton) this.toggleCustomizationModeUI(settingsButton);
- }
- this.closePanel();
- } else {
- this.helpPanelElement.style.animation = "";
- this.helpPanelElement.style.opacity = "0";
- this.helpPanelElement.style.visibility = "hidden";
- requestAnimationFrame(() => {
- this.helpPanelElement.classList.add(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- );
- this.helpPanelElement.style.visibility = "visible";
- this.helpPanelElement.style.animation = `${Config.ELEMENT_IDS.HELP_PANEL_ANIMATE_IN} ${Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS}ms ${Config.SCRIPT_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;
- });
- setTimeout(() => {
- window.addEventListener(
- "click",
- EventManager.handlePanelInteractionOutsideProxy,
- true
- );
- window.addEventListener(
- "keydown",
- EventManager.handlePanelEscapeKeyProxy,
- true
- );
- }, 0);
- }
- },
- updateShortcutDisplayUI(shortcutId, newKey) {
- const spanElement = this.shortcutDisplaySpansMap.get(shortcutId);
- if (spanElement) {
- spanElement.textContent = newKey
- ? ShortcutManager.formatShortcutDisplay(newKey)
- : Config.UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
- }
- },
- setListeningStateUI(shortcutId, spanElement, isListening) {
- if (isListening) {
- if (
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.spanElement !== spanElement
- ) {
- const prevShortcut = State.currentShortcutConfig.SHORTCUTS.find(
- (s) => s.id === State.activeCustomizationTarget.shortcutId
- );
- State.activeCustomizationTarget.spanElement.textContent =
- ShortcutManager.formatShortcutDisplay(prevShortcut?.key);
- this.updateKeySpanAppearance(
- State.activeCustomizationTarget.spanElement,
- prevShortcut
- );
- }
- State.activeCustomizationTarget = { shortcutId, spanElement };
- spanElement.textContent = Config.UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
- this.shortcutDisplaySpansMap.forEach((s) =>
- s.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
- )
- );
- spanElement.classList.add(Config.CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
- spanElement.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
- );
- } else {
- if (
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.shortcutId === shortcutId
- ) {
- State.activeCustomizationTarget = null;
- }
- const currentKey = State.currentShortcutConfig.SHORTCUTS.find(
- (s) => s.id === shortcutId
- )?.key;
- spanElement.textContent =
- ShortcutManager.formatShortcutDisplay(currentKey);
- spanElement.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_KEY_LISTENING
- );
- if (State.isCustomizingShortcuts) {
- this.shortcutDisplaySpansMap.forEach((s, id) => {
- const cfg = State.currentShortcutConfig.SHORTCUTS.find(
- (sc) => sc.id === id
- );
- if (cfg && !cfg.nonConfigurable && !cfg.isSettingsEntry) {
- s.classList.add(
- Config.CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
- );
- }
- });
- }
- }
- },
- toggleCustomizationModeUI(settingsButtonSpan) {
- State.isCustomizingShortcuts = !State.isCustomizingShortcuts;
- if (State.activeCustomizationTarget) {
- this.setListeningStateUI(
- State.activeCustomizationTarget.shortcutId,
- State.activeCustomizationTarget.spanElement,
- false
- );
- }
- settingsButtonSpan.textContent = State.isCustomizingShortcuts
- ? Config.UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
- : Config.UI_STRINGS.SETTINGS_BUTTON_TEXT;
- this.shortcutDisplaySpansMap.forEach((span, id) => {
- const shortcut = State.currentShortcutConfig.SHORTCUTS.find(
- (s) => s.id === id
- );
- if (shortcut) this.updateKeySpanAppearance(span, shortcut);
- });
- },
- displayKeyConflictWarning(spanElement, conflictingKey) {
- spanElement.textContent = `${
- Config.UI_STRINGS.KEY_CONFLICT_TEXT_PREFIX
- }${conflictingKey.toUpperCase()}${
- Config.UI_STRINGS.KEY_CONFLICT_TEXT_SUFFIX
- }`;
- setTimeout(() => {
- if (
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.spanElement === spanElement
- ) {
- spanElement.textContent = Config.UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
- }
- }, 2000);
- },
- displayInvalidModifierWarning(spanElement) {
- spanElement.textContent = `${
- Config.UI_STRINGS.INVALID_MODIFIER_TEXT_PREFIX
- }${State.getActiveModifierCharDisplay()}${
- Config.UI_STRINGS.INVALID_MODIFIER_TEXT_SUFFIX
- }`;
- setTimeout(() => {
- if (
- State.activeCustomizationTarget &&
- State.activeCustomizationTarget.spanElement === spanElement
- ) {
- spanElement.textContent = Config.UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
- }
- }, 2000);
- },
- triggerInvalidKeyShake(spanElement) {
- spanElement.classList.add(
- Config.CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE
- );
- setTimeout(() => {
- spanElement.classList.remove(
- Config.CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE
- );
- }, 500);
- },
- };
- const EventManager = {
- globalKeyDownListener: null,
- debouncedToggleHelpPanel: null,
- init() {
- this.debouncedToggleHelpPanel = this.debounce(
- HelpPanelManager.toggleVisibility.bind(HelpPanelManager),
- Config.SCRIPT_SETTINGS.DEBOUNCE_DELAY_MS
- );
- this.reinitializeGlobalKeyListener();
- },
- debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func.apply(this, args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
- },
- createGlobalKeyListener() {
- const specialActionHandlers = {
- toggleHelpPanel: this.debouncedToggleHelpPanel,
- toggleCustomizationMode: () => {
- if (HelpPanelManager.helpPanelElement) {
- const settingsButton =
- HelpPanelManager.helpPanelElement.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsButton)
- HelpPanelManager.toggleCustomizationModeUI(settingsButton);
- }
- },
- };
- const shortcutActionMap = {};
- State.currentShortcutConfig.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] = () =>
- ShortcutManager.triggerElementClick(shortcut.selectorConfig);
- }
- }
- });
- return (event) => {
- if (event.key === "Escape") {
- return;
- }
- if (State.activeCustomizationTarget) {
- event.preventDefault();
- event.stopPropagation();
- const newKey = event.key;
- const targetSpan = State.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 =
- State.currentShortcutConfig.SHORTCUTS.find(
- (s) =>
- s.key &&
- s.key.toLowerCase() === newKey.toLowerCase() &&
- s.id !== State.activeCustomizationTarget.shortcutId
- );
- if (conflictingShortcut) {
- HelpPanelManager.displayKeyConflictWarning(targetSpan, newKey);
- return;
- }
- State.saveCustomShortcut(
- State.activeCustomizationTarget.shortcutId,
- newKey
- );
- HelpPanelManager.updateShortcutDisplayUI(
- State.activeCustomizationTarget.shortcutId,
- newKey
- );
- HelpPanelManager.setListeningStateUI(
- State.activeCustomizationTarget.shortcutId,
- targetSpan,
- false
- );
- } else {
- HelpPanelManager.triggerInvalidKeyShake(targetSpan);
- }
- } else if (!["Control", "Alt", "Shift", "Meta"].includes(newKey)) {
- if (event[State.getActiveModifierProperty()]) {
- const conflictingShortcut =
- State.currentShortcutConfig.SHORTCUTS.find(
- (s) =>
- s.key &&
- s.key.toLowerCase() === newKey.toLowerCase() &&
- s.id !== State.activeCustomizationTarget.shortcutId
- );
- if (conflictingShortcut) {
- HelpPanelManager.displayKeyConflictWarning(targetSpan, newKey);
- return;
- }
- State.saveCustomShortcut(
- State.activeCustomizationTarget.shortcutId,
- newKey
- );
- HelpPanelManager.updateShortcutDisplayUI(
- State.activeCustomizationTarget.shortcutId,
- newKey
- );
- HelpPanelManager.setListeningStateUI(
- State.activeCustomizationTarget.shortcutId,
- targetSpan,
- false
- );
- } else {
- HelpPanelManager.displayInvalidModifierWarning(targetSpan);
- }
- }
- return;
- }
- if (State.isCustomizingShortcuts) {
- return;
- }
- if (!event[State.getActiveModifierProperty()]) {
- return;
- }
- const pressedKey = event.key.toLowerCase();
- const actionToExecute = shortcutActionMap[pressedKey];
- if (typeof actionToExecute === "function") {
- actionToExecute();
- event.preventDefault();
- event.stopPropagation();
- }
- };
- },
- reinitializeGlobalKeyListener() {
- if (this.globalKeyDownListener) {
- window.removeEventListener("keydown", this.globalKeyDownListener, true);
- }
- this.globalKeyDownListener = this.createGlobalKeyListener();
- window.addEventListener("keydown", this.globalKeyDownListener, true);
- },
- handlePanelInteractionOutsideProxy(event) {
- if (
- HelpPanelManager.helpPanelElement &&
- HelpPanelManager.helpPanelElement.classList.contains(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- ) &&
- !HelpPanelManager.helpPanelElement.contains(event.target) &&
- parseFloat(
- getComputedStyle(HelpPanelManager.helpPanelElement).opacity
- ) === 1
- ) {
- if (State.isCustomizingShortcuts) {
- const settingsButton =
- HelpPanelManager.helpPanelElement.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsButton)
- HelpPanelManager.toggleCustomizationModeUI(settingsButton);
- }
- HelpPanelManager.closePanel();
- }
- },
- handlePanelEscapeKeyProxy(event) {
- if (event.key === "Escape") {
- if (
- HelpPanelManager.helpPanelElement &&
- HelpPanelManager.helpPanelElement.classList.contains(
- Config.CSS_CLASSES.HELP_PANEL_VISIBLE
- ) &&
- parseFloat(
- getComputedStyle(HelpPanelManager.helpPanelElement).opacity
- ) === 1
- ) {
- event.preventDefault();
- event.stopPropagation();
- if (State.activeCustomizationTarget) {
- HelpPanelManager.setListeningStateUI(
- State.activeCustomizationTarget.shortcutId,
- State.activeCustomizationTarget.spanElement,
- false
- );
- } else if (State.isCustomizingShortcuts) {
- const settingsButton =
- HelpPanelManager.helpPanelElement.querySelector(
- `.${Config.CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
- );
- if (settingsButton)
- HelpPanelManager.toggleCustomizationModeUI(settingsButton);
- }
- HelpPanelManager.closePanel();
- }
- }
- },
- };
- const ScriptManager = {
- init() {
- try {
- State.initialize();
- UserInterface.injectStyles();
- EventManager.init();
- } catch (error) {}
- },
- };
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", () => ScriptManager.init(), {
- once: true,
- });
- } else {
- ScriptManager.init();
- }
- })();