Google Docs Shortcuts

Adds shortcuts for text, highlight, and cell color, for opening Borders and Shading, for opening table border selector, for opening current tab outline, for switching to last open tab, for restoring scroll position of previous session.

目前为 2025-03-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Google Docs Shortcuts
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description Adds shortcuts for text, highlight, and cell color, for opening Borders and Shading, for opening table border selector, for opening current tab outline, for switching to last open tab, for restoring scroll position of previous session.
  6. // @match https://docs.google.com/*
  7. // @grant none
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. // ------------------------------
  15. // 1. Enlarge Table Border Menu
  16. // ------------------------------
  17. const borderMenuStyle = document.createElement('style');
  18. borderMenuStyle.textContent = `
  19. .goog-menu.goog-menu-vertical.docs-material[aria-label="Table border selection menu"] {
  20. transform: scale(2.5) !important;
  21. transform-origin: top left !important;
  22. }
  23. `;
  24. document.head.appendChild(borderMenuStyle);
  25.  
  26. // ------------------------------
  27. // 2. Color Shortcut Definitions
  28. // ------------------------------
  29. // Text Color shortcuts (Alt+1-4)
  30. const TEXT_COLOR_SHORTCUTS = {
  31. '4': { rgb: 'rgb(255, 51, 51)', hex: '#ff3333' },
  32. '3': { rgb: 'rgb(0, 96, 87)', hex: '#006057' },
  33. '2': { rgb: 'rgb(0, 167, 151)', hex: '#00a797' },
  34. '1': { rgb: 'rgb(0, 0, 0)', hex: '#000000' }
  35. };
  36.  
  37. // Highlight Color shortcuts (Alt+K and Alt+M)
  38. const HIGHLIGHT_COLOR_SHORTCUTS = {
  39. 'k': { rgb: 'rgb(243, 243, 243)', hex: '#f3f3f3' },
  40. 'm': { rgb: 'rgb(235, 242, 232)', hex: '#ebf2e8' }
  41. };
  42.  
  43. // Cell (Background) Color shortcut (Alt+P)
  44. const CELL_COLOR_SHORTCUTS = {
  45. 'p': { rgb: 'rgb(245, 146, 142)', hex: '#f5928e' }
  46. };
  47.  
  48. // ------------------------------
  49. // 3. Other Shortcut Constants
  50. // ------------------------------
  51. const TAB_SHORTCUT_KEY = '5'; // Alt+5
  52. const COLOR_SHORTCUT_KEY = '7'; // Alt+7 (opens text color menu)
  53. const SCROLL_SHORTCUT_KEY = '8'; // Alt+8
  54. const TAB_SWITCH_KEY_CODE = 87; // Alt+W
  55. const BORDER_SHADING_KEY_CODE = 71; // Alt+G
  56. const BORDER_SELECTION_KEY_CODE = 82; // Alt+R
  57.  
  58. const TAB_SWITCH_REFACTORY_PERIOD = 500;
  59. let lastSelectedTab = null;
  60. let currentSelectedTab = null;
  61. let isTabSwitchInProgress = false;
  62. let lastSelectedTabInterval = null; // For our interval
  63.  
  64. // ------------------------------
  65. // 4. Key Event Handlers
  66. // ------------------------------
  67. function handleKeydown(event) {
  68. if (event.altKey && !event.ctrlKey) {
  69. const key = event.key.toLowerCase();
  70. switch (key) {
  71. case TAB_SHORTCUT_KEY:
  72. event.preventDefault();
  73. event.stopImmediatePropagation();
  74. clickSelectedTab();
  75. break;
  76. case COLOR_SHORTCUT_KEY:
  77. event.preventDefault();
  78. event.stopImmediatePropagation();
  79. clickTextColorButton();
  80. break;
  81. case SCROLL_SHORTCUT_KEY:
  82. event.preventDefault();
  83. event.stopImmediatePropagation();
  84. restoreScrollPosition();
  85. break;
  86. case '4':
  87. case '3':
  88. case '2':
  89. case '1': {
  90. event.preventDefault();
  91. event.stopPropagation();
  92. event.stopImmediatePropagation();
  93. const { rgb, hex } = TEXT_COLOR_SHORTCUTS[key];
  94. clickColor(rgb, hex);
  95. break;
  96. }
  97. case 'k': {
  98. event.preventDefault();
  99. event.stopPropagation();
  100. event.stopImmediatePropagation();
  101. const { rgb, hex } = HIGHLIGHT_COLOR_SHORTCUTS[key];
  102. clickColorForMenu('highlight', 'div[aria-label="Highlight color"]', rgb, hex);
  103. break;
  104. }
  105. case 'm': {
  106. event.preventDefault();
  107. event.stopPropagation();
  108. event.stopImmediatePropagation();
  109. const { rgb, hex } = HIGHLIGHT_COLOR_SHORTCUTS[key];
  110. clickColorForMenu('highlight', 'div[aria-label="Highlight color"]', rgb, hex);
  111. break;
  112. }
  113. case 'p': {
  114. event.preventDefault();
  115. event.stopPropagation();
  116. event.stopImmediatePropagation();
  117. const { rgb, hex } = CELL_COLOR_SHORTCUTS[key];
  118. clickColorForMenu('cell', 'div[aria-label="Background color"]', rgb, hex);
  119. break;
  120. }
  121. }
  122. }
  123. }
  124.  
  125. function handleAltWKey(event) {
  126. if (event.altKey && event.keyCode === TAB_SWITCH_KEY_CODE) {
  127. event.preventDefault();
  128. event.stopImmediatePropagation();
  129. clickLastSelectedTab();
  130. }
  131. }
  132.  
  133. function handleAltGKey(event) {
  134. if (event.altKey && event.keyCode === BORDER_SHADING_KEY_CODE) {
  135. event.preventDefault();
  136. event.stopImmediatePropagation();
  137. clickBordersAndShading();
  138. }
  139. }
  140.  
  141. function handleAltRKey(event) {
  142. if (event.altKey && event.keyCode === BORDER_SELECTION_KEY_CODE) {
  143. event.preventDefault();
  144. event.stopImmediatePropagation();
  145. clickBordersSelectionButton();
  146. }
  147. }
  148.  
  149. function attachKeyListener() {
  150. const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
  151. if (iframe) {
  152. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  153. iframeDoc.addEventListener('keydown', handleKeydown, true);
  154. iframeDoc.addEventListener('keydown', handleAltWKey, true);
  155. iframeDoc.addEventListener('keydown', handleAltGKey, true);
  156. iframeDoc.addEventListener('keydown', handleAltRKey, true);
  157. console.log('Key listener attached to iframe.');
  158. } else {
  159. console.log('Iframe not found. Retrying...');
  160. setTimeout(attachKeyListener, 1000);
  161. }
  162. window.addEventListener('keydown', handleKeydown, true);
  163. window.addEventListener('keydown', handleAltWKey, true);
  164. window.addEventListener('keydown', handleAltGKey, true);
  165. window.addEventListener('keydown', handleAltRKey, true);
  166. console.log('Key listener attached to top window.');
  167. }
  168.  
  169. // ------------------------------
  170. // 5. Element Interaction Functions
  171. // ------------------------------
  172. function clickElement(element) {
  173. const mouseDown = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window });
  174. const mouseUp = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window });
  175. const clickEvt = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
  176. element.dispatchEvent(mouseDown);
  177. element.dispatchEvent(mouseUp);
  178. element.dispatchEvent(clickEvt);
  179. console.log('Simulated click on', element);
  180. }
  181.  
  182. function clickSelectedTab() {
  183. const tabElement = document.querySelector('.chapter-item-label-and-buttons-container[aria-selected="true"]');
  184. if (tabElement) {
  185. clickElement(tabElement);
  186. console.log('Tab clicked');
  187. } else {
  188. console.log('Tab element not found.');
  189. }
  190. }
  191.  
  192. // Click the Text Color button to load the text palette (if not already loaded)
  193. function clickTextColorButton() {
  194. const textColorButton = document.querySelector('div[aria-label="Text color"]');
  195. if (textColorButton) {
  196. clickElement(textColorButton);
  197. console.log('Text color button clicked');
  198. } else {
  199. console.log('Text color button not found.');
  200. }
  201. }
  202.  
  203. // ------------------------------
  204. // 6. Color Menu Functions
  205. // ------------------------------
  206. // Returns the menu element based on its inner text:
  207. // 'text': does NOT include "None" or "Transparent"
  208. // 'cell': includes "Transparent"
  209. // 'highlight': includes "None"
  210. function getColorMenu(menuType) {
  211. const menus = document.querySelectorAll('div.goog-menu.goog-menu-vertical.docs-colormenuitems.docs-material');
  212. for (const menu of menus) {
  213. const menuText = menu.textContent;
  214. if (menuType === 'text' && !menuText.includes('None') && !menuText.includes('Transparent')) {
  215. return menu;
  216. } else if (menuType === 'cell' && menuText.includes('Transparent')) {
  217. return menu;
  218. } else if (menuType === 'highlight' && menuText.includes('None')) {
  219. return menu;
  220. }
  221. }
  222. return null;
  223. }
  224.  
  225. // Text Color selection function
  226. function clickColor(rgb, hex) {
  227. let container = getColorMenu('text');
  228. if (!container) {
  229. console.log('Text menu not found; forcing open text menu...');
  230. clickTextColorButton();
  231. setTimeout(() => {
  232. container = getColorMenu('text');
  233. if (!container) {
  234. console.log('Text menu still not found.');
  235. return;
  236. }
  237. forceShowAndClick(container);
  238. }, 150);
  239. } else {
  240. forceShowAndClick(container);
  241. }
  242.  
  243. function forceShowAndClick(container) {
  244. const originalDisplay = container.style.display;
  245. if (getComputedStyle(container).display === 'none') {
  246. container.style.display = 'block';
  247. }
  248. const palettes = container.querySelectorAll('.docs-material-colorpalette');
  249. let colorElement = null;
  250. const normalizedRgb = rgb.replace(/\s/g, '');
  251. for (const palette of palettes) {
  252. const candidates = palette.querySelectorAll('.docs-material-colorpalette-colorswatch, .docs-material-colorpalette-colorswatch-color');
  253. for (const candidate of candidates) {
  254. const styleAttr = candidate.getAttribute('style') || '';
  255. if (styleAttr.replace(/\s/g, '').includes(normalizedRgb)) {
  256. colorElement = candidate;
  257. break;
  258. }
  259. }
  260. if (colorElement) break;
  261. }
  262. if (colorElement) {
  263. clickElement(colorElement);
  264. console.log(`Simulated click on color ${hex} in text menu.`);
  265. container.style.display = 'none';
  266. } else {
  267. console.log(`Color element for ${hex} not found in text menu using normalized RGB: ${normalizedRgb}`);
  268. container.style.display = originalDisplay;
  269. }
  270. }
  271. }
  272.  
  273. // Generic function for highlight and cell color selection
  274. // menuType: 'highlight' or 'cell'
  275. // buttonSelector: selector for the button that opens the menu
  276. function clickColorForMenu(menuType, buttonSelector, rgb, hex) {
  277. let container = getColorMenu(menuType);
  278. if (!container) {
  279. console.log(`${menuType} menu not found; forcing open menu using button ${buttonSelector}...`);
  280. const button = document.querySelector(buttonSelector);
  281. if (button) {
  282. clickElement(button);
  283. console.log(`Button for ${menuType} menu clicked.`);
  284. } else {
  285. console.log(`Button for ${menuType} menu not found.`);
  286. return;
  287. }
  288. setTimeout(() => {
  289. container = getColorMenu(menuType);
  290. if (!container) {
  291. console.log(`${menuType} menu still not found.`);
  292. return;
  293. }
  294. forceShowAndClick(container);
  295. }, 150);
  296. } else {
  297. forceShowAndClick(container);
  298. }
  299.  
  300. function forceShowAndClick(container) {
  301. const originalDisplay = container.style.display;
  302. if (getComputedStyle(container).display === 'none') {
  303. container.style.display = 'block';
  304. }
  305. const palettes = container.querySelectorAll('.docs-material-colorpalette');
  306. let colorElement = null;
  307. const normalizedRgb = rgb.replace(/\s/g, '');
  308. for (const palette of palettes) {
  309. const candidates = palette.querySelectorAll('.docs-material-colorpalette-colorswatch, .docs-material-colorpalette-colorswatch-color');
  310. for (const candidate of candidates) {
  311. const styleAttr = candidate.getAttribute('style') || '';
  312. if (styleAttr.replace(/\s/g, '').includes(normalizedRgb)) {
  313. colorElement = candidate;
  314. break;
  315. }
  316. }
  317. if (colorElement) break;
  318. }
  319. if (colorElement) {
  320. clickElement(colorElement);
  321. console.log(`Simulated click on color ${hex} in ${menuType} menu.`);
  322. container.style.display = 'none';
  323. } else {
  324. console.log(`Color element for ${hex} not found in ${menuType} menu using normalized RGB: ${normalizedRgb}`);
  325. container.style.display = originalDisplay;
  326. }
  327. }
  328. }
  329.  
  330. // ------------------------------
  331. // 7. Wait-for-Element-Gone Helper
  332. // ------------------------------
  333. // Polls until an element matching the selector is no longer present, then calls callback.
  334. function waitForElementGone(selector, callback) {
  335. const checkGone = () => {
  336. if (!document.querySelector(selector)) {
  337. callback();
  338. } else {
  339. setTimeout(checkGone, 300);
  340. }
  341. };
  342. checkGone();
  343. }
  344.  
  345. // ------------------------------
  346. // 8. Modified Borders and Shading Function
  347. // ------------------------------
  348. function clickBordersAndShading() {
  349. // Try direct click first
  350. let bordersAndShadingButton = document.querySelector('span[aria-label^="Borders and shading"]');
  351. if (bordersAndShadingButton) {
  352. clickElement(bordersAndShadingButton);
  353. console.log('Borders and shading menu clicked directly.');
  354. return;
  355. }
  356.  
  357. console.log('Direct Borders and shading menu not found, attempting Paragraph styles fallback.');
  358.  
  359. // Fallback: Click Paragraph styles button to load Borders and shading
  360. const paragraphStylesButton = document.querySelector('span[aria-label="Paragraph styles p"]');
  361. if (paragraphStylesButton) {
  362. clickElement(paragraphStylesButton);
  363. console.log('Paragraph styles button clicked.');
  364.  
  365. // Wait for Borders and shading button to appear
  366. waitForElement('span[aria-label^="Borders and shading"]', function (bordersBtn) {
  367. clickElement(bordersBtn);
  368. console.log('Borders and shading button clicked from Paragraph styles menu.');
  369.  
  370. // Close Paragraph styles menu explicitly after clicking Borders and shading
  371. closeMenusExcept([]);
  372. });
  373. return;
  374. }
  375.  
  376. // Additional fallback: Try Insert menu → Paragraph styles
  377. const insertMenu = document.querySelector('div#docs-insert-menu');
  378. if (insertMenu) {
  379. clickElement(insertMenu);
  380. console.log('Insert menu clicked.');
  381.  
  382. waitForElement('span[aria-label="Paragraph styles p"]', function (psButton) {
  383. clickElement(psButton);
  384. console.log('Paragraph styles button clicked from Insert menu.');
  385.  
  386. waitForElement('span[aria-label^="Borders and shading"]', function (bordersBtn) {
  387. clickElement(bordersBtn);
  388. console.log('Borders and shading button clicked after Insert → Paragraph styles.');
  389.  
  390. // Close Insert menu explicitly after clicking Borders and shading
  391. closeMenusExcept([]);
  392. });
  393. });
  394. return;
  395. }
  396.  
  397. console.log('All fallbacks failed: Borders and shading button not found.');
  398. }
  399.  
  400. // Helper to close unwanted menus after fallback actions
  401. function closeMenusExcept(exceptionsSelectors = []) {
  402. const allMenus = document.querySelectorAll('div.goog-menu.goog-menu-vertical.docs-material.shell-menu.goog-menu-noaccel, div.goog-menu.goog-menu-vertical.docs-material');
  403.  
  404. allMenus.forEach(menu => {
  405. if (!exceptionsSelectors.some(selector => menu.matches(selector))) {
  406. menu.style.display = 'none';
  407. console.log('Closed unwanted menu:', menu);
  408. }
  409. });
  410.  
  411. // Additionally, click on document body to remove any residual menu overlays
  412. document.body.click();
  413. }
  414.  
  415. // ------------------------------
  416. // 9. Utility Functions
  417. // ------------------------------
  418. function getDocumentId() {
  419. const url = new URL(window.location.href);
  420. url.hash = '';
  421. return url.toString();
  422. }
  423.  
  424. function saveScrollPosition() {
  425. const documentId = getDocumentId();
  426. const scrollableElement = document.querySelector('.kix-appview-editor');
  427. if (scrollableElement) {
  428. const scrollPosition = scrollableElement.scrollTop;
  429. const scrollData = JSON.parse(localStorage.getItem('googleDocsScrollData') || '{}');
  430. scrollData[documentId] = scrollPosition;
  431. localStorage.setItem('googleDocsScrollData', JSON.stringify(scrollData));
  432. console.log('Scroll position saved for document:', documentId, scrollPosition);
  433. }
  434. }
  435.  
  436. function restoreScrollPosition() {
  437. const documentId = getDocumentId();
  438. const scrollData = JSON.parse(localStorage.getItem('googleDocsScrollData') || '{}');
  439. const scrollPosition = scrollData[documentId];
  440. const scrollableElement = document.querySelector('.kix-appview-editor');
  441. if (scrollableElement && scrollPosition !== undefined) {
  442. scrollableElement.scrollTo(0, parseInt(scrollPosition, 10));
  443. console.log('Scroll position restored for document:', documentId, scrollPosition);
  444. } else {
  445. console.log('No scroll position saved for this document.');
  446. }
  447. }
  448.  
  449. function getTabsAndSubtabs() {
  450. const treeItems = document.querySelectorAll('[role="treeitem"]');
  451. return Array.from(treeItems).filter(item => {
  452. const ariaLabel = item.getAttribute('aria-label');
  453. return ariaLabel && !ariaLabel.toLowerCase().includes('level');
  454. });
  455. }
  456.  
  457. function getLastSelectedTab() {
  458. const selectedTab = document.querySelector('.chapter-item-label-and-buttons-container[aria-selected="true"]');
  459. if (selectedTab) {
  460. if (currentSelectedTab !== selectedTab) {
  461. lastSelectedTab = currentSelectedTab;
  462. }
  463. currentSelectedTab = selectedTab;
  464. console.log('Current selected tab:', selectedTab.getAttribute('aria-label'));
  465. } else {
  466. console.log('No tab is currently selected.');
  467. }
  468. }
  469.  
  470. function clickLastSelectedTab() {
  471. if (isTabSwitchInProgress) return;
  472. if (lastSelectedTab && lastSelectedTab !== currentSelectedTab) {
  473. console.log('Clicking on last selected tab:', lastSelectedTab.getAttribute('aria-label'));
  474. isTabSwitchInProgress = true;
  475. clickElement(lastSelectedTab);
  476. const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
  477. if (iframe) {
  478. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  479. iframe.focus();
  480. iframeDoc.body.click();
  481. console.log('Focus set inside the document and caret activated!');
  482. }
  483. setTimeout(() => {
  484. isTabSwitchInProgress = false;
  485. }, TAB_SWITCH_REFACTORY_PERIOD);
  486. } else {
  487. console.log('No valid last selected tab found.');
  488. }
  489. }
  490.  
  491. // ------------------------------
  492. // 10. Initialization and Refresh
  493. // ------------------------------
  494. function initialize() {
  495. console.log('Userscript initialized. Ready to detect shortcuts.');
  496. lastSelectedTabInterval = setInterval(getLastSelectedTab, 1000);
  497. attachKeyListener();
  498. }
  499.  
  500. window.addEventListener('beforeunload', saveScrollPosition);
  501. window.addEventListener('load', initialize);
  502.  
  503. function removeKeyListeners() {
  504. window.removeEventListener('keydown', handleKeydown, true);
  505. window.removeEventListener('keydown', handleAltWKey, true);
  506. window.removeEventListener('keydown', handleAltGKey, true);
  507. window.removeEventListener('keydown', handleAltRKey, true);
  508. const iframe = document.querySelector('iframe.docs-texteventtarget-iframe');
  509. if (iframe) {
  510. const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  511. iframeDoc.removeEventListener('keydown', handleKeydown, true);
  512. iframeDoc.removeEventListener('keydown', handleAltWKey, true);
  513. iframeDoc.removeEventListener('keydown', handleAltGKey, true);
  514. iframeDoc.removeEventListener('keydown', handleAltRKey, true);
  515. }
  516. }
  517.  
  518. function refreshScript() {
  519. if (lastSelectedTabInterval) {
  520. clearInterval(lastSelectedTabInterval);
  521. lastSelectedTabInterval = null;
  522. }
  523. removeKeyListeners();
  524. lastSelectedTab = null;
  525. currentSelectedTab = null;
  526. isTabSwitchInProgress = false;
  527. initialize();
  528. console.log('Docs Shortcuts script reinitialized.');
  529. }
  530.  
  531. function waitForElement(selector, callback) {
  532. const element = document.querySelector(selector);
  533. if (element) {
  534. callback(element);
  535. } else {
  536. setTimeout(() => waitForElement(selector, callback), 300);
  537. }
  538. }
  539.  
  540. function loadGoogleMaterialSymbols() {
  541. if (!document.querySelector('link[href*="fonts.googleapis.com/css2?family=Material+Symbols+Outlined"]')) {
  542. const link = document.createElement('link');
  543. link.rel = 'stylesheet';
  544. link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200';
  545. document.head.appendChild(link);
  546. }
  547. }
  548.  
  549. function createRefreshButton() {
  550. const button = document.createElement('div');
  551. button.setAttribute('role', 'button');
  552. button.id = 'refresh-button';
  553. button.className = "goog-inline-block jfk-button jfk-button-standard docs-appbar-circle-button docs-titlebar-button";
  554. button.setAttribute('aria-disabled', 'false');
  555. button.setAttribute('aria-label', 'Refresh Docs Shortcuts');
  556. button.tabIndex = 0;
  557. button.setAttribute('data-tooltip', 'Refresh Docs Shortcuts');
  558. button.style.userSelect = 'none';
  559. button.style.marginRight = "8px";
  560.  
  561. const iconWrapper = document.createElement('div');
  562. iconWrapper.className = "docs-icon goog-inline-block";
  563.  
  564. const icon = document.createElement('span');
  565. icon.className = "material-symbols-outlined";
  566. icon.textContent = "refresh";
  567.  
  568. iconWrapper.appendChild(icon);
  569. button.appendChild(iconWrapper);
  570.  
  571. return button;
  572. }
  573.  
  574. function injectRefreshButtonStyles() {
  575. const style = document.createElement('style');
  576. style.textContent = `
  577. #refresh-button {
  578. display: flex;
  579. align-items: center;
  580. justify-content: center;
  581. }
  582. #refresh-button .docs-icon {
  583. display: flex;
  584. align-items: center;
  585. justify-content: center;
  586. width: 100%;
  587. height: 100%;
  588. }
  589. #refresh-button .material-symbols-outlined {
  590. font-size: 26px;
  591. color: #444746;
  592. transition: color 0.2s ease, font-variation-settings 0.2s ease;
  593. font-variation-settings: 'FILL' 0;
  594. }
  595. #refresh-button.active .material-symbols-outlined {
  596. color: #50A387;
  597. font-variation-settings: 'FILL' 1;
  598. }
  599. #refresh-button.jfk-button-hover .material-symbols-outlined {
  600. color: #333;
  601. }
  602. #refresh-button.active.jfk-button-hover .material-symbols-outlined {
  603. color: #50A387;
  604. font-variation-settings: 'FILL' 1;
  605. }
  606. `;
  607. document.head.appendChild(style);
  608. }
  609.  
  610. waitForElement('.docs-revisions-appbarbutton-container', (revisionsContainer) => {
  611. loadGoogleMaterialSymbols();
  612. injectRefreshButtonStyles();
  613. const refreshButton = createRefreshButton();
  614. revisionsContainer.parentNode.insertBefore(refreshButton, revisionsContainer);
  615.  
  616. refreshButton.addEventListener('mouseenter', () => {
  617. refreshButton.classList.add('jfk-button-hover');
  618. });
  619. refreshButton.addEventListener('mouseleave', () => {
  620. refreshButton.classList.remove('jfk-button-hover');
  621. refreshButton.blur();
  622. });
  623. refreshButton.addEventListener('focus', () => {
  624. refreshButton.classList.remove('jfk-button-hover');
  625. });
  626. refreshButton.addEventListener('click', () => {
  627. console.log('Refresh button clicked. Reinitializing userscript...');
  628. refreshScript();
  629. });
  630. });
  631. })();