Quickly edit question for Superpower ChatGPT

Press Ctrl + Alt + Left click on the text of the question you asked to jump in at the current mouse position to quickly edit the question

  1. // ==UserScript==
  2. // @name Quickly edit question for Superpower ChatGPT
  3. // @description Press Ctrl + Alt + Left click on the text of the question you asked to jump in at the current mouse position to quickly edit the question
  4. // @author NWP
  5. // @namespace https://greasyfork.org/users/877912
  6. // @version 0.4
  7. // @license MIT
  8. // @match https://chat.openai.com/*
  9. // @match https://chatgpt.com/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. // TODO:
  14. // 1. Fix the screen flicker due to the fast jumping to the Save & Submit button and then
  15. // jumping back to the current position of the caret in the textarea, except for Chrome
  16. // 2. Launch a version for pure OpenAI
  17.  
  18. // DONE:
  19. // Fixed code for Firefox, enabled instant scrolling instead of smooth scrolling
  20. // Fixed flicker for Chrome
  21.  
  22. (function() {
  23.  
  24. if (navigator.userAgent.toLowerCase().includes('firefox')) {
  25. document.addEventListener('mousedown', function(event) {
  26. if (event.ctrlKey && event.altKey && event.button === 0) {
  27. const targetDiv = event.target;
  28. if (targetDiv.tagName === 'DIV') {
  29. let textNode, offset;
  30. if (document.caretPositionFromPoint) {
  31. const caretPos = document.caretPositionFromPoint(event.clientX, event.clientY);
  32. if (caretPos) {
  33. textNode = caretPos.offsetNode;
  34. offset = caretPos.offset;
  35. }
  36. } else if (document.caretRangeFromPoint) {
  37. const range = document.caretRangeFromPoint(event.clientX, event.clientY);
  38. if (range) {
  39. textNode = range.startContainer;
  40. offset = range.startOffset;
  41. }
  42. }
  43. if (textNode && (offset !== undefined)) {
  44. if (targetDiv) {
  45. const uniqueID = targetDiv.id.replace('message-text-', '');
  46. const button = document.getElementById(`message-edit-button-${uniqueID}`);
  47. if (button) {
  48. // scrollableContainer WILL LIKELY BREAK IN THE FUTURE
  49. const scrollableContainer = document.querySelector('div#conversation-inner-div.h-full.overflow-y-auto');
  50. const y = scrollableContainer.scrollTop;
  51.  
  52. button.click();
  53. event.preventDefault();
  54.  
  55. setTimeout(function() {
  56. const textarea = document.getElementById(`message-text-${uniqueID}`);
  57. if (textarea) {
  58. textarea.focus({preventScroll: true});
  59. textarea.setSelectionRange(offset, offset);
  60.  
  61. scrollableContainer.scrollTo({
  62. top: y,
  63. behavior: 'instant'
  64. })
  65. }
  66. }, 0);
  67. }
  68. }
  69. }
  70. }
  71. }
  72. });
  73.  
  74. console.log("The browser is Firefox.")
  75.  
  76. } else {
  77. document.addEventListener('mousedown', function(event) {
  78. if (event.ctrlKey && event.altKey && event.button === 0) {
  79. const targetDiv = event.target;
  80. if (targetDiv.tagName === 'DIV' && targetDiv.id.startsWith('message-text-')) {
  81. event.preventDefault();
  82. let textNode, offset;
  83. const pointMethod = document.caretPositionFromPoint ? 'caretPositionFromPoint' : 'caretRangeFromPoint';
  84. const caretResult = document[pointMethod](event.clientX, event.clientY);
  85.  
  86. if (caretResult) {
  87. textNode = caretResult.offsetNode || caretResult.startContainer;
  88. offset = caretResult.offset !== undefined ? caretResult.offset : caretResult.startOffset;
  89. }
  90.  
  91. if (textNode && offset !== undefined) {
  92. const uniqueID = targetDiv.id.replace('message-text-', '');
  93. const button = document.getElementById(`message-edit-button-${uniqueID}`);
  94. if (button) {
  95. // scrollableContainer WILL LIKELY BREAK IN THE FUTURE
  96. const scrollableContainer = document.querySelector('div#conversation-inner-div.h-full.overflow-y-auto');
  97. const originalScroll = { x: scrollableContainer.scrollLeft, y: scrollableContainer.scrollTop };
  98.  
  99. button.click();
  100.  
  101. const observer = new MutationObserver((mutations, obs) => {
  102. const textarea = document.getElementById(`message-text-${uniqueID}`);
  103. if (textarea && textarea.offsetHeight > 0 && document.activeElement === textarea) {
  104. textarea.setSelectionRange(offset, offset);
  105. scrollableContainer.scrollTo({
  106. top: originalScroll.y,
  107. behavior: 'instant'
  108. });
  109. obs.disconnect();
  110. }
  111. });
  112.  
  113. observer.observe(document.body, { childList: true, subtree: true });
  114. }
  115. }
  116. }
  117. }
  118. });
  119.  
  120. console.log("The browser is not Firefox.")
  121. }
  122.  
  123.  
  124. })();
  125.  
  126.  
  127. // When scrollableContainer breaks use this code to find the new scrollableContainer element:
  128.  
  129. // function isScrollable(element) {
  130. // const hasScrollableContent = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
  131. // const overflowStyle = window.getComputedStyle(element).overflow;
  132. // return hasScrollableContent && (overflowStyle === 'scroll' || overflowStyle === 'auto');
  133. // }
  134.  
  135. // // Find all elements in the body
  136. // const allElements = document.querySelectorAll('body, body *');
  137.  
  138. // // Counter variable to track index
  139. // let index = 1;
  140.  
  141. // // Filter out scrollable elements and apply a red border
  142. // Array.from(allElements).forEach(element => {
  143. // if (isScrollable(element)) {
  144. // element.style.border = '2px solid red';
  145. // console.log("Scrollable Element " + index + ":", element);
  146. // console.log("Scroll Position " + index + " (x, y):", element.scrollLeft, element.scrollTop);
  147. // index++; // Increment index for the next scrollable element
  148. // }
  149. // });