记录位置

允许用户自定义快捷键记录滚动位置,记录快捷键默认 Ctrl+O,恢复位置快捷键默认 Ctrl+Shift+H。提供保存位置、恢复位置、一键清理记录的菜单选项。恢复时在记录中找到匹配的完整网址记录,跳转到最近的匹配网址并平滑滚动恢复位置。进入网页时检查是否有记录,如果有则自动跳转到保存位置,主页面不自动恢复位置。

  1. // ==UserScript==
  2. // @name 记录位置
  3. // @version 3.1
  4. // @description 允许用户自定义快捷键记录滚动位置,记录快捷键默认 Ctrl+O,恢复位置快捷键默认 Ctrl+Shift+H。提供保存位置、恢复位置、一键清理记录的菜单选项。恢复时在记录中找到匹配的完整网址记录,跳转到最近的匹配网址并平滑滚动恢复位置。进入网页时检查是否有记录,如果有则自动跳转到保存位置,主页面不自动恢复位置。
  5. // @author hiisme
  6. // @match *://*/*
  7. // @grant GM_setValue
  8. // @grant GM_getValue
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_notification
  11. // @namespace https://greasyfork.org/users/217852
  12. // ==/UserScript==
  13.  
  14. (async () => {
  15. const MAX_STORED_PAGES = 20;
  16. const DEFAULT_SAVE_SHORTCUT = 'Ctrl+O';
  17. const DEFAULT_RESTORE_SHORTCUT = 'Ctrl+Shift+H';
  18.  
  19. let saveShortcut = await GM_getValue('saveShortcut', DEFAULT_SAVE_SHORTCUT);
  20. let restoreShortcut = await GM_getValue('restoreShortcut', DEFAULT_RESTORE_SHORTCUT);
  21.  
  22. const getStorageKey = () => `scrollPosition_${window.location.href}`;
  23.  
  24. const saveScrollPosition = async () => {
  25. const storedData = await GM_getValue('scrollPositions', {});
  26. storedData[getStorageKey()] = Math.round(window.scrollY);
  27. await GM_setValue('scrollPositions', storedData);
  28. manageStoredKeys(storedData);
  29. showNotification('塞进了回忆');
  30. };
  31.  
  32. const smoothScrollTo = (position) => {
  33. window.scrollTo({
  34. top: position,
  35. behavior: 'smooth'
  36. });
  37. };
  38.  
  39. const restoreScrollPosition = async () => {
  40. const storedData = await GM_getValue('scrollPositions', {});
  41. const domain = new URL(window.location.href).origin;
  42. const keys = Object.keys(storedData);
  43. const closestMatch = keys
  44. .filter(key => key.includes(domain))
  45. .sort()
  46. .pop();
  47.  
  48. if (closestMatch) {
  49. const targetUrl = closestMatch.split('_')[1];
  50. if (targetUrl !== window.location.href) {
  51. await GM_setValue('scrollPositionsToRestore', {
  52. url: targetUrl,
  53. position: storedData[closestMatch]
  54. });
  55. window.location.href = targetUrl;
  56. } else {
  57. smoothScrollTo(storedData[closestMatch]);
  58. showNotification('已回到从前');
  59. }
  60. } else {
  61. showNotification('没有找到回忆');
  62. }
  63. };
  64.  
  65. const clearAllPositions = async () => {
  66. await GM_setValue('scrollPositions', {});
  67. showNotification('所有的回忆都消失了');
  68. };
  69.  
  70. const manageStoredKeys = (storedData) => {
  71. const keys = Object.keys(storedData);
  72. if (keys.length > MAX_STORED_PAGES) {
  73. keys
  74. .sort((a, b) => storedData[a] - storedData[b])
  75. .slice(0, keys.length - MAX_STORED_PAGES)
  76. .forEach(key => delete storedData[key]);
  77. GM_setValue('scrollPositions', storedData);
  78. }
  79. };
  80.  
  81. const showNotification = (message) => {
  82. GM_notification({
  83. text: message,
  84. title: '脚本通知',
  85. timeout: 3000
  86. });
  87. };
  88.  
  89. const isValidShortcut = (shortcut) => /^((Ctrl|Shift|Alt)\+)*[A-Z]$/.test(shortcut);
  90.  
  91. const handleKeyboardEvent = (event) => {
  92. const modifierKeys = `${event.ctrlKey ? 'Ctrl+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.altKey ? 'Alt+' : ''}${event.key.toUpperCase()}`;
  93. if (modifierKeys === saveShortcut) {
  94. saveScrollPosition();
  95. event.preventDefault();
  96. } else if (modifierKeys === restoreShortcut) {
  97. restoreScrollPosition();
  98. event.preventDefault();
  99. }
  100. };
  101.  
  102. const setupEventListeners = () => {
  103. window.addEventListener('keydown', handleKeyboardEvent);
  104. };
  105.  
  106. GM_registerMenuCommand('塞进回忆', saveScrollPosition);
  107. GM_registerMenuCommand('回忆从前', restoreScrollPosition);
  108. GM_registerMenuCommand('清空回忆', clearAllPositions);
  109.  
  110. GM_registerMenuCommand('如何记忆时光', async () => {
  111. const newSaveShortcut = prompt('键入记忆时光的按钮 (例如 Ctrl+O)', saveShortcut);
  112. if (newSaveShortcut && isValidShortcut(newSaveShortcut)) {
  113. await GM_setValue('saveShortcut', newSaveShortcut);
  114. saveShortcut = newSaveShortcut;
  115. alert(`记录位置快捷键已设置为: ${newSaveShortcut}`);
  116. } else {
  117. alert('快捷键格式无效,请使用 Ctrl、Shift、Alt 组合键加单个字母。');
  118. }
  119. });
  120.  
  121. GM_registerMenuCommand('如何回到从前', async () => {
  122. const newRestoreShortcut = prompt('输入回到从前的按钮 (例如 Ctrl+Shift+H)', restoreShortcut);
  123. if (newRestoreShortcut && isValidShortcut(newRestoreShortcut)) {
  124. await GM_setValue('restoreShortcut', newRestoreShortcut);
  125. restoreShortcut = newRestoreShortcut;
  126. alert(`恢复位置快捷键已设置为: ${newRestoreShortcut}`);
  127. } else {
  128. alert('快捷键格式无效,请使用 Ctrl、Shift、Alt 组合键加单个字母。');
  129. }
  130. });
  131.  
  132. const checkForPendingRestore = async () => {
  133. const pendingData = await GM_getValue('scrollPositionsToRestore', null);
  134. if (pendingData) {
  135. await GM_setValue('scrollPositionsToRestore', null);
  136. window.addEventListener('load', () => {
  137. smoothScrollTo(pendingData.position);
  138. showNotification('回忆从前');
  139. }, { once: true });
  140. window.location.href = pendingData.url;
  141. }
  142. };
  143.  
  144. const initialize = async () => {
  145. setupEventListeners();
  146. await checkForPendingRestore();
  147. window.addEventListener('load', async () => {
  148. const storedData = await GM_getValue('scrollPositions', {});
  149. const currentKey = getStorageKey();
  150. if (storedData[currentKey]) {
  151. smoothScrollTo(storedData[currentKey]);
  152. showNotification('回忆从前');
  153. }
  154. });
  155. };
  156.  
  157. initialize();
  158. })();