BGG Shortcuts

Keyboard shortcuts for the Geek

当前为 2021-03-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name BGG Shortcuts
  3. // @namespace BGG Shortcuts
  4. // @version 0.9.8
  5. // @description Keyboard shortcuts for the Geek
  6. // @include http://*.boardgamegeek.*/*
  7. // @include http://boardgamegeek.*/*
  8. // @include https://*.boardgamegeek.*/*
  9. // @include https://boardgamegeek.*/*
  10. // @copyright 2013+, JB McMichael
  11. // ==/UserScript==
  12.  
  13. /*
  14. * CHANGLOG::
  15. * ============================================
  16. * 0.9.8.1 - Use the correct key
  17. * 0.9.8 - Update to hot key handling
  18. * 0.9.7 - Change some font sizes
  19. * 0.9.6 - Add shortcuts on the subscription page for auto selecting settings
  20. * 0.9.5 - Add K shortcut to go back a page
  21. * 0.9.4 - When loading a comment in a geeklist, scroll to item the comment is for
  22. * 0.9.3 - Fixed an error on Firefox relating to using strict
  23. * 0.9.2 - Better next link
  24. * 0.9.1 - Fixed a stupid bug
  25. * 0.9.0 - Updated so that the shortcuts work on the new game page style
  26. * 0.8.1 - Updated to work on https
  27. * 0.8.0 - Forum links pop up in a modal so that you don't lose your place on a page
  28. * 0.7.0 - Changed the links to just be J for next item, H for home, and / for search, but disabled shortcuts in form elements
  29. * 0.6.1 - Give the page some time to load its scripts before changing links
  30. * 0.6.0 - Added a shortcut to go to the searchbox Ctrl + /
  31. * 0.5.0 - Added homepage links opening in new tab
  32. * 0.4.0 - If search returns one result just go to that result
  33. * 0.3.0 - Added homepage shortcut; Ctrl + Shift + H
  34. * 0.2.0 - Cleaned up the subscription jump link
  35. * 0.1.0 - First version, shortcut for subscriptions; Ctrl + M
  36. *
  37. */
  38.  
  39. (function () {
  40. "use strict";
  41. console.log('Loaded BGG Shortcuts');
  42.  
  43. document.body.addEventListener('keydown', function (e) {
  44. let active = document.activeElement.tagName.toLowerCase(),
  45. badElements = ['input', 'textarea', 'select'];
  46.  
  47. // ignore shortcuts if we are in some form of input
  48. if (badElements.indexOf(active) === -1) {
  49.  
  50. // Next subscription item J
  51. if (e.key === 'j') {
  52. if (!!document.querySelector('[href="/subscriptions/next"]')) {
  53. let next = document.querySelectorAll('[href="/subscriptions/next"]');
  54. next[0].click();
  55. } else if (!!document.querySelectorAll("img:not(dn).nextsubcol")[0]) {
  56. [].slice.call(document.querySelectorAll("img:not(dn).nextsubcol")[0].parentNode.parentNode.click());
  57. }
  58. }
  59.  
  60. // Home page H
  61. if (e.key === 'h' && window.location.href !== window.location.origin) {
  62. window.location.href = window.location.origin;
  63. }
  64.  
  65. // Search box jump /
  66. if (e.key === '/') {
  67. let searchbox = !!document.getElementById('sitesearch') ? document.getElementById('sitesearch') : document.querySelector('[ng-model="searchctrl.search.q"]');
  68. document.body.scrollTop = 0;
  69. searchbox.focus();
  70. window.setTimeout(function () {
  71. searchbox.value = '';
  72. }, 10);
  73. }
  74.  
  75. // K to go back
  76. if (e.key === 'k') {
  77. window.history.back();
  78. }
  79.  
  80. /**
  81. * Subscription Page Hotkeys
  82. * =========================
  83. * These are only triggered on the subscription page, so each one has a check
  84. * a = turn everything to yes 65
  85. * o = i own the game, and want to track it 80
  86. * b = i want to buy the game 71
  87. */
  88. let subKeys = ['a', 'o', 'b'];
  89. if (subKeys.indexOf(e.key) !== -1) {
  90. subscriptionSelection(e.key);
  91. }
  92.  
  93. }
  94.  
  95. }, false);
  96.  
  97. if (window.location.pathname.split('/')[1] === 'subscription') {
  98. //show the hoykeys for this page
  99. let table = getNearestTableAncestor(document.querySelector('#thread'));
  100. let newDiv = document.createElement("div");
  101. let all = document.createElement("div");
  102. let allDesc = document.createTextNode('a - turn on all subscriptions');
  103. let own = document.createElement("div");
  104. let ownDesc = document.createTextNode("o - I own the game, just track the important things");
  105. let buy = document.createElement("div");
  106. let buyDesc = document.createTextNode('b - I want to buy the game, so track sales');
  107.  
  108. all.appendChild(allDesc);
  109. own.appendChild(ownDesc);
  110. buy.appendChild(buyDesc);
  111.  
  112. newDiv.appendChild(all);
  113. newDiv.appendChild(own);
  114. newDiv.appendChild(buy);
  115.  
  116. table.parentNode.insertBefore(newDiv, table.nextSibling);
  117. }
  118.  
  119. function subscriptionSelection(key) {
  120. if (window.location.pathname.split('/')[1] === 'subscription') {
  121. let thread = document.querySelector('#thread');
  122. let reply = document.querySelector('#article');
  123. let geeklist = document.querySelector('#listitem');
  124. let image = document.querySelector('#image');
  125. let video = document.querySelector('#video');
  126. let ebay = document.querySelector('#ebayauction');
  127. let market = document.querySelector('#storeitem');
  128. let file = document.querySelector('#file');
  129. let member = document.querySelector('#linkeditem');
  130. let comment = document.querySelector('#comment');
  131. let blog = document.querySelector('#blogpost');
  132. let preview = document.querySelector('#previewitem');
  133.  
  134. switch (key) {
  135. case 65:
  136. thread.selectedIndex = 1;
  137. reply.selectedIndex = 1;
  138. geeklist.selectedIndex = 1;
  139. image.selectedIndex = 1;
  140. video.selectedIndex = 1;
  141. ebay.selectedIndex = 1;
  142. market.selectedIndex = 1;
  143. file.selectedIndex = 1;
  144. member.selectedIndex = 1;
  145. comment.selectedIndex = 1;
  146. blog.selectedIndex = 1;
  147. preview.selectedIndex = 1;
  148. break;
  149. case 66:
  150. thread.selectedIndex = 2;
  151. reply.selectedIndex = 2;
  152. geeklist.selectedIndex = 1;
  153. image.selectedIndex = 2;
  154. video.selectedIndex = 2;
  155. ebay.selectedIndex = 1;
  156. market.selectedIndex = 1;
  157. file.selectedIndex = 2;
  158. member.selectedIndex = 2;
  159. comment.selectedIndex = 2;
  160. blog.selectedIndex = 2;
  161. preview.selectedIndex = 2;
  162. break;
  163. case 79:
  164. thread.selectedIndex = 1;
  165. reply.selectedIndex = 2;
  166. geeklist.selectedIndex = 2;
  167. image.selectedIndex = 2;
  168. video.selectedIndex = 2;
  169. ebay.selectedIndex = 2;
  170. market.selectedIndex = 2;
  171. file.selectedIndex = 2;
  172. member.selectedIndex = 2;
  173. comment.selectedIndex = 2;
  174. blog.selectedIndex = 2;
  175. preview.selectedIndex = 2;
  176. break;
  177.  
  178. }
  179. }
  180. }
  181.  
  182. //check for one result on the search page
  183. if (window.location.pathname === '/geeksearch.php' && window.location.search.search(/action=search/)) {
  184. let results = document.querySelectorAll('#collectionitems tbody tr');
  185. console.log('We are searching');
  186. if (results.length === 2) {
  187. console.log('Found just one result, redirect');
  188. let link = results[1].querySelectorAll('#results_objectname1 a'),
  189. href = link[0].getAttribute('href');
  190.  
  191. window.location.href = window.location.origin + href;
  192. }
  193. }
  194.  
  195. // set all homepage module links to open in new tab
  196. if (window.location.pathname === '/') {
  197. window.setTimeout(function () {
  198. console.log('Changing homepage links');
  199. let links = document.querySelectorAll('.innermoduletable tbody td a.ng-binding'),
  200. linkArray = [].slice.call(links);
  201.  
  202. linkArray.forEach(function (item, index) {
  203. item.setAttribute('target', '_blank');
  204. }
  205. );
  206. }, 500);
  207. }
  208.  
  209. // popup forum links in a dialog
  210. if (window.location.pathname.split('/')[1] === 'boardgame') {
  211. // grab all forum link clicks
  212. document.addEventListener('click', forumClick, false);
  213. }
  214.  
  215. function forumClick(e) {
  216. if (e.target.tagName === 'A' && e.target.href.split('/')[3] === 'thread') {
  217. e.preventDefault();
  218.  
  219. // use the BGG API to get the thread
  220. let req = new XMLHttpRequest(),
  221. apiUrl = window.location.protocol + '//' + window.location.host + '/xmlapi2/thread?id=',
  222. diag = document.createElement('dialog'),
  223. content = document.createElement('div'),
  224. close = document.createElement('button'),
  225. thread = e.target.href.split('/')[4];
  226.  
  227. diag.style.width = '80%';
  228. diag.style.height = '80%';
  229. diag.style.border = '2px solid rgba(0, 0, 0, 0.3)';
  230. diag.style.borderRadius = '6px';
  231. diag.style.boxShadow = '0 3px 7px rgba(0, 0, 0, 0.3)';
  232.  
  233. content.style.overflowY = 'auto';
  234. content.style.height = '95%';
  235. content.style.margin = '5px 0px';
  236.  
  237. close.innerText = 'Close';
  238.  
  239. req.onreadystatechange = showContents;
  240.  
  241. req.open('GET', apiUrl + thread, true);
  242.  
  243. req.send();
  244.  
  245. showContents(e, req);
  246. }
  247. }
  248.  
  249. function showContents(e, req) {
  250. if (req.readyState === 4 && req.status === 200) {
  251. let subject = req.responseXML.documentElement.children[0].firstChild.nodeValue,
  252. articles = req.responseXML.documentElement.children[1].children;
  253.  
  254. for (let i = 0; i < articles.length; i++) {
  255. let article = articles[i];
  256. let user = article.getAttribute('username');
  257. let title = article.children[0].firstChild.nodeValue;
  258. let body = article.children[1].firstChild.nodeValue;
  259. let postdate = article.getAttribute('postdate');
  260. let articleDiv = document.createElement('div');
  261. let dl = document.createElement('dl');
  262. let ddLeft = document.createElement('dd');
  263. let ddRight = document.createElement('dd');
  264. let userDiv = document.createElement('div');
  265. let bottomDl = document.createElement('dl');
  266. let ddLeft2 = document.createElement('dd');
  267. let ddCommands = document.createElement('dd');
  268. let ul = document.createElement('ul');
  269. let li = document.createElement('li');
  270. let ulInfo = document.createElement('ul');
  271. let liInfo = document.createElement('li');
  272. let postLink = document.createElement('a');
  273. let clearDiv = document.createElement('div');
  274. let rollsDiv = document.createElement('div');
  275. let userInfo = getUser(user);
  276.  
  277. articleDiv.addClass('article');
  278.  
  279. rollsDiv.addClass('rollsblock');
  280.  
  281. ddLeft.addClass('left');
  282. ddRight.addClass('right');
  283. userDiv.addClass('username');
  284. userDiv.innerHTML = user;
  285.  
  286. ddLeft.appendChild(userDiv);
  287. ddRight.innerHTML = body;
  288.  
  289. dl.appendChild(ddLeft);
  290. dl.appendChild(ddRight);
  291.  
  292. articleDiv.appendChild(dl);
  293.  
  294. ddLeft2.addClass('left');
  295. ddCommands.addClass('commands');
  296.  
  297. ul.appendChild(li);
  298. ddCommands.appendChild(ul);
  299.  
  300. clearDiv.addClass('clear');
  301. ulInfo.addClass('information');
  302.  
  303. postLink.innerHTML = postdate;
  304. liInfo.appendChild(postLink);
  305. ulInfo.appendChild(liInfo);
  306. ddCommands.appendChild(ulInfo);
  307.  
  308. bottomDl.appendChild(ddLeft2);
  309. bottomDl.appendChild(ddCommands);
  310.  
  311. articleDiv.appendChild(bottomDl);
  312. articleDiv.appendChild(clearDiv);
  313. articleDiv.appendChild(rollsDiv);
  314.  
  315. content.appendChild(articleDiv);
  316. }
  317.  
  318. diag.insertBefore(close, diag.childNodes[0]);
  319. diag.insertBefore(content, diag.childNodes[0]);
  320. close.addEventListener('click', function (e) {
  321. diag.close();
  322. }
  323. );
  324. document.body.insertBefore(diag, document.body.childNodes[0]);
  325. diag.showModal();
  326. }
  327. }
  328.  
  329. // Check for readystate so that we can shift the page if needed
  330. let interval = setInterval(function () {
  331. if (document.readyState === 'complete') {
  332. clearInterval(interval);
  333. checkForComment();
  334. }
  335. }, 100);
  336.  
  337. function checkForComment() {
  338. if (/comment[0-9]+/.test(window.location.hash) && window.location.pathname.includes('/geeklist/')) {
  339. let hash = window.location.hash;
  340. let comment = document.querySelector(hash);
  341. let parent = comment.parentElement.parentElement.parentElement.parentElement; // get the actual item
  342.  
  343. window.scroll(0, findPos(parent));
  344. }
  345. }
  346.  
  347. function findPos(obj) {
  348. let curtop = 0;
  349. if (obj.offsetParent) {
  350. do {
  351. curtop += obj.offsetTop;
  352. } while (obj = obj.offsetParent);
  353. return [curtop];
  354. }
  355. }
  356.  
  357. function getUser(name) {
  358. let req = new XMLHttpRequest(),
  359. apiUrl = window.location.protocol + '//' + window.location.host + '/xmlapi2/user?name=' + name;
  360.  
  361. req.onreadystatechange = function (e) {
  362. if (req.readyState === 4 && req.status === 200) {
  363. console.log(req.responseXML);
  364. }
  365. };
  366.  
  367. req.open('GET', apiUrl, true);
  368.  
  369. req.send();
  370. }
  371.  
  372. function getNearestTableAncestor(htmlElementNode) {
  373. while (htmlElementNode) {
  374. htmlElementNode = htmlElementNode.parentNode;
  375. if (htmlElementNode.tagName.toLowerCase() === 'table') {
  376. return htmlElementNode;
  377. }
  378. }
  379. return undefined;
  380. }
  381.  
  382. // change the font size
  383. let el = document.querySelectorAll('div.article dd');
  384. for (let i = 0; i < el.length; i++) {
  385. let element = el[i];
  386. element.style.fontSize = "1.25em";
  387. }
  388. }
  389. ());