StackOverflow close votes shortcuts

A script to add keyboard shortcuts to StackOverflow's close votes review queue

  1. // ==UserScript==
  2. // @name StackOverflow close votes shortcuts
  3. // @namespace albireo.stackoverflow.close-votes-shortcuts
  4. // @version 1.0.0-rc.5
  5. // @description A script to add keyboard shortcuts to StackOverflow's close votes review queue
  6. // @author Albireo
  7. // @match http://stackoverflow.com/review/close*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13. $(document).ready(function () {
  14. var keys = {
  15. '1': 49,
  16. '2': 50,
  17. '3': 51,
  18. '4': 52,
  19. '5': 53,
  20. '6': 54,
  21. '7': 55,
  22. '8': 56,
  23. '9': 57,
  24. '0': 48
  25. };
  26. var configuration = {
  27. 'actions': {
  28. 'leaveOpen': { 'key': '1', 'value': '8' },
  29. 'close': { 'key': '2', 'value': '6' },
  30. 'edit': { 'key': '3', 'value': '5' },
  31. 'skip': { 'key': '4', 'value': '1' }
  32. },
  33. 'closeReasons': {
  34. 'duplicate': { 'key': '1', 'value': 'Duplicate' },
  35. 'offTopic': { 'key': '2', 'value': 'OffTopic' },
  36. 'unclear': { 'key': '3', 'value': 'Unclear' },
  37. 'tooBroad': { 'key': '4', 'value': 'TooBroad' },
  38. 'opinionBased': { 'key': '5', 'value': 'OpinionBased' }
  39. },
  40. 'offTopicReasons': {
  41. 'superUser': { 'key': '1', 'value': '4' },
  42. 'serverFault': { 'key': '2', 'value': '7' },
  43. 'recommend': { 'key': '3', 'value': '16' },
  44. 'minimalProgram': { 'key': '4', 'value': '13' },
  45. 'typo': { 'key': '5', 'value': '11' },
  46. 'migration': { 'key': '6', 'value': '2' },
  47. 'other': { 'key': '7', 'value': '3' },
  48. },
  49. 'migrationReasons': {
  50. 'meta': { 'key': '1', 'value': 'meta.stackoverflow.com' },
  51. 'superUser': { 'key': '2', 'value': 'superuser.com' },
  52. 'tex': { 'key': '3', 'value': 'tex.stackexchange.com' },
  53. 'dba': { 'key': '4', 'value': 'dba.stackexchange.com' },
  54. 'stats': { 'key': '5', 'value': 'stats.stackexchange.com' },
  55. }
  56. };
  57. (function () {
  58. var states = {
  59. atQuestion: 1,
  60. atCloseReason: 2,
  61. atDuplicate: 3,
  62. atOffTopic: 4,
  63. atOtherSite: 5
  64. };
  65. var state = states.atQuestion;
  66. var clickElement = function (selector) {
  67. $(selector).focus().click();
  68. };
  69. var clickAction = function (action) {
  70. clickElement('.review-actions [data-result-type="' + action + '"]');
  71. };
  72. var clickCloseReason = function (reason) {
  73. clickElement('[name="close-reason"][value="' + reason + '"]');
  74. };
  75. var clickOffTopicReason = function (reason) {
  76. clickElement('[name="close-as-off-topic-reason"][value="' + reason + '"][data-other-comment-id=""]');
  77. };
  78. var clickOtherSite = function (site) {
  79. clickElement('[name="migration"][value="' + site + '"]');
  80. };
  81. var keyHandler = function(key) {
  82. switch (state) {
  83. case states.atQuestion:
  84. actionHandler(key);
  85. break;
  86. case states.atCloseReason:
  87. closeReasonHandler(key);
  88. break;
  89. case states.atOffTopic:
  90. offTopicHandler(key);
  91. break;
  92. case states.atOtherSite:
  93. otherSiteHandler(key);
  94. break;
  95. }
  96. };
  97. var actionHandler = function (key) {
  98. switch (key) {
  99. case keys[configuration.actions.leaveOpen.key]:
  100. clickAction(configuration.actions.leaveOpen.value);
  101. resetState();
  102. break;
  103. case keys[configuration.actions.close.key]:
  104. state = states.atCloseReason;
  105. clickAction(configuration.actions.close.value);
  106. break;
  107. case keys[configuration.actions.edit.key]:
  108. clickAction(configuration.actions.edit.value);
  109. resetState();
  110. break;
  111. case keys[configuration.actions.skip.key]:
  112. clickAction(configuration.actions.skip.value);
  113. resetState();
  114. break;
  115. }
  116. };
  117. var closeReasonHandler = function (key) {
  118. switch (key) {
  119. case keys[configuration.closeReasons.duplicate.key]:
  120. clickCloseReason(configuration.closeReasons.duplicate.value);
  121. state = states.atDuplicate;
  122. break;
  123. case keys[configuration.closeReasons.offTopic.key]:
  124. clickCloseReason(configuration.closeReasons.offTopic.value);
  125. state = states.atOffTopic;
  126. break;
  127. case keys[configuration.closeReasons.unclear.key]:
  128. clickCloseReason(configuration.closeReasons.unclear.value);
  129. break;
  130. case keys[configuration.closeReasons.tooBroad.key]:
  131. clickCloseReason(configuration.closeReasons.tooBroad.value);
  132. break;
  133. case keys[configuration.closeReasons.opinionBased.key]:
  134. clickCloseReason(configuration.closeReasons.opinionBased.value);
  135. break;
  136. }
  137. };
  138. var offTopicHandler = function (key) {
  139. switch (key) {
  140. case keys[configuration.offTopicReasons.superUser.key]:
  141. clickOffTopicReason(configuration.offTopicReasons.superUser.value);
  142. break;
  143. case keys[configuration.offTopicReasons.serverFault.key]:
  144. clickOffTopicReason(configuration.offTopicReasons.serverFault.value);
  145. break;
  146. case keys[configuration.offTopicReasons.recommend.key]:
  147. clickOffTopicReason(configuration.offTopicReasons.recommend.value);
  148. break;
  149. case keys[configuration.offTopicReasons.minimalProgram.key]:
  150. clickOffTopicReason(configuration.offTopicReasons.minimalProgram.value);
  151. break;
  152. case keys[configuration.offTopicReasons.typo.key]:
  153. clickOffTopicReason(configuration.offTopicReasons.typo.value);
  154. break;
  155. case keys[configuration.offTopicReasons.migration.key]:
  156. state = states.atOtherSite;
  157. clickOffTopicReason(configuration.offTopicReasons.migration.value);
  158. break;
  159. case keys[configuration.offTopicReasons.other.key]:
  160. clickOffTopicReason(configuration.offTopicReasons.other.value);
  161. break;
  162. }
  163. };
  164. var otherSiteHandler = function (key) {
  165. switch (key) {
  166. case keys[configuration.migrationReasons.meta.key]:
  167. clickOtherSite(configuration.migrationReasons.meta.value);
  168. break;
  169. case keys[configuration.migrationReasons.superUser.key]:
  170. clickOtherSite(configuration.migrationReasons.superUser.value);
  171. break;
  172. case keys[configuration.migrationReasons.tex.key]:
  173. clickOtherSite(configuration.migrationReasons.tex.value);
  174. break;
  175. case keys[configuration.migrationReasons.dba.key]:
  176. clickOtherSite(configuration.migrationReasons.dba.value);
  177. break;
  178. case keys[configuration.migrationReasons.stats.key]:
  179. clickOtherSite(configuration.migrationReasons.stats.value);
  180. break;
  181. }
  182. };
  183. var resetState = function () {
  184. state = states.atQuestion;
  185. };
  186. $(document).on('click', '#popup-close-question .popup-close a', function () {
  187. resetState();
  188. });
  189. $(document).on('click', '#popup-close-question .popup-submit', function () {
  190. resetState();
  191. });
  192. $(document).on('keyup', function (e) {
  193. if (e.keyCode === 27) {
  194. resetState();
  195. return;
  196. }
  197. if ((e.target.tagName === 'INPUT' && e.target.type === 'text') || e.target.tagName === 'TEXTAREA') {
  198. return;
  199. }
  200. keyHandler(e.keyCode);
  201. });
  202. })();
  203. (function () {
  204. var lookup = { };
  205. lookup[configuration.actions.leaveOpen.value] = configuration.actions.leaveOpen.key;
  206. lookup[configuration.actions.close.value] = configuration.actions.close.key;
  207. lookup[configuration.actions.edit.value] = configuration.actions.edit.key;
  208. lookup[configuration.actions.skip.value] = configuration.actions.skip.key;
  209. var observer = new MutationObserver(function (mutations) {
  210. mutations.forEach(function (mutation) {
  211. for (var i = 0, j = mutation.addedNodes.length; i < j; i++) {
  212. var node = $(mutation.addedNodes[i]);
  213. if (node.prop('tagName') !== 'INPUT' || node.prop('type') !== 'button') {
  214. continue;
  215. }
  216. var key = lookup[node.data('result-type')];
  217. if (!key) {
  218. continue;
  219. }
  220. node.val('[' + key + '] ' + node.val());
  221. }
  222. });
  223. });
  224. observer.observe(document.querySelector('.review-actions'), { 'childList': true });
  225. })();
  226. (function () {
  227. var addSiblingHelper = function (root, selector, key) {
  228. var element = $(root).find(selector).next();
  229. element.html('[' + key + '] ' + element.html());
  230. };
  231. var addCousinHelper = function (root, selector, key) {
  232. var element = $(root).find(selector).parent().next().next();
  233. element.html('[' + key + '] ' + element.html());
  234. };
  235. var addCloseReasonHelper = function (root, reason, key) {
  236. addSiblingHelper(root, '[name="close-reason"][value="' + reason + '"]', key);
  237. };
  238. var addOffTopicReasonHelper = function (root, reason, key) {
  239. addSiblingHelper(root, '[name="close-as-off-topic-reason"][value="' + reason +'"][data-other-comment-id=""]', key);
  240. };
  241. var addMigrationHelper = function (root, reason, key) {
  242. addCousinHelper(root, '[name="migration"][value="' + reason + '"]', key);
  243. };
  244. var addHelpers = function (root) {
  245. addCloseReasonHelper(root, configuration.closeReasons.duplicate.value, configuration.closeReasons.duplicate.key);
  246. addCloseReasonHelper(root, configuration.closeReasons.offTopic.value, configuration.closeReasons.offTopic.key);
  247. addCloseReasonHelper(root, configuration.closeReasons.unclear.value, configuration.closeReasons.unclear.key);
  248. addCloseReasonHelper(root, configuration.closeReasons.tooBroad.value, configuration.closeReasons.tooBroad.key);
  249. addCloseReasonHelper(root, configuration.closeReasons.opinionBased.value, configuration.closeReasons.opinionBased.key);
  250. addOffTopicReasonHelper(root, configuration.offTopicReasons.superUser.value, configuration.offTopicReasons.superUser.key);
  251. addOffTopicReasonHelper(root, configuration.offTopicReasons.serverFault.value, configuration.offTopicReasons.serverFault.key);
  252. addOffTopicReasonHelper(root, configuration.offTopicReasons.recommend.value, configuration.offTopicReasons.recommend.key);
  253. addOffTopicReasonHelper(root, configuration.offTopicReasons.minimalProgram.value, configuration.offTopicReasons.minimalProgram.key);
  254. addOffTopicReasonHelper(root, configuration.offTopicReasons.typo.value, configuration.offTopicReasons.typo.key);
  255. addOffTopicReasonHelper(root, configuration.offTopicReasons.migration.value, configuration.offTopicReasons.migration.key);
  256. addOffTopicReasonHelper(root, configuration.offTopicReasons.other.value, configuration.offTopicReasons.other.key);
  257. addMigrationHelper(root, configuration.migrationReasons.meta.value, configuration.migrationReasons.meta.key);
  258. addMigrationHelper(root, configuration.migrationReasons.superUser.value, configuration.migrationReasons.superUser.key);
  259. addMigrationHelper(root, configuration.migrationReasons.tex.value, configuration.migrationReasons.tex.key);
  260. addMigrationHelper(root, configuration.migrationReasons.dba.value, configuration.migrationReasons.dba.key);
  261. addMigrationHelper(root, configuration.migrationReasons.stats.value, configuration.migrationReasons.stats.key);
  262. };
  263. var observer = new MutationObserver(function (mutations) {
  264. mutations.forEach(function (mutation) {
  265. for (var i = 0, j = mutation.addedNodes.length; i < j; i++) {
  266. var node = mutation.addedNodes[i];
  267. if (node.tagName === 'DIV' && node.id === 'popup-close-question') {
  268. addHelpers(node);
  269. }
  270. }
  271. });
  272. });
  273. observer.observe(document.querySelector('.review-content'), { 'childList': true, 'subtree': true });
  274. })();
  275. });
  276. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址