DeepSeek 快捷键

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

  1. // ==UserScript==
  2. // @name DeepSeek ShortCuts
  3. // @name:zh-CN DeepSeek 快捷键
  4. // @name:zh-TW DeepSeek 快捷鍵
  5. // @description Keyboard Shortcuts For DeepSeek | Support Custom Shortcut Keys
  6. // @description:zh-CN 为DeepSeek提供快捷键支持 | 支持自定义快捷键
  7. // @description:zh-TW 為DeepSeek提供快捷鍵支援 | 支援自定義快捷鍵
  8. // @version 1.5.0
  9. // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DeepSeekShortcutsIcon.svg
  10. // @author 念柚
  11. // @namespace https://github.com/MiPoNianYou/UserScripts
  12. // @supportURL https://github.com/MiPoNianYou/UserScripts/issues
  13. // @license GPL-3.0
  14. // @match https://chat.deepseek.com/*
  15. // @grant GM_addStyle
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. "use strict";
  22.  
  23. const UI_SETTINGS = {
  24. FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
  25. ANIMATION_DURATION_MS: 350,
  26. ANIMATION_EASING_POP_OUT: "cubic-bezier(0.34, 1.56, 0.64, 1)",
  27. ANIMATION_EASING_STANDARD_INTERACTIVE: "cubic-bezier(0, 0, 0.58, 1)",
  28. BREATHING_ANIMATION_DURATION: "2.2s",
  29. DEBOUNCE_DELAY_MS: 150,
  30. };
  31.  
  32. const STORAGE_KEYS = {
  33. CUSTOM_SHORTCUTS_PREFIX: "dsk_custom_shortcuts_",
  34. };
  35.  
  36. const ELEMENT_SELECTORS = {
  37. REGENERATE_BUTTON: {
  38. selector: ".ds-icon-button",
  39. filterText: "#重新生成",
  40. },
  41. CONTINUE_BUTTON: {
  42. selector: ".ds-button",
  43. filterText: "继续生成",
  44. },
  45. STOP_GENERATING_BUTTON: {
  46. selector: "._7436101",
  47. position: "first",
  48. },
  49. LAST_COPY_BUTTON: {
  50. parentSelector: "div._4f9bf79.d7dc56a8",
  51. parentPosition: "last",
  52. selector: "._965abe9 .ds-icon-button",
  53. childPosition: "first",
  54. },
  55. LAST_EDIT_BUTTON: {
  56. parentSelector: "._9663006",
  57. parentPosition: "last",
  58. selector: "._78e0558 .ds-icon-button",
  59. childPosition: "last",
  60. },
  61. DEEP_THINK_MODE_BUTTON: {
  62. selector: ".ds-button span",
  63. filterText: "深度思考",
  64. },
  65. SEARCH_MODE_BUTTON: {
  66. selector: ".ds-button span",
  67. filterText: "联网搜索",
  68. },
  69. UPLOAD_FILE_BUTTON: {
  70. selector: ".f02f0e25",
  71. position: "first",
  72. },
  73. NEW_CHAT_BUTTON: {
  74. selector: "._217e214",
  75. position: "first",
  76. },
  77. TOGGLE_SIDEBAR_BUTTON: {
  78. selector: ".ds-icon-button",
  79. filterText: "svg #打开边栏0730, svg #折叠边栏0730",
  80. },
  81. CURRENT_CHAT_MENU_BUTTON: {
  82. parentSelector: "._83421f9.b64fb9ae",
  83. parentPosition: "last",
  84. selector: "._2090548",
  85. childPosition: "first",
  86. },
  87. };
  88.  
  89. const ELEMENT_IDS = {
  90. HELP_PANEL: "dsk-help-panel",
  91. HELP_PANEL_ANIMATE_IN: "dsk-help-panel-animate-in",
  92. HELP_PANEL_ANIMATE_OUT: "dsk-help-panel-animate-out",
  93. };
  94.  
  95. const CSS_CLASSES = {
  96. HELP_PANEL_VISIBLE: "dsk-help-panel--visible",
  97. HELP_PANEL_CLOSE_BUTTON: "dsk-help-panel-close-button",
  98. HELP_PANEL_TITLE: "dsk-help-panel-title",
  99. HELP_PANEL_CONTENT: "dsk-help-panel-content",
  100. HELP_PANEL_ROW: "dsk-help-panel-row",
  101. HELP_PANEL_KEY: "dsk-help-panel-key",
  102. HELP_PANEL_KEY_DISPLAY: "dsk-help-panel-key--display",
  103. HELP_PANEL_KEY_SETTING: "dsk-help-panel-key--setting",
  104. HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT:
  105. "dsk-help-panel-key--configurable-highlight",
  106. HELP_PANEL_KEY_LISTENING: "dsk-help-panel-key--listening",
  107. HELP_PANEL_KEY_INVALID_SHAKE: "dsk-key-invalid-shake",
  108. HELP_PANEL_DESCRIPTION: "dsk-help-panel-description",
  109. HELP_PANEL_WARNING: "dsk-help-panel-warning",
  110. };
  111.  
  112. const UI_STRINGS = {
  113. HELP_PANEL_TITLE: "快捷按键指北",
  114. HELP_PANEL_WARNING_TEXT: "⚠️ 脚本依UA自动适配快捷键 篡改UA或致功能异常",
  115. CUSTOMIZE_SHORTCUTS_LABEL: "自定义快捷键",
  116. SETTINGS_BUTTON_TEXT: "设置自定按键",
  117. FINISH_CUSTOMIZING_BUTTON_TEXT: "完成自定设置",
  118. PRESS_NEW_SHORTCUT_TEXT: "请按下快捷键",
  119. KEY_CONFLICT_TEXT_PREFIX: "键 「",
  120. KEY_CONFLICT_TEXT_SUFFIX: "」 已被使用",
  121. INVALID_MODIFIER_TEXT_PREFIX: "请按 ",
  122. INVALID_MODIFIER_TEXT_SUFFIX: " + 字母/数字",
  123. };
  124.  
  125. let currentKeybindingConfig = {
  126. MODIFIERS: (() => {
  127. const isMac = /Macintosh|Mac OS X/i.test(navigator.userAgent);
  128. return {
  129. CHARACTER_DISPLAY: isMac ? "Control" : "Alt",
  130. EVENT_PROPERTY: isMac ? "ctrlKey" : "altKey",
  131. };
  132. })(),
  133. SHORTCUTS: [
  134. {
  135. id: "regenerate",
  136. key: "R",
  137. description: "重新生成回答",
  138. selectorConfig: ELEMENT_SELECTORS.REGENERATE_BUTTON,
  139. },
  140. {
  141. id: "continueGenerating",
  142. key: "C",
  143. description: "继续生成回答",
  144. selectorConfig: ELEMENT_SELECTORS.CONTINUE_BUTTON,
  145. },
  146. {
  147. id: "stopGenerating",
  148. key: "Q",
  149. description: "中断当前生成",
  150. selectorConfig: ELEMENT_SELECTORS.STOP_GENERATING_BUTTON,
  151. },
  152. {
  153. id: "copyLastResponse",
  154. key: "K",
  155. description: "复制末条回答",
  156. selectorConfig: ELEMENT_SELECTORS.LAST_COPY_BUTTON,
  157. },
  158. {
  159. id: "editLastQuery",
  160. key: "E",
  161. description: "编辑末次提问",
  162. selectorConfig: ELEMENT_SELECTORS.LAST_EDIT_BUTTON,
  163. },
  164. {
  165. id: "deepThinkMode",
  166. key: "D",
  167. description: "深度思考模式",
  168. selectorConfig: ELEMENT_SELECTORS.DEEP_THINK_MODE_BUTTON,
  169. },
  170. {
  171. id: "searchMode",
  172. key: "S",
  173. description: "联网搜索模式",
  174. selectorConfig: ELEMENT_SELECTORS.SEARCH_MODE_BUTTON,
  175. },
  176. {
  177. id: "uploadFile",
  178. key: "U",
  179. description: "上传本地文件",
  180. selectorConfig: ELEMENT_SELECTORS.UPLOAD_FILE_BUTTON,
  181. },
  182. {
  183. id: "newChat",
  184. key: "N",
  185. description: "新建对话窗口",
  186. selectorConfig: ELEMENT_SELECTORS.NEW_CHAT_BUTTON,
  187. },
  188. {
  189. id: "toggleSidebar",
  190. key: "T",
  191. description: "切换开关边栏",
  192. selectorConfig: ELEMENT_SELECTORS.TOGGLE_SIDEBAR_BUTTON,
  193. },
  194. {
  195. id: "currentChatMenu",
  196. key: "I",
  197. description: "当前对话菜单",
  198. selectorConfig: ELEMENT_SELECTORS.CURRENT_CHAT_MENU_BUTTON,
  199. },
  200. {
  201. id: "toggleHelpPanel",
  202. key: "H",
  203. description: "快捷按键帮助",
  204. actionIdentifier: "toggleHelpPanel",
  205. isSpecialAction: true,
  206. },
  207. {
  208. id: "settingsEntry",
  209. key: null,
  210. description: UI_STRINGS.CUSTOMIZE_SHORTCUTS_LABEL,
  211. isSettingsEntry: true,
  212. actionIdentifier: "toggleCustomizationMode",
  213. nonConfigurable: true,
  214. },
  215. ],
  216. };
  217.  
  218. let helpPanelElement = null;
  219. let keydownEventListener = null;
  220. let isCustomizingShortcuts = false;
  221. let activeCustomizationTarget = null;
  222. const shortcutDisplaySpansMap = new Map();
  223. let panelCloseTimer = null;
  224.  
  225. function injectUserInterfaceStyles() {
  226. const styles = `
  227. :root {
  228. --ctp-frappe-rosewater: #f2d5cf;
  229. --ctp-frappe-flamingo: #eebebe;
  230. --ctp-frappe-pink: #f4b8e4;
  231. --ctp-frappe-mauve: #ca9ee6;
  232. --ctp-frappe-red: #e78284;
  233. --ctp-frappe-maroon: #ea999c;
  234. --ctp-frappe-peach: #ef9f76;
  235. --ctp-frappe-yellow: #e5c890;
  236. --ctp-frappe-green: #a6d189;
  237. --ctp-frappe-teal: #81c8be;
  238. --ctp-frappe-sky: #99d1db;
  239. --ctp-frappe-sapphire: #85c1dc;
  240. --ctp-frappe-blue: #8caaee;
  241. --ctp-frappe-lavender: #babbf1;
  242. --ctp-frappe-text: #c6d0f5;
  243. --ctp-frappe-subtext1: #b5bfe2;
  244. --ctp-frappe-subtext0: #a5adce;
  245. --ctp-frappe-overlay2: #949cbb;
  246. --ctp-frappe-overlay1: #838ba7;
  247. --ctp-frappe-overlay0: #737994;
  248. --ctp-frappe-surface2: #626880;
  249. --ctp-frappe-surface1: #51576d;
  250. --ctp-frappe-surface0: #414559;
  251. --ctp-frappe-base: #303446;
  252. --ctp-frappe-mantle: #292c3c;
  253. --ctp-frappe-crust: #232634;
  254. --ctp-frappe-crust-rgb: 35, 38, 52;
  255.  
  256. --dsk-panel-bg: rgba(41, 44, 60, 0.85);
  257. --dsk-panel-border: rgba(65, 69, 89, 0.5);
  258. --dsk-panel-shadow:
  259. 0 1px 3px rgba(var(--ctp-frappe-crust-rgb), 0.12),
  260. 0 6px 16px rgba(var(--ctp-frappe-crust-rgb), 0.10),
  261. 0 12px 28px rgba(var(--ctp-frappe-crust-rgb), 0.08);
  262. --dsk-text-primary: var(--ctp-frappe-text);
  263. --dsk-text-secondary: var(--ctp-frappe-subtext0);
  264. --dsk-key-bg: var(--ctp-frappe-surface0);
  265. --dsk-key-border: var(--ctp-frappe-surface1);
  266. --dsk-key-setting-text: var(--ctp-frappe-blue);
  267. --dsk-key-setting-hover-bg: var(--ctp-frappe-surface1);
  268. --dsk-key-breathing-highlight-color: var(--ctp-frappe-mauve);
  269. --dsk-key-listening-border: var(--ctp-frappe-green);
  270. --dsk-key-listening-bg: color-mix(in srgb, var(--dsk-key-bg) 85%, var(--ctp-frappe-green) 15%);
  271. --dsk-key-invalid-shake-color: var(--ctp-frappe-red);
  272. --dsk-warning-bg: rgba(65, 69, 89, 0.5);
  273. --dsk-warning-border: var(--ctp-frappe-surface1);
  274. --dsk-warning-text: var(--ctp-frappe-yellow);
  275. --dsk-scrollbar-thumb: var(--ctp-frappe-overlay0);
  276. --dsk-scrollbar-thumb-hover: var(--ctp-frappe-overlay1);
  277. --dsk-close-button-bg: var(--ctp-frappe-red);
  278. --dsk-close-button-hover-bg: color-mix(in srgb, var(--ctp-frappe-red) 80%, var(--ctp-frappe-crust) 20%);
  279. --dsk-close-button-symbol: rgba(var(--ctp-frappe-crust-rgb), 0.7);
  280. }
  281.  
  282. @keyframes dsk-opacity-breathing-effect {
  283. 0%, 100% {
  284. opacity: 0;
  285. }
  286. 50% {
  287. opacity: 0.25;
  288. }
  289. }
  290.  
  291. @keyframes dsk-border-breathing-effect {
  292. 0%, 100% {
  293. border-color: var(--dsk-key-border);
  294. }
  295. 50% {
  296. border-color: var(--dsk-key-breathing-highlight-color);
  297. }
  298. }
  299.  
  300. @keyframes dsk-invalid-shake-effect {
  301. 0%, 100% { transform: translateX(0); }
  302. 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
  303. 20%, 40%, 60%, 80% { transform: translateX(3px); }
  304. }
  305.  
  306. @keyframes ${ELEMENT_IDS.HELP_PANEL_ANIMATE_IN} {
  307. 0% {
  308. transform: translate(-50%, -50%) scale(0.88);
  309. opacity: 0;
  310. }
  311. 100% {
  312. transform: translate(-50%, -50%) scale(1);
  313. opacity: 1;
  314. }
  315. }
  316.  
  317. @keyframes ${ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT} {
  318. 0% {
  319. transform: translate(-50%, -50%) scale(1);
  320. opacity: 1;
  321. }
  322. 100% {
  323. transform: translate(-50%, -50%) scale(0.9);
  324. opacity: 0;
  325. }
  326. }
  327.  
  328. #${ELEMENT_IDS.HELP_PANEL} {
  329. position: fixed;
  330. top: 50%;
  331. left: 50%;
  332. transform: translate(-50%, -50%) scale(0.88);
  333. opacity: 0;
  334. visibility: hidden;
  335. z-index: 2147483647;
  336. min-width: 300px;
  337. max-width: 480px;
  338. padding: 24px;
  339. border: 1px solid var(--dsk-panel-border);
  340. border-radius: 16px;
  341. background-color: var(--dsk-panel-bg);
  342. color: var(--dsk-text-primary);
  343. font-family: ${UI_SETTINGS.FONT_STACK};
  344. font-size: 14px;
  345. font-weight: 500;
  346. line-height: 1.5;
  347. box-shadow: var(--dsk-panel-shadow);
  348. backdrop-filter: blur(20px) saturate(180%);
  349. -webkit-backdrop-filter: blur(20px) saturate(180%);
  350. display: flex;
  351. flex-direction: column;
  352. pointer-events: none;
  353. }
  354.  
  355. #${ELEMENT_IDS.HELP_PANEL}.${CSS_CLASSES.HELP_PANEL_VISIBLE} {
  356. pointer-events: auto;
  357. }
  358.  
  359. .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON} {
  360. position: absolute;
  361. top: 14px;
  362. left: 14px;
  363. width: 12px;
  364. height: 12px;
  365. padding: 0;
  366. border: none;
  367. border-radius: 50%;
  368. background-color: var(--dsk-close-button-bg);
  369. cursor: pointer;
  370. display: flex;
  371. align-items: center;
  372. justify-content: center;
  373. transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
  374. transform 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
  375. appearance: none;
  376. -webkit-appearance: none;
  377. outline: none;
  378. }
  379.  
  380. .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}::before {
  381. content: '✕';
  382. display: block;
  383. color: transparent;
  384. font-size: 10px;
  385. font-weight: bold;
  386. line-height: 12px;
  387. text-align: center;
  388. transition: color 0.1s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
  389. }
  390.  
  391. .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover {
  392. background-color: var(--dsk-close-button-hover-bg);
  393. }
  394.  
  395. .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:hover::before {
  396. color: var(--dsk-close-button-symbol);
  397. }
  398.  
  399. .${CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON}:active {
  400. filter: brightness(0.85);
  401. transform: scale(0.9);
  402. }
  403.  
  404. .${CSS_CLASSES.HELP_PANEL_TITLE} {
  405. margin: 0 0 18px 0;
  406. padding-top: 8px;
  407. color: var(--dsk-text-primary);
  408. font-size: 17px;
  409. font-weight: 600;
  410. text-align: center;
  411. flex-shrink: 0;
  412. }
  413.  
  414. .${CSS_CLASSES.HELP_PANEL_CONTENT} {
  415. flex-grow: 1;
  416. overflow-y: auto;
  417. max-height: 60vh;
  418. margin-right: -12px;
  419. padding-right: 12px;
  420. scrollbar-width: thin;
  421. scrollbar-color: var(--dsk-scrollbar-thumb) transparent;
  422. }
  423.  
  424. .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar {
  425. width: 6px;
  426. }
  427.  
  428. .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-track {
  429. background: transparent;
  430. margin: 4px 0;
  431. }
  432.  
  433. .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb {
  434. background-color: var(--dsk-scrollbar-thumb);
  435. border-radius: 3px;
  436. transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
  437. }
  438.  
  439. .${CSS_CLASSES.HELP_PANEL_CONTENT}::-webkit-scrollbar-thumb:hover {
  440. background-color: var(--dsk-scrollbar-thumb-hover);
  441. }
  442.  
  443. .${CSS_CLASSES.HELP_PANEL_ROW} {
  444. display: flex;
  445. justify-content: space-between;
  446. align-items: center;
  447. margin-bottom: 12px;
  448. padding: 6px 2px;
  449. }
  450.  
  451. .${CSS_CLASSES.HELP_PANEL_CONTENT} > .${CSS_CLASSES.HELP_PANEL_ROW}:last-child {
  452. margin-bottom: 0;
  453. }
  454.  
  455. .${CSS_CLASSES.HELP_PANEL_KEY} {
  456. min-width: 95px;
  457. padding: 5px 10px;
  458. margin-left: 18px;
  459. background-color: var(--dsk-key-bg);
  460. border: 1px solid var(--dsk-key-border);
  461. border-radius: 6px;
  462. box-shadow: 0 1px 1px rgba(0,0,0,0.08), inset 0 1px 1px rgba(255,255,255,0.03);
  463. color: var(--dsk-text-primary);
  464. font-family: inherit;
  465. font-size: 13px;
  466. font-weight: 500;
  467. text-align: center;
  468. flex-shrink: 0;
  469. transition: background-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
  470. border-color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE},
  471. color 0.15s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_INTERACTIVE};
  472. cursor: default;
  473. position: relative;
  474. }
  475.  
  476. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_DISPLAY} {
  477. }
  478.  
  479. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_SETTING} {
  480. color: var(--dsk-key-setting-text);
  481. cursor: pointer;
  482. }
  483.  
  484. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}:hover {
  485. background-color: var(--dsk-key-setting-hover-bg);
  486. border-color: var(--ctp-frappe-overlay0);
  487. }
  488.  
  489. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT} {
  490. animation: dsk-border-breathing-effect ${UI_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
  491. cursor: pointer;
  492. }
  493.  
  494. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT}::after {
  495. content: '';
  496. position: absolute;
  497. top: 0;
  498. left: 0;
  499. right: 0;
  500. bottom: 0;
  501. border-radius: inherit;
  502. background-color: var(--dsk-key-breathing-highlight-color);
  503. opacity: 0;
  504. z-index: 0;
  505. pointer-events: none;
  506. animation: dsk-opacity-breathing-effect ${UI_SETTINGS.BREATHING_ANIMATION_DURATION} infinite ease-in-out;
  507. }
  508.  
  509. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_LISTENING} {
  510. border-color: var(--dsk-key-listening-border) !important;
  511. background-color: var(--dsk-key-listening-bg) !important;
  512. color: var(--ctp-frappe-green) !important;
  513. animation: none !important;
  514. cursor: default !important;
  515. }
  516. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_LISTENING}::after {
  517. animation: none !important;
  518. opacity: 0 !important;
  519. }
  520.  
  521. .${CSS_CLASSES.HELP_PANEL_KEY}.${CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE} {
  522. animation: dsk-invalid-shake-effect 0.5s ${UI_SETTINGS.ANIMATION_EASING_STANDARD_DEFAULT};
  523. border-color: var(--dsk-key-invalid-shake-color) !important;
  524. color: var(--dsk-key-invalid-shake-color) !important;
  525. }
  526.  
  527.  
  528. .${CSS_CLASSES.HELP_PANEL_DESCRIPTION} {
  529. flex-grow: 1;
  530. padding-right: 10px;
  531. color: var(--dsk-text-secondary);
  532. font-size: 13.5px;
  533. }
  534.  
  535. .${CSS_CLASSES.HELP_PANEL_WARNING} {
  536. margin-top: 20px;
  537. padding: 12px 16px;
  538. background-color: var(--dsk-warning-bg);
  539. border: 1px solid var(--dsk-warning-border);
  540. border-radius: 10px;
  541. color: var(--dsk-warning-text);
  542. font-size: 12.5px;
  543. font-weight: 500;
  544. line-height: 1.45;
  545. text-align: center;
  546. flex-shrink: 0;
  547. }
  548. `;
  549. try {
  550. GM_addStyle(styles);
  551. } catch (e) {
  552. const styleElement = document.createElement("style");
  553. styleElement.textContent = styles;
  554. (document.head || document.documentElement).appendChild(styleElement);
  555. }
  556. }
  557.  
  558. function debounce(func, wait) {
  559. let timeout;
  560. return function executedFunction(...args) {
  561. const later = () => {
  562. clearTimeout(timeout);
  563. func.apply(this, args);
  564. };
  565. clearTimeout(timeout);
  566. timeout = setTimeout(later, wait);
  567. };
  568. }
  569.  
  570. function getElementByConfig(config) {
  571. if (!config || !config.selector) return null;
  572. const {
  573. selector,
  574. filterText,
  575. position = "first",
  576. parentSelector,
  577. parentPosition = "last",
  578. childPosition = "first",
  579. } = config;
  580.  
  581. let targetElements = [];
  582.  
  583. if (parentSelector) {
  584. const parents = Array.from(document.querySelectorAll(parentSelector));
  585. if (parents.length === 0) return null;
  586. const parentIndex = parentPosition === "last" ? parents.length - 1 : 0;
  587. const targetParent = parents[parentIndex];
  588. if (!targetParent) return null;
  589. targetElements = Array.from(targetParent.querySelectorAll(selector));
  590. } else {
  591. targetElements = Array.from(document.querySelectorAll(selector));
  592. }
  593.  
  594. if (targetElements.length === 0) return null;
  595.  
  596. if (filterText) {
  597. const filters = filterText.split(",").map((f) => f.trim());
  598. const foundElement = targetElements.find((element) =>
  599. filters.some(
  600. (ft) =>
  601. element.textContent?.includes(ft) ||
  602. (ft.startsWith("svg #") &&
  603. element.querySelector(ft.replace("svg ", "")))
  604. )
  605. );
  606. return foundElement || null;
  607. } else {
  608. const index =
  609. position === "last"
  610. ? targetElements.length - 1
  611. : childPosition === "last"
  612. ? targetElements.length - 1
  613. : 0;
  614. return targetElements[index] || null;
  615. }
  616. }
  617.  
  618. function triggerElementClick(elementConfig) {
  619. const element = getElementByConfig(elementConfig);
  620. if (element && typeof element.click === "function") {
  621. element.click();
  622. return true;
  623. }
  624. return false;
  625. }
  626.  
  627. function formatShortcutForDisplay(shortcutKey) {
  628. if (!shortcutKey) return "---";
  629. return `${
  630. currentKeybindingConfig.MODIFIERS.CHARACTER_DISPLAY
  631. } + ${shortcutKey.toUpperCase()}`;
  632. }
  633.  
  634. function updateShortcutDisplay(shortcutId, newKey) {
  635. const spanElement = shortcutDisplaySpansMap.get(shortcutId);
  636. if (spanElement) {
  637. spanElement.textContent = newKey
  638. ? formatShortcutForDisplay(newKey)
  639. : UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  640. }
  641. }
  642.  
  643. function saveCustomShortcut(shortcutId, newKey) {
  644. const shortcutToUpdate = currentKeybindingConfig.SHORTCUTS.find(
  645. (s) => s.id === shortcutId
  646. );
  647. if (shortcutToUpdate) {
  648. shortcutToUpdate.key = newKey.toUpperCase();
  649. try {
  650. GM_setValue(
  651. `${STORAGE_KEYS.CUSTOM_SHORTCUTS_PREFIX}${shortcutId}`,
  652. shortcutToUpdate.key
  653. );
  654. } catch (e) {}
  655.  
  656. if (keydownEventListener) {
  657. window.removeEventListener("keydown", keydownEventListener, true);
  658. }
  659. keydownEventListener = createKeyboardEventHandler();
  660. window.addEventListener("keydown", keydownEventListener, true);
  661. }
  662. }
  663.  
  664. function loadCustomShortcuts() {
  665. currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
  666. if (shortcut.nonConfigurable || shortcut.isSettingsEntry) return;
  667. try {
  668. const savedKey = GM_getValue(
  669. `${STORAGE_KEYS.CUSTOM_SHORTCUTS_PREFIX}${shortcut.id}`,
  670. shortcut.key
  671. );
  672. if (
  673. savedKey &&
  674. typeof savedKey === "string" &&
  675. savedKey.match(/^[a-zA-Z0-9]$/i)
  676. ) {
  677. shortcut.key = savedKey.toUpperCase();
  678. }
  679. } catch (e) {}
  680. });
  681. }
  682.  
  683. function setListeningState(shortcutId, spanElement, isListening) {
  684. if (isListening) {
  685. if (
  686. activeCustomizationTarget &&
  687. activeCustomizationTarget.spanElement !== spanElement
  688. ) {
  689. const prevShortcut = currentKeybindingConfig.SHORTCUTS.find(
  690. (s) => s.id === activeCustomizationTarget.shortcutId
  691. );
  692. activeCustomizationTarget.spanElement.textContent =
  693. formatShortcutForDisplay(prevShortcut?.key);
  694. activeCustomizationTarget.spanElement.classList.remove(
  695. CSS_CLASSES.HELP_PANEL_KEY_LISTENING
  696. );
  697. activeCustomizationTarget.spanElement.classList.remove(
  698. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  699. );
  700. if (isCustomizingShortcuts) {
  701. activeCustomizationTarget.spanElement.classList.add(
  702. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  703. );
  704. }
  705. }
  706.  
  707. activeCustomizationTarget = { shortcutId, spanElement };
  708. spanElement.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  709. shortcutDisplaySpansMap.forEach((s) => {
  710. s.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
  711. });
  712. spanElement.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  713. spanElement.classList.remove(
  714. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  715. );
  716. } else {
  717. if (
  718. activeCustomizationTarget &&
  719. activeCustomizationTarget.shortcutId === shortcutId
  720. ) {
  721. activeCustomizationTarget = null;
  722. }
  723. spanElement.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  724. const currentKey = currentKeybindingConfig.SHORTCUTS.find(
  725. (s) => s.id === shortcutId
  726. )?.key;
  727. spanElement.textContent = formatShortcutForDisplay(currentKey);
  728.  
  729. if (isCustomizingShortcuts) {
  730. shortcutDisplaySpansMap.forEach((s, id) => {
  731. const cfg = currentKeybindingConfig.SHORTCUTS.find(
  732. (sc) => sc.id === id
  733. );
  734. if (cfg && !cfg.nonConfigurable && !cfg.isSettingsEntry) {
  735. s.classList.add(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
  736. }
  737. });
  738. }
  739. }
  740. }
  741.  
  742. function toggleCustomizationMode(settingsButtonSpan) {
  743. isCustomizingShortcuts = !isCustomizingShortcuts;
  744.  
  745. if (activeCustomizationTarget) {
  746. setListeningState(
  747. activeCustomizationTarget.shortcutId,
  748. activeCustomizationTarget.spanElement,
  749. false
  750. );
  751. }
  752.  
  753. if (isCustomizingShortcuts) {
  754. settingsButtonSpan.textContent =
  755. UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT;
  756. shortcutDisplaySpansMap.forEach((span, id) => {
  757. const shortcut = currentKeybindingConfig.SHORTCUTS.find(
  758. (s) => s.id === id
  759. );
  760. if (
  761. shortcut &&
  762. !shortcut.nonConfigurable &&
  763. !shortcut.isSettingsEntry
  764. ) {
  765. span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT);
  766. }
  767. });
  768. } else {
  769. settingsButtonSpan.textContent = UI_STRINGS.SETTINGS_BUTTON_TEXT;
  770. shortcutDisplaySpansMap.forEach((span) => {
  771. span.classList.remove(
  772. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  773. );
  774. span.classList.remove(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  775. });
  776. }
  777. }
  778.  
  779. function createHelpPanelElement() {
  780. if (helpPanelElement && document.body.contains(helpPanelElement)) {
  781. const settingsButton = helpPanelElement.querySelector(
  782. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  783. );
  784. if (settingsButton) {
  785. settingsButton.textContent = isCustomizingShortcuts
  786. ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
  787. : UI_STRINGS.SETTINGS_BUTTON_TEXT;
  788. }
  789. shortcutDisplaySpansMap.forEach((span, id) => {
  790. const shortcut = currentKeybindingConfig.SHORTCUTS.find(
  791. (s) => s.id === id
  792. );
  793. if (shortcut) {
  794. span.textContent = formatShortcutForDisplay(shortcut.key);
  795. span.classList.remove(
  796. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT,
  797. CSS_CLASSES.HELP_PANEL_KEY_LISTENING
  798. );
  799. if (
  800. isCustomizingShortcuts &&
  801. !shortcut.isSettingsEntry &&
  802. !shortcut.nonConfigurable
  803. ) {
  804. if (
  805. activeCustomizationTarget &&
  806. activeCustomizationTarget.shortcutId === id
  807. ) {
  808. span.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  809. span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  810. } else {
  811. span.classList.add(
  812. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  813. );
  814. }
  815. }
  816. }
  817. });
  818. return helpPanelElement;
  819. }
  820.  
  821. shortcutDisplaySpansMap.clear();
  822.  
  823. const panel = document.createElement("div");
  824. panel.id = ELEMENT_IDS.HELP_PANEL;
  825.  
  826. const closeButton = document.createElement("button");
  827. closeButton.className = CSS_CLASSES.HELP_PANEL_CLOSE_BUTTON;
  828. closeButton.setAttribute("aria-label", "Close help panel");
  829. closeButton.addEventListener("click", (event) => {
  830. event.stopPropagation();
  831. if (isCustomizingShortcuts) {
  832. const settingsBtnInPanel = panel.querySelector(
  833. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  834. );
  835. if (settingsBtnInPanel) toggleCustomizationMode(settingsBtnInPanel);
  836. }
  837. closeHelpPanel();
  838. });
  839.  
  840. const titleElement = document.createElement("h3");
  841. titleElement.className = CSS_CLASSES.HELP_PANEL_TITLE;
  842. titleElement.textContent = UI_STRINGS.HELP_PANEL_TITLE;
  843.  
  844. const contentContainer = document.createElement("div");
  845. contentContainer.className = CSS_CLASSES.HELP_PANEL_CONTENT;
  846.  
  847. currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
  848. const row = document.createElement("div");
  849. row.className = CSS_CLASSES.HELP_PANEL_ROW;
  850.  
  851. const descriptionSpan = document.createElement("span");
  852. descriptionSpan.className = CSS_CLASSES.HELP_PANEL_DESCRIPTION;
  853. descriptionSpan.textContent = shortcut.description;
  854.  
  855. const keySpan = document.createElement("span");
  856. keySpan.className = CSS_CLASSES.HELP_PANEL_KEY;
  857.  
  858. if (shortcut.isSettingsEntry) {
  859. keySpan.textContent = isCustomizingShortcuts
  860. ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
  861. : UI_STRINGS.SETTINGS_BUTTON_TEXT;
  862. keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_SETTING);
  863. keySpan.addEventListener("click", () => {
  864. toggleCustomizationMode(keySpan);
  865. });
  866. } else {
  867. keySpan.textContent = formatShortcutForDisplay(shortcut.key);
  868. keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_DISPLAY);
  869. shortcutDisplaySpansMap.set(shortcut.id, keySpan);
  870.  
  871. if (!shortcut.nonConfigurable) {
  872. keySpan.addEventListener("click", () => {
  873. if (
  874. isCustomizingShortcuts &&
  875. (!activeCustomizationTarget ||
  876. activeCustomizationTarget.shortcutId !== shortcut.id)
  877. ) {
  878. setListeningState(shortcut.id, keySpan, true);
  879. } else if (
  880. isCustomizingShortcuts &&
  881. activeCustomizationTarget &&
  882. activeCustomizationTarget.shortcutId === shortcut.id
  883. ) {
  884. setListeningState(shortcut.id, keySpan, false);
  885. }
  886. });
  887. }
  888.  
  889. if (isCustomizingShortcuts && !shortcut.nonConfigurable) {
  890. if (
  891. activeCustomizationTarget &&
  892. activeCustomizationTarget.shortcutId === shortcut.id
  893. ) {
  894. keySpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  895. keySpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  896. } else {
  897. keySpan.classList.add(
  898. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  899. );
  900. }
  901. }
  902. }
  903.  
  904. row.appendChild(descriptionSpan);
  905. row.appendChild(keySpan);
  906. contentContainer.appendChild(row);
  907. });
  908.  
  909. const warningElement = document.createElement("div");
  910. warningElement.className = CSS_CLASSES.HELP_PANEL_WARNING;
  911. warningElement.textContent = UI_STRINGS.HELP_PANEL_WARNING_TEXT;
  912.  
  913. panel.appendChild(closeButton);
  914. panel.appendChild(titleElement);
  915. panel.appendChild(contentContainer);
  916. panel.appendChild(warningElement);
  917.  
  918. helpPanelElement = panel;
  919. document.body.appendChild(panel);
  920. return panel;
  921. }
  922.  
  923. function closeHelpPanel() {
  924. if (
  925. helpPanelElement &&
  926. helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE)
  927. ) {
  928. helpPanelElement.style.animation = `${ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT} ${UI_SETTINGS.ANIMATION_DURATION_MS}ms ${UI_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;
  929.  
  930. clearTimeout(panelCloseTimer);
  931. panelCloseTimer = setTimeout(() => {
  932. if (helpPanelElement) {
  933. helpPanelElement.classList.remove(CSS_CLASSES.HELP_PANEL_VISIBLE);
  934. helpPanelElement.style.animation = "";
  935. helpPanelElement.style.opacity = "0";
  936. helpPanelElement.style.visibility = "hidden";
  937. }
  938. window.removeEventListener(
  939. "click",
  940. handlePanelInteractionOutside,
  941. true
  942. );
  943. window.removeEventListener("keydown", handlePanelEscapeKey, true);
  944. }, UI_SETTINGS.ANIMATION_DURATION_MS);
  945. }
  946. }
  947.  
  948. function handlePanelInteractionOutside(event) {
  949. if (
  950. helpPanelElement &&
  951. helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE) &&
  952. !helpPanelElement.contains(event.target) &&
  953. parseFloat(getComputedStyle(helpPanelElement).opacity) === 1
  954. ) {
  955. if (isCustomizingShortcuts) {
  956. const settingsButton = helpPanelElement.querySelector(
  957. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  958. );
  959. if (settingsButton) toggleCustomizationMode(settingsButton);
  960. }
  961. closeHelpPanel();
  962. }
  963. }
  964.  
  965. function handlePanelEscapeKey(event) {
  966. if (event.key === "Escape") {
  967. if (
  968. helpPanelElement &&
  969. helpPanelElement.classList.contains(CSS_CLASSES.HELP_PANEL_VISIBLE) &&
  970. parseFloat(getComputedStyle(helpPanelElement).opacity) === 1
  971. ) {
  972. event.preventDefault();
  973. event.stopPropagation();
  974. if (activeCustomizationTarget) {
  975. setListeningState(
  976. activeCustomizationTarget.shortcutId,
  977. activeCustomizationTarget.spanElement,
  978. false
  979. );
  980. } else if (isCustomizingShortcuts) {
  981. const settingsButton = helpPanelElement.querySelector(
  982. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  983. );
  984. if (settingsButton) toggleCustomizationMode(settingsButton);
  985. }
  986. closeHelpPanel();
  987. }
  988. }
  989. }
  990.  
  991. function toggleHelpPanelVisibility() {
  992. if (!helpPanelElement || !document.body.contains(helpPanelElement)) {
  993. helpPanelElement = createHelpPanelElement();
  994. } else {
  995. const settingsButton = helpPanelElement.querySelector(
  996. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  997. );
  998. if (settingsButton) {
  999. settingsButton.textContent = isCustomizingShortcuts
  1000. ? UI_STRINGS.FINISH_CUSTOMIZING_BUTTON_TEXT
  1001. : UI_STRINGS.SETTINGS_BUTTON_TEXT;
  1002. }
  1003. shortcutDisplaySpansMap.forEach((span, id) => {
  1004. const shortcut = currentKeybindingConfig.SHORTCUTS.find(
  1005. (s) => s.id === id
  1006. );
  1007. if (shortcut) {
  1008. span.textContent = formatShortcutForDisplay(shortcut.key);
  1009. span.classList.remove(
  1010. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT,
  1011. CSS_CLASSES.HELP_PANEL_KEY_LISTENING
  1012. );
  1013. if (
  1014. isCustomizingShortcuts &&
  1015. !shortcut.isSettingsEntry &&
  1016. !shortcut.nonConfigurable
  1017. ) {
  1018. if (
  1019. activeCustomizationTarget &&
  1020. activeCustomizationTarget.shortcutId === id
  1021. ) {
  1022. span.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  1023. span.classList.add(CSS_CLASSES.HELP_PANEL_KEY_LISTENING);
  1024. } else {
  1025. span.classList.add(
  1026. CSS_CLASSES.HELP_PANEL_KEY_CONFIGURABLE_HIGHLIGHT
  1027. );
  1028. }
  1029. }
  1030. }
  1031. });
  1032. }
  1033.  
  1034. clearTimeout(panelCloseTimer);
  1035. const isCurrentlyVisibleByClass = helpPanelElement.classList.contains(
  1036. CSS_CLASSES.HELP_PANEL_VISIBLE
  1037. );
  1038. let currentOpacity = 0;
  1039. if (
  1040. isCurrentlyVisibleByClass ||
  1041. helpPanelElement.style.animationName ===
  1042. ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT
  1043. ) {
  1044. currentOpacity = parseFloat(getComputedStyle(helpPanelElement).opacity);
  1045. }
  1046. const isAnimatingOut =
  1047. helpPanelElement.style.animationName ===
  1048. ELEMENT_IDS.HELP_PANEL_ANIMATE_OUT;
  1049.  
  1050. if (isCurrentlyVisibleByClass && currentOpacity > 0.01 && !isAnimatingOut) {
  1051. if (isCustomizingShortcuts && !activeCustomizationTarget) {
  1052. const settingsButton = helpPanelElement.querySelector(
  1053. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  1054. );
  1055. if (settingsButton) toggleCustomizationMode(settingsButton);
  1056. }
  1057. closeHelpPanel();
  1058. } else {
  1059. helpPanelElement.style.animation = "";
  1060. helpPanelElement.style.opacity = "0";
  1061. helpPanelElement.style.visibility = "hidden";
  1062.  
  1063. requestAnimationFrame(() => {
  1064. helpPanelElement.classList.add(CSS_CLASSES.HELP_PANEL_VISIBLE);
  1065. helpPanelElement.style.visibility = "visible";
  1066. helpPanelElement.style.animation = `${ELEMENT_IDS.HELP_PANEL_ANIMATE_IN} ${UI_SETTINGS.ANIMATION_DURATION_MS}ms ${UI_SETTINGS.ANIMATION_EASING_POP_OUT} forwards`;
  1067. });
  1068.  
  1069. setTimeout(() => {
  1070. window.addEventListener("click", handlePanelInteractionOutside, true);
  1071. window.addEventListener("keydown", handlePanelEscapeKey, true);
  1072. }, 0);
  1073. }
  1074. }
  1075.  
  1076. const debouncedToggleHelpPanelVisibility = debounce(
  1077. toggleHelpPanelVisibility,
  1078. UI_SETTINGS.DEBOUNCE_DELAY_MS
  1079. );
  1080.  
  1081. function createKeyboardEventHandler() {
  1082. const specialActionHandlers = {
  1083. toggleHelpPanel: debouncedToggleHelpPanelVisibility,
  1084. toggleCustomizationMode: () => {
  1085. if (helpPanelElement) {
  1086. const settingsButton = helpPanelElement.querySelector(
  1087. `.${CSS_CLASSES.HELP_PANEL_KEY_SETTING}`
  1088. );
  1089. if (settingsButton) toggleCustomizationMode(settingsButton);
  1090. }
  1091. },
  1092. };
  1093.  
  1094. const shortcutActionMap = {};
  1095. currentKeybindingConfig.SHORTCUTS.forEach((shortcut) => {
  1096. if (shortcut.key && !shortcut.isSettingsEntry) {
  1097. const lowerKey = shortcut.key.toLowerCase();
  1098. if (
  1099. shortcut.actionIdentifier &&
  1100. specialActionHandlers[shortcut.actionIdentifier]
  1101. ) {
  1102. shortcutActionMap[lowerKey] =
  1103. specialActionHandlers[shortcut.actionIdentifier];
  1104. } else if (shortcut.selectorConfig) {
  1105. shortcutActionMap[lowerKey] = () =>
  1106. triggerElementClick(shortcut.selectorConfig);
  1107. }
  1108. }
  1109. });
  1110.  
  1111. return function handleKeyDown(event) {
  1112. if (event.key === "Escape") {
  1113. return;
  1114. }
  1115.  
  1116. if (activeCustomizationTarget) {
  1117. event.preventDefault();
  1118. event.stopPropagation();
  1119.  
  1120. const newKey = event.key;
  1121. const targetSpan = activeCustomizationTarget.spanElement;
  1122.  
  1123. if (
  1124. newKey &&
  1125. newKey.length === 1 &&
  1126. !event.ctrlKey &&
  1127. !event.altKey &&
  1128. !event.shiftKey &&
  1129. !event.metaKey &&
  1130. !["Control", "Alt", "Shift", "Meta"].includes(newKey)
  1131. ) {
  1132. if (newKey.match(/^[a-zA-Z0-9]$/i)) {
  1133. const conflictingShortcut = currentKeybindingConfig.SHORTCUTS.find(
  1134. (s) =>
  1135. s.key &&
  1136. s.key.toLowerCase() === newKey.toLowerCase() &&
  1137. s.id !== activeCustomizationTarget.shortcutId
  1138. );
  1139. if (conflictingShortcut) {
  1140. targetSpan.textContent = `${
  1141. UI_STRINGS.KEY_CONFLICT_TEXT_PREFIX
  1142. }${newKey.toUpperCase()}${UI_STRINGS.KEY_CONFLICT_TEXT_SUFFIX}`;
  1143. setTimeout(() => {
  1144. if (
  1145. activeCustomizationTarget &&
  1146. activeCustomizationTarget.spanElement === targetSpan
  1147. ) {
  1148. targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  1149. }
  1150. }, 2000);
  1151. return;
  1152. }
  1153. saveCustomShortcut(activeCustomizationTarget.shortcutId, newKey);
  1154. updateShortcutDisplay(activeCustomizationTarget.shortcutId, newKey);
  1155. setListeningState(
  1156. activeCustomizationTarget.shortcutId,
  1157. targetSpan,
  1158. false
  1159. );
  1160. } else {
  1161. targetSpan.classList.add(CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE);
  1162. setTimeout(() => {
  1163. if (
  1164. activeCustomizationTarget &&
  1165. activeCustomizationTarget.spanElement === targetSpan
  1166. ) {
  1167. targetSpan.classList.remove(
  1168. CSS_CLASSES.HELP_PANEL_KEY_INVALID_SHAKE
  1169. );
  1170. }
  1171. }, 500);
  1172. }
  1173. } else if (!["Control", "Alt", "Shift", "Meta"].includes(newKey)) {
  1174. if (event[currentKeybindingConfig.MODIFIERS.EVENT_PROPERTY]) {
  1175. const conflictingShortcut = currentKeybindingConfig.SHORTCUTS.find(
  1176. (s) =>
  1177. s.key &&
  1178. s.key.toLowerCase() === newKey.toLowerCase() &&
  1179. s.id !== activeCustomizationTarget.shortcutId
  1180. );
  1181. if (conflictingShortcut) {
  1182. targetSpan.textContent = `${
  1183. UI_STRINGS.KEY_CONFLICT_TEXT_PREFIX
  1184. }${newKey.toUpperCase()}${UI_STRINGS.KEY_CONFLICT_TEXT_SUFFIX}`;
  1185. setTimeout(() => {
  1186. if (
  1187. activeCustomizationTarget &&
  1188. activeCustomizationTarget.spanElement === targetSpan
  1189. ) {
  1190. targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  1191. }
  1192. }, 2000);
  1193. return;
  1194. }
  1195. saveCustomShortcut(activeCustomizationTarget.shortcutId, newKey);
  1196. updateShortcutDisplay(activeCustomizationTarget.shortcutId, newKey);
  1197. setListeningState(
  1198. activeCustomizationTarget.shortcutId,
  1199. targetSpan,
  1200. false
  1201. );
  1202. } else {
  1203. targetSpan.textContent = `${UI_STRINGS.INVALID_MODIFIER_TEXT_PREFIX}${currentKeybindingConfig.MODIFIERS.CHARACTER_DISPLAY}${UI_STRINGS.INVALID_MODIFIER_TEXT_SUFFIX}`;
  1204. setTimeout(() => {
  1205. if (
  1206. activeCustomizationTarget &&
  1207. activeCustomizationTarget.spanElement === targetSpan
  1208. ) {
  1209. targetSpan.textContent = UI_STRINGS.PRESS_NEW_SHORTCUT_TEXT;
  1210. }
  1211. }, 2000);
  1212. }
  1213. }
  1214. return;
  1215. }
  1216.  
  1217. if (isCustomizingShortcuts) {
  1218. return;
  1219. }
  1220.  
  1221. if (!event[currentKeybindingConfig.MODIFIERS.EVENT_PROPERTY]) {
  1222. return;
  1223. }
  1224.  
  1225. const pressedKey = event.key.toLowerCase();
  1226. const actionToExecute = shortcutActionMap[pressedKey];
  1227.  
  1228. if (typeof actionToExecute === "function") {
  1229. actionToExecute();
  1230. event.preventDefault();
  1231. event.stopPropagation();
  1232. }
  1233. };
  1234. }
  1235.  
  1236. function initializeScript() {
  1237. loadCustomShortcuts();
  1238. injectUserInterfaceStyles();
  1239. keydownEventListener = createKeyboardEventHandler();
  1240. window.addEventListener("keydown", keydownEventListener, true);
  1241. }
  1242.  
  1243. if (document.readyState === "loading") {
  1244. document.addEventListener("DOMContentLoaded", initializeScript, {
  1245. once: true,
  1246. });
  1247. } else {
  1248. initializeScript();
  1249. }
  1250. })();