[GitHub] Additional Hotkeys

Hotkeys and additional CSS improvements

  1. // ==UserScript==
  2. // @name [GitHub] Additional Hotkeys
  3. // @namespace https://github.com/oleglegun/github-additional-hotkeys
  4. // @version 0.1
  5. // @description Hotkeys and additional CSS improvements
  6. // @author oleglegun <oleg.legun@gmail.com>
  7. // @match https://github.com/*
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const DEBUG = true
  15. let firstKey
  16.  
  17. // g h - go to the profile
  18. // g p - go to the projects page
  19.  
  20. // Shift Enter - save edited note
  21. // Arrow Up/Down - select project link + added custom CSS
  22.  
  23. const keyG = 'KeyG'
  24. const keyH = 'KeyH'
  25. const keyP = 'KeyP'
  26. const keyN = 'KeyN'
  27. const keyE = 'KeyE'
  28. const keyEnter = 'Enter'
  29. const arrowUp = 'ArrowUp'
  30. const arrowDown = 'ArrowDown'
  31. const keyBackspace = 'Backspace'
  32.  
  33. const key1 = 'Digit1'
  34. const key2 = 'Digit2'
  35. const key3 = 'Digit3'
  36. const key4 = 'Digit4'
  37. const key5 = 'Digit5'
  38. const key6 = 'Digit6'
  39. const key7 = 'Digit7'
  40. const key8 = 'Digit8'
  41. const key9 = 'Digit9'
  42.  
  43. function userIsPrinting() {
  44. return ['INPUT', 'TEXTAREA'].indexOf(document.activeElement.tagName) !== -1
  45. }
  46.  
  47. function isProjectsTabOpened() {
  48. return window.location.search === '?tab=projects'
  49. }
  50.  
  51. function isProjectOpened() {
  52. return window.location.pathname.includes('/projects/')
  53. }
  54.  
  55. if (isProjectsTabOpened()) {
  56. // Focus on first project
  57. setTimeout(() => document.querySelector('#projects-results h4 a').focus(), 500)
  58. }
  59.  
  60. if (isProjectOpened()) {
  61. // Add numbers to columns
  62. // Focus on first column
  63. setTimeout(() => {
  64. const columnNamesElements = document.querySelectorAll('.js-project-columns-container .js-project-column-name')
  65.  
  66. columnNamesElements.forEach((nameElement, idx) => {
  67. nameElement.innerText = `${idx + 1} ${nameElement.innerText}`
  68. })
  69.  
  70. document.querySelector('.js-project-columns-container > .project-column').focus()
  71. }, 500)
  72.  
  73. }
  74.  
  75. document.addEventListener('keydown', function(e) {
  76. if (DEBUG) {
  77. console.log(e)
  78. }
  79.  
  80. switch(e.code) {
  81. case keyG:
  82. if (userIsPrinting()) return
  83.  
  84. firstKey = e.code
  85. break
  86. case keyH:
  87. if (userIsPrinting()) return
  88.  
  89. if (firstKey === keyG) {
  90. console.log('g h: Going Home')
  91. window.location.assign("/oleglegun");
  92.  
  93. firstKey = null
  94. }
  95. break
  96. case keyP:
  97. if (userIsPrinting()) return
  98. if (firstKey === keyG) {
  99. console.log('g p: Going to the Projects')
  100. e.stopPropagation()
  101. window.location.assign('/oleglegun?tab=projects')
  102. firstKey = null
  103. }
  104. break
  105.  
  106. case keyEnter:
  107. if (e.shiftKey) {
  108. const saveNoteButton = document.querySelector('button[data-disable-with="Saving note..."]')
  109.  
  110. if (!saveNoteButton) {
  111. return
  112. }
  113.  
  114. if (DEBUG) {
  115. console.log("Shift Enter: Saving Note")
  116. }
  117. saveNoteButton.click()
  118. }
  119. break
  120. case arrowUp:
  121. if (isProjectsTabOpened()) {
  122. const projectLinks = document.querySelectorAll('#projects-results h4 a')
  123.  
  124. let focusedLink
  125. let focusedLinkIdx
  126. let activeElement = document.activeElement
  127.  
  128. for (let i = 0; i < projectLinks.length; i++) {
  129. if (activeElement === projectLinks[i]) {
  130. focusedLink = projectLinks[i]
  131. focusedLinkIdx = i
  132. }
  133. }
  134.  
  135. if (!focusedLinkIdx) {
  136. projectLinks[0].focus()
  137. } else {
  138. projectLinks[focusedLinkIdx - 1].focus()
  139. }
  140. }
  141. break
  142. case arrowDown:
  143. if (isProjectsTabOpened()) {
  144. const projectLinks = document.querySelectorAll('#projects-results h4 a')
  145.  
  146. let focusedLink
  147. let focusedLinkIdx
  148. let activeElement = document.activeElement
  149.  
  150. for (let i = 0; i < projectLinks.length; i++) {
  151. if (activeElement === projectLinks[i]) {
  152. focusedLink = projectLinks[i]
  153. focusedLinkIdx = i
  154. }
  155. }
  156.  
  157. if (focusedLinkIdx === undefined) {
  158. projectLinks[0].focus()
  159. } else {
  160. if (focusedLinkIdx === projectLinks.length - 1) return
  161.  
  162. projectLinks[focusedLinkIdx + 1].focus()
  163. }
  164. }
  165. break
  166. case key1:
  167. case key2:
  168. case key3:
  169. case key4:
  170. case key5:
  171. case key6:
  172. case key7:
  173. case key8:
  174. case key9:
  175. if (userIsPrinting()) return
  176.  
  177. if (isProjectOpened()) {
  178. const columnElements = document.querySelectorAll('.js-project-columns-container > .project-column')
  179. const columnNumberToFocus = parseInt(e.key)
  180.  
  181. if (columnNumberToFocus <= columnElements.length) {
  182. columnElements[columnNumberToFocus - 1].focus()
  183. }
  184. }
  185. break
  186. case keyN:
  187. if (userIsPrinting()) return
  188.  
  189. if (isProjectOpened()) {
  190. // Click on "Add new card"
  191. const focusedElement = document.activeElement
  192. focusedElement &&
  193. focusedElement.classList.contains("project-column") &&
  194. focusedElement.querySelector('button[aria-label^="Add a note"]').click()
  195. e.preventDefault() // do not print this 'n' into Textarea
  196. }
  197. break
  198. case keyE:
  199. if (userIsPrinting()) return
  200.  
  201. if (isProjectOpened()) {
  202. const focusedElement = document.activeElement
  203. focusedElement &&
  204. focusedElement.classList.contains('issue-card') &&
  205. focusedElement.querySelector('button[data-dialog-id^="edit-note"]').click()
  206. }
  207. break
  208. case keyBackspace:
  209. if (userIsPrinting()) return
  210.  
  211. if (isProjectOpened()) {
  212. const focusedElement = document.activeElement
  213. focusedElement &&
  214. focusedElement.classList.contains('issue-card') &&
  215. focusedElement.querySelector('form.js-remove-card-after-request button[type="submit"]').click()
  216. }
  217. break
  218. }
  219. }, true)
  220.  
  221. })();