// ==UserScript==
// @name BGG Shortcuts
// @namespace BGG Shortcuts
// @version 0.9.9
// @description Keyboard shortcuts for the Geek
// @include http://*.boardgamegeek.*/*
// @include http://boardgamegeek.*/*
// @include https://*.boardgamegeek.*/*
// @include https://boardgamegeek.*/*
// @copyright 2013+, JB McMichael
// ==/UserScript==
/*
* CHANGLOG::
* ============================================
* 0.9.9 - Use the correct key
* 0.9.8 - Update to hot key handling
* 0.9.7 - Change some font sizes
* 0.9.6 - Add shortcuts on the subscription page for auto selecting settings
* 0.9.5 - Add K shortcut to go back a page
* 0.9.4 - When loading a comment in a geeklist, scroll to item the comment is for
* 0.9.3 - Fixed an error on Firefox relating to using strict
* 0.9.2 - Better next link
* 0.9.1 - Fixed a stupid bug
* 0.9.0 - Updated so that the shortcuts work on the new game page style
* 0.8.1 - Updated to work on https
* 0.8.0 - Forum links pop up in a modal so that you don't lose your place on a page
* 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
* 0.6.1 - Give the page some time to load its scripts before changing links
* 0.6.0 - Added a shortcut to go to the searchbox Ctrl + /
* 0.5.0 - Added homepage links opening in new tab
* 0.4.0 - If search returns one result just go to that result
* 0.3.0 - Added homepage shortcut; Ctrl + Shift + H
* 0.2.0 - Cleaned up the subscription jump link
* 0.1.0 - First version, shortcut for subscriptions; Ctrl + M
*
*/
(function () {
"use strict";
console.log('Loaded BGG Shortcuts');
document.body.addEventListener('keydown', function (e) {
let active = document.activeElement.tagName.toLowerCase(),
badElements = ['input', 'textarea', 'select'];
// ignore shortcuts if we are in some form of input
if (badElements.indexOf(active) === -1) {
// Next subscription item J
if (e.key === 'j') {
if (!!document.querySelector('[href="/subscriptions/next"]')) {
let next = document.querySelectorAll('[href="/subscriptions/next"]');
next[0].click();
} else if (!!document.querySelectorAll("img:not(dn).nextsubcol")[0]) {
[].slice.call(document.querySelectorAll("img:not(dn).nextsubcol")[0].parentNode.parentNode.click());
}
}
// Home page H
if (e.key === 'h' && window.location.href !== window.location.origin) {
window.location.href = window.location.origin;
}
// Search box jump /
if (e.key === '/') {
let searchbox = !!document.getElementById('sitesearch') ? document.getElementById('sitesearch') : document.querySelector('[ng-model="searchctrl.search.q"]');
document.body.scrollTop = 0;
searchbox.focus();
window.setTimeout(function () {
searchbox.value = '';
}, 10);
}
// K to go back
if (e.key === 'k') {
window.history.back();
}
/**
* Subscription Page Hotkeys
* =========================
* These are only triggered on the subscription page, so each one has a check
* a = turn everything to yes 65
* o = i own the game, and want to track it 80
* b = i want to buy the game 71
*/
let subKeys = ['a', 'o', 'b'];
if (subKeys.indexOf(e.key) !== -1) {
subscriptionSelection(e.key);
}
}
}, false);
if (window.location.pathname.split('/')[1] === 'subscription') {
//show the hoykeys for this page
let table = getNearestTableAncestor(document.querySelector('#thread'));
let newDiv = document.createElement("div");
let all = document.createElement("div");
let allDesc = document.createTextNode('a - turn on all subscriptions');
let own = document.createElement("div");
let ownDesc = document.createTextNode("o - I own the game, just track the important things");
let buy = document.createElement("div");
let buyDesc = document.createTextNode('b - I want to buy the game, so track sales');
all.appendChild(allDesc);
own.appendChild(ownDesc);
buy.appendChild(buyDesc);
newDiv.appendChild(all);
newDiv.appendChild(own);
newDiv.appendChild(buy);
table.parentNode.insertBefore(newDiv, table.nextSibling);
}
function subscriptionSelection(key) {
if (window.location.pathname.split('/')[1] === 'subscription') {
let thread = document.querySelector('#thread');
let reply = document.querySelector('#article');
let geeklist = document.querySelector('#listitem');
let image = document.querySelector('#image');
let video = document.querySelector('#video');
let ebay = document.querySelector('#ebayauction');
let market = document.querySelector('#storeitem');
let file = document.querySelector('#file');
let member = document.querySelector('#linkeditem');
let comment = document.querySelector('#comment');
let blog = document.querySelector('#blogpost');
let preview = document.querySelector('#previewitem');
switch (key) {
case 65:
thread.selectedIndex = 1;
reply.selectedIndex = 1;
geeklist.selectedIndex = 1;
image.selectedIndex = 1;
video.selectedIndex = 1;
ebay.selectedIndex = 1;
market.selectedIndex = 1;
file.selectedIndex = 1;
member.selectedIndex = 1;
comment.selectedIndex = 1;
blog.selectedIndex = 1;
preview.selectedIndex = 1;
break;
case 66:
thread.selectedIndex = 2;
reply.selectedIndex = 2;
geeklist.selectedIndex = 1;
image.selectedIndex = 2;
video.selectedIndex = 2;
ebay.selectedIndex = 1;
market.selectedIndex = 1;
file.selectedIndex = 2;
member.selectedIndex = 2;
comment.selectedIndex = 2;
blog.selectedIndex = 2;
preview.selectedIndex = 2;
break;
case 79:
thread.selectedIndex = 1;
reply.selectedIndex = 2;
geeklist.selectedIndex = 2;
image.selectedIndex = 2;
video.selectedIndex = 2;
ebay.selectedIndex = 2;
market.selectedIndex = 2;
file.selectedIndex = 2;
member.selectedIndex = 2;
comment.selectedIndex = 2;
blog.selectedIndex = 2;
preview.selectedIndex = 2;
break;
}
}
}
//check for one result on the search page
if (window.location.pathname === '/geeksearch.php' && window.location.search.search(/action=search/)) {
let results = document.querySelectorAll('#collectionitems tbody tr');
console.log('We are searching');
if (results.length === 2) {
console.log('Found just one result, redirect');
let link = results[1].querySelectorAll('#results_objectname1 a'),
href = link[0].getAttribute('href');
window.location.href = window.location.origin + href;
}
}
// set all homepage module links to open in new tab
if (window.location.pathname === '/') {
window.setTimeout(function () {
console.log('Changing homepage links');
let links = document.querySelectorAll('.innermoduletable tbody td a.ng-binding'),
linkArray = [].slice.call(links);
linkArray.forEach(function (item, index) {
item.setAttribute('target', '_blank');
}
);
}, 500);
}
// popup forum links in a dialog
if (window.location.pathname.split('/')[1] === 'boardgame') {
// grab all forum link clicks
document.addEventListener('click', forumClick, false);
}
function forumClick(e) {
if (e.target.tagName === 'A' && e.target.href.split('/')[3] === 'thread') {
e.preventDefault();
// use the BGG API to get the thread
let req = new XMLHttpRequest(),
apiUrl = window.location.protocol + '//' + window.location.host + '/xmlapi2/thread?id=',
diag = document.createElement('dialog'),
content = document.createElement('div'),
close = document.createElement('button'),
thread = e.target.href.split('/')[4];
diag.style.width = '80%';
diag.style.height = '80%';
diag.style.border = '2px solid rgba(0, 0, 0, 0.3)';
diag.style.borderRadius = '6px';
diag.style.boxShadow = '0 3px 7px rgba(0, 0, 0, 0.3)';
content.style.overflowY = 'auto';
content.style.height = '95%';
content.style.margin = '5px 0px';
close.innerText = 'Close';
req.onreadystatechange = showContents;
req.open('GET', apiUrl + thread, true);
req.send();
showContents(e, req);
}
}
function showContents(e, req) {
if (req.readyState === 4 && req.status === 200) {
let subject = req.responseXML.documentElement.children[0].firstChild.nodeValue,
articles = req.responseXML.documentElement.children[1].children;
for (let i = 0; i < articles.length; i++) {
let article = articles[i];
let user = article.getAttribute('username');
let title = article.children[0].firstChild.nodeValue;
let body = article.children[1].firstChild.nodeValue;
let postdate = article.getAttribute('postdate');
let articleDiv = document.createElement('div');
let dl = document.createElement('dl');
let ddLeft = document.createElement('dd');
let ddRight = document.createElement('dd');
let userDiv = document.createElement('div');
let bottomDl = document.createElement('dl');
let ddLeft2 = document.createElement('dd');
let ddCommands = document.createElement('dd');
let ul = document.createElement('ul');
let li = document.createElement('li');
let ulInfo = document.createElement('ul');
let liInfo = document.createElement('li');
let postLink = document.createElement('a');
let clearDiv = document.createElement('div');
let rollsDiv = document.createElement('div');
let userInfo = getUser(user);
articleDiv.addClass('article');
rollsDiv.addClass('rollsblock');
ddLeft.addClass('left');
ddRight.addClass('right');
userDiv.addClass('username');
userDiv.innerHTML = user;
ddLeft.appendChild(userDiv);
ddRight.innerHTML = body;
dl.appendChild(ddLeft);
dl.appendChild(ddRight);
articleDiv.appendChild(dl);
ddLeft2.addClass('left');
ddCommands.addClass('commands');
ul.appendChild(li);
ddCommands.appendChild(ul);
clearDiv.addClass('clear');
ulInfo.addClass('information');
postLink.innerHTML = postdate;
liInfo.appendChild(postLink);
ulInfo.appendChild(liInfo);
ddCommands.appendChild(ulInfo);
bottomDl.appendChild(ddLeft2);
bottomDl.appendChild(ddCommands);
articleDiv.appendChild(bottomDl);
articleDiv.appendChild(clearDiv);
articleDiv.appendChild(rollsDiv);
content.appendChild(articleDiv);
}
diag.insertBefore(close, diag.childNodes[0]);
diag.insertBefore(content, diag.childNodes[0]);
close.addEventListener('click', function (e) {
diag.close();
}
);
document.body.insertBefore(diag, document.body.childNodes[0]);
diag.showModal();
}
}
// Check for readystate so that we can shift the page if needed
let interval = setInterval(function () {
if (document.readyState === 'complete') {
clearInterval(interval);
checkForComment();
}
}, 100);
function checkForComment() {
if (/comment[0-9]+/.test(window.location.hash) && window.location.pathname.includes('/geeklist/')) {
let hash = window.location.hash;
let comment = document.querySelector(hash);
let parent = comment.parentElement.parentElement.parentElement.parentElement; // get the actual item
window.scroll(0, findPos(parent));
}
}
function findPos(obj) {
let curtop = 0;
if (obj.offsetParent) {
do {
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return [curtop];
}
}
function getUser(name) {
let req = new XMLHttpRequest(),
apiUrl = window.location.protocol + '//' + window.location.host + '/xmlapi2/user?name=' + name;
req.onreadystatechange = function (e) {
if (req.readyState === 4 && req.status === 200) {
console.log(req.responseXML);
}
};
req.open('GET', apiUrl, true);
req.send();
}
function getNearestTableAncestor(htmlElementNode) {
while (htmlElementNode) {
htmlElementNode = htmlElementNode.parentNode;
if (htmlElementNode.tagName.toLowerCase() === 'table') {
return htmlElementNode;
}
}
return undefined;
}
// change the font size
let el = document.querySelectorAll('div.article dd');
for (let i = 0; i < el.length; i++) {
let element = el[i];
element.style.fontSize = "1.25em";
}
}
());