From c8b7fe61c045e973baabf90c00e225579a7ebf43 Mon Sep 17 00:00:00 2001 From: Samir Benmendil Date: Wed, 11 Sep 2013 00:13:04 +0200 Subject: [PATCH] add reddit cookies + enhancment enhancement don't work in dwb :( --- dwb/default/cookies.allow | 1 + .../reddit_enhancement_suite.user.js | 21072 ++++++++++++++++ 2 files changed, 21073 insertions(+) create mode 100644 dwb/greasemonkey/reddit_enhancement_suite.user.js diff --git a/dwb/default/cookies.allow b/dwb/default/cookies.allow index bcb07d4..2bb10fe 100644 --- a/dwb/default/cookies.allow +++ b/dwb/default/cookies.allow @@ -40,3 +40,4 @@ www.wiebetaaltwat.nl github.com forums.introversion.co.uk gitorious.org +.reddit.com diff --git a/dwb/greasemonkey/reddit_enhancement_suite.user.js b/dwb/greasemonkey/reddit_enhancement_suite.user.js new file mode 100644 index 0000000..e8d4aa8 --- /dev/null +++ b/dwb/greasemonkey/reddit_enhancement_suite.user.js @@ -0,0 +1,21072 @@ +// ==UserScript== +// @name Reddit Enhancement Suite +// @namespace http://reddit.honestbleeps.com/ +// @description A suite of tools to enhance reddit... +// @copyright 2010-2013, Steve Sobel (http://redditenhancementsuite.com/) +// @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html +// @author honestbleeps +// @include http://redditenhancementsuite.com/* +// @include http://reddit.honestbleeps.com/* +// @include http://reddit.com/* +// @include https://reddit.com/* +// @include http://*.reddit.com/* +// @include https://*.reddit.com/* +// @version 4.3.0.4 +// @updateURL http://redditenhancementsuite.com/latest/reddit_enhancement_suite.meta.js +// @downloadURL http://redditenhancementsuite.com/latest/reddit_enhancement_suite.user.js +// @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js +// ==/UserScript== + +/*jshint undef: true, unused: true, strict: false, laxbreak: true, multistr: true, smarttabs: true, sub: true, browser: true */ + +var RESVersion = "4.3.0.4"; + +var jQuery, $, guiders, Tinycon, SnuOwnd; + +/* + Reddit Enhancement Suite - a suite of tools to enhance Reddit + Copyright (C) 2010-2012 - honestbleeps (steve@honestbleeps.com) + + RES is released under the GPL. However, I do ask a favor (obviously I don't/can't require it, I ask out of courtesy): + + Because RES auto updates and is hosted from a central server, I humbly request that if you intend to distribute your own + modified Reddit Enhancement Suite, you name it something else and make it very clear to your users that it's your own + branch and isn't related to mine. + + RES is updated very frequently, and I get lots of tech support questions/requests from people on outdated versions. If + you're distributing RES via your own means, those recipients won't always be on the latest and greatest, which makes + it harder for me to debug things and understand (at least with browsers that auto-update) whether or not people are on + a current version of RES. + + I can't legally hold you to any of this - I'm just asking out of courtesy. + + Thanks, I appreciate your consideration. Without further ado, the all-important GPL Statement: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +var tokenizeCSS = 'ul.token-input-list-facebook { overflow: hidden; height: auto !important; height: 1%; width: 400px; border: 1px solid #96bfe8; cursor: text; font-size: 12px; font-family: Verdana; min-height: 1px; z-index: 1010; margin: 0; padding: 0; background-color: #fff; list-style-type: none; float: left; margin-right: 12px; }'; +tokenizeCSS += '.optionsTable ul.token-input-list-facebook {clear: left; float: none; margin-right: 0; }'; +tokenizeCSS += 'ul.token-input-list-facebook li input { border: 0; width: 100px; padding: 3px 8px; background-color: white; margin: 2px 0; -webkit-appearance: caret; }'; +tokenizeCSS += 'li.token-input-token-facebook { overflow: hidden; height: auto !important; height: 15px; margin: 3px; padding: 1px 3px; background-color: #eff2f7; color: #000; cursor: default; border: 1px solid #ccd5e4; font-size: 11px; border-radius: 5px; float: left; white-space: nowrap; }'; +tokenizeCSS += 'li.token-input-token-facebook p { display: inline; padding: 0; margin: 0;}'; +tokenizeCSS += 'li.token-input-token-facebook span { color: #a6b3cf; margin-left: 5px; font-weight: bold; cursor: pointer;}'; +tokenizeCSS += 'li.token-input-selected-token-facebook { background-color: #5670a6; border: 1px solid #3b5998; color: #fff;}'; +tokenizeCSS += 'li.token-input-input-token-facebook { float: left; margin: 0; padding: 0; list-style-type: none;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook { position: absolute; width: 400px; background-color: #fff; overflow: hidden; border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; cursor: default; font-size: 11px; font-family: Verdana; z-index: 100000100; }'; +tokenizeCSS += 'div.token-input-dropdown-facebook p { margin: 0; padding: 5px; font-weight: bold; color: #777;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul { margin: 0; padding: 0;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul li { background-color: #fff; padding: 3px; margin: 0; list-style-type: none;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul li.token-input-dropdown-item-facebook { background-color: #fff;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul li.token-input-dropdown-item2-facebook { background-color: #fff;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul li em { font-weight: bold; font-style: normal;}'; +tokenizeCSS += 'div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook { background-color: #3b5998; color: #fff;}'; + + +var guidersCSS = '.guider { background: #FFF; border: 1px solid #666; font-family: arial; position: absolute; outline: none; z-index: 100000005 !important; padding: 4px 12px; width: 500px; z-index: 100; -moz-box-shadow: 0 0 8px #111; -webkit-box-shadow: 0 0 8px #111; box-shadow: 0 0 8px #111; border-radius: 4px;}'; +guidersCSS += '.guider_buttons { height: 36px; position: relative; width: 100%; }'; +guidersCSS += '.guider_content { position: relative; }'; +guidersCSS += '.guider_description { margin-bottom: 10px; }'; +guidersCSS += '.guider_content h1 { color: #1054AA; float: left; font-size: 21px; }'; +guidersCSS += '.guider_close { float: right; padding: 10px 0 0; }'; +// guidersCSS += '.x_button { background-image: url(\'x_close_button.jpg\'); cursor: pointer; height: 13px; width: 13px; }'; +guidersCSS += '.x_button { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAIAAAD9iXMrAAAACXBIWXMAAAsTAAALEwEAmpwYAAABe0lEQVQoFT2RT0/CQBDFd7tbqIULiAaMgK1ngSqFCyHwyaFANS1+BEDigRgIFORAQv/5lhI32eR1duY3b6Z0vvg+Ho+M0TAMGWPx5VBKCSH/OpPJ8OVyqaqKqqqcsSgKophKkpTUIBX6dDptNhuON13X7wq3F4RgkEQJnpCr1c9sNgOFhUHgebvpdNrpdNAiiiIwt1vPdd1er8c5D4IAvAjm8vl8LpebTCZ4SKXSu91+Mh7XajX1Jv17OMCs4PlBBCutVgvIwcBqGIZt269GvVqtwAalMUD8OiYhKGo2Tfv9w7Isw3jTNA3FOGiIHA6FLcByTIjn7dfrdaFwv1gsKuWSoigXntgAxxXGKYHx8WjUqL9gfMdxhta43+/LMpeYHITxled5h5FIquv6Exjw6rifw+Gw2+2ez2eMLOZF62w2a5pm+fEhaYRIu23O51+Kkkpyrn1lmRWLxcQrKmEG+vlZAwQaxRwXa0NUHHzE4i/7vh8TCSTEoEul0h9jNtRZlgw9UAAAAABJRU5ErkJggg==); cursor: pointer; height: 13px; width: 13px; }'; +guidersCSS += '.guider_content p { clear: both; color: #333; font-size: 13px; }'; +guidersCSS += '.guider_button { background: -moz-linear-gradient(top, #5CA9FF 0%, #3D79C3 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5CA9FF), color-stop(100%, #3D79C3)); background-color: #4A95E0; border: solid 1px #4B5D7E; color: #FFF; cursor: pointer; display: inline-block; float: right; font-size: 75%; font-weight: bold; margin-left: 6px; min-width: 40px; padding: 3px 5px; text-align: center; text-decoration: none;border-radius: 2px; }'; +guidersCSS += '#guider_overlay { background-color: #000; width: 100%; height: 100%; position: fixed; top: 0; left: 0; opacity: 0.5; filter: alpha(opacity=50); z-index: 1000; }'; + /** + * For optimization, the arrows image is inlined in the css below. + * + * To use your own arrows image, replace this background-image with your own arrows. + * It should have four arrows, top, right, left, and down. + */ +guidersCSS += '.guider_arrow { width: 42px; height: 42px; position: absolute; display: none; background-repeat: no-repeat; z-index: 100000006 !important; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAACoCAMAAAChZYy6AAABelBMVEX///8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzN+fn4zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPe3t4zMzMzMzN2dnYzMzMzMzPV1dUzMzMzMzMzMzMzMzMzMzN8fHwzMzOEhIQzMzPa2tozMzMzMzNtbW0zMzNzc3MzMzMzMzPR0dFtbW1xcXHS0tIzMzMzMzNxcXHh4eEzMzMzMzPi4uIzMzMzMzMzMzMzMzMzMzMzMzOEhITPz8+MjIwzMzMzMzMzMzOHh4czMzMzMzPZ2dkzMzMzMzPPz88zMzPW1tYzMzMzMzPV1dVvb28zMzMzMzMzMzOBgYEzMzPY2NiBgYHh4eHd3d3Nzc3o6OikpKQzMzPe3t7R0dEzMzPZ2dna2tqHh4czMzMzMzP///+41NW+AAAAfXRSTlMAAAECBA0HCBUfDhYsYy4gPU07CyF4PFCLjnVhA3ZMYC1PuxSMOmJ0jSscivBOGbY+eepVDDZ3Hrk/vgbuKn+yNLUJKOmxs+pZU7TwMgXcEhcmHRNun+SzNw+HvnJe6Eok50vsZArrpHFSNbyJ137v7ufmkyXv6F/s6qFWWFA9DhkAAAWkSURBVHhe7djpU+LKHsfh3ARiQBE0ioIIKoSwCKiAiAguuG+Ay7jv+z4uozNq/vebDub8OImdpOq8O3W+L62nPkVZZTc24aEokiQMjWUnaBNlCEcicb+5zhAeSy/mLYwhPDebPHY2iJjWxcvrBb4jZggLwn4i0NnRZAAL4k4S7SJu1sOCtHNtDBRtYByLgepjoIDn290+PEYENoPFQAFPR90+b3PY4kcYQ+VNSpgLZ/wp2kPhqIxHFRioeq+jUf7Ny+VXVtkcRcoUs6OpW37vIF0pm3MUUMwep7bXks4Kc2YCit2Pi8ur64x1iQSK39199rCUM0SfnrMvJdoAfdjgk5uWLf0PsLAT2P3MFsusSYcuDLv6bTGuGLfS2r+sXhGetvU12pkU7SFlioE2EbbUW2tPiG/gjatVDYEC7EHQARBHfykhjv7sCSkgho4g2I2BQAHaZYijI0OhwWC3owsghv4eCmhBoH+0IWz5veAOxpwiNAPEHcUfXoB6B3zYzmCg/rXxzy8juOL0KRsZm1sWNLc8NxZhPQTFRtKz79p0fTYdYSmCmogvfhT+aMn9QnIxPkERJO0Pe91DWjTBH+f9NEmQJrPdGQz8xsuTQIfTYkZ/4VQd0xUb1MgmOmMNTB1FEHJ2BCfP2+UoytZ3dUNWHW2qRr+yjmAIkx34KwrZnu/pOIrSKIrNQrT5KwrZ1h79KMpa7Q5b6KdWVDc7L0eV2V9KOQPR2mwjZCHqlqPKrKtXGfXJUUW2TZmdlqPKbIsyOxmFqDp7ox+FrF4UssNAR91eiGpmX3FROds/XBMNQ1Sd7bO5FqryKOrjIKqZneK9YT+K4rOn1ezj7RuXgSgmu6MXhU8beBCEH9t7XCaFolrZ2O6GIFysHeQhisnauU/+6e4ymV5JeRDQyDLFbPL5/spZWYUoJpuKFzez2etKmYUoJktby5aXwwwDX9qwWQ/NbpVK1jOQeEuZlujckrGbhBSncP+NJCnKYwhSJnqCZQ3BOrM/HokYgow9vJgeMwDr7V1O78fsnCEYC7oL78va0CpCR3dwMDAkXu3GoHRZ68PQ0NeFpgEbHd221lAPgtJwsAWgPCxsQ/Bvlx4eunoU1xge9gqKqWEfgjcAgaphv2sYoJqSHjrF2EV4ClA5OFTiRS6GigsI4ClpYsvF7OduYAcgji5tWTaT/MYDACylSy/Z5ycAGjRXOsze3xmiS9bM9dXlxQ99SpjOmIozubY99ahLqZy5XEkf7PG3U0c6lKRy7OpKnvO+8dHRVxyVux465c+EOa/PHR2dxFI1np7EURmbaLPfEm6W8IwmJUiEGUtDc5PP3T4PGCgWDygoFnd0to8DBorHQHVx4hyoPj4BqoWdIg4k9oFq4lgHX1hfBqCJj5Ozc8YOeEseDniD14bxy8j4Ffcv3f/UI9HUP1ZR9OUpR6MvT7qUyp1ZS6UtlvaQGArSzGQOXyxlK03pUA9brlxns5vFeMpEfkfhJKBXK86r++dktoj+IZX3HfWkVtLJy7sn/pOzW00klqKoP3+wdiEIG7uxxnrIqilpSmW4vW3xQH4I2PpaUFZFIRr28lOCuJ3+NsgqKYqaM9zb7SOiC65TyKooRNGGIauiKGrhfNEjBFEWPq2KUjQT9rpHJQhZJZWjzb7oq0x7XTYHyqopijZAFGVbpayCQnQSKGQVFEWb3NNCzW6qWSWFqDqroBCtWY+craXVaPuMjCBrR9laWo3Oyway3ShbS6VohxyF/QpVszVUinaiqDrbJWa/KEQH1PRnTZaA6DgCGlkCE5U3Ego67GYpS2CikB2Us0Q16oQoLosoVYeiCQGzITlLyNFzHB0JBJ1SlpCiMRTFZ2NdjJglqtHACZ7+/sqix7/8MY+i+KwbPQCQ0pNisrCvRf8UPtCTYvWhcl3Q3Hv1odL48+f/AZYqoYKwlk9uAAAAAElFTkSuQmCC); } '; +guidersCSS += '.guider_arrow_right { display: block; background-position: 0 0; right: -42px; }'; +guidersCSS += '.guider_arrowdown { display: block; background-position: 0 -42px; bottom: -42px; }'; +guidersCSS += '.guider_arrow_up { display: block; background-position: 0 -126px; top: -42px; }'; +guidersCSS += '.guider_arrow_left { display: block; background-position: 0 -84px; left: -42px;}'; +guidersCSS += '.guider_content h2 { margin-top: 1em; }'; +guidersCSS += '.guider_content td { vertical-align: top; }'; +guidersCSS += '.guider_content td + td { padding-left: 1em; }'; +guidersCSS += '.guider_content td code { white-space: nowrap; }'; + + + +// DOM utility functions +var escapeLookups = { "&": "&", '"': """, "<": "<", ">": ">" }; +function escapeHTML(str) { + return (typeof str === 'undefined' || str === null) ? + null : + str.toString().replace(/[&"<>]/g, function(m) { return escapeLookups[m]; }); +} + +function insertAfter( referenceNode, newNode ) { + if ((typeof referenceNode === 'undefined') || (referenceNode === null)) { + console.log(arguments.callee.caller); + } else if ((typeof referenceNode.parentNode !== 'undefined') && (typeof referenceNode.nextSibling !== 'undefined')) { + if (referenceNode.parentNode === null) { + console.log(arguments.callee.caller); + } else { + referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling ); + } + } +} +function createElementWithID(elementType, id, classname) { + var obj = document.createElement(elementType); + if (id !== null) { + obj.setAttribute('id', id); + } + if ((typeof classname !== 'undefined') && (classname !== '')) { + obj.setAttribute('class', classname); + } + return obj; +} + +// this alias is to account for opera having different behavior... +if (typeof navigator === 'undefined') navigator = window.navigator; + +//Because Safari 5.1 doesn't have Function.bind +if (typeof Function.prototype.bind === 'undefined') { + Function.prototype.bind = function(context) { + var oldRef = this; + return function() { + return oldRef.apply(context || null, Array.prototype.slice.call(arguments)); + }; + }; +} + +var BrowserDetect = { + init: function () { + this.browser = this.searchString(this.dataBrowser) || "An unknown browser"; + this.version = this.searchVersion(navigator.userAgent) || + this.searchVersion(navigator.appVersion) || + "an unknown version"; + this.OS = this.searchString(this.dataOS) || "an unknown OS"; + + // set up MutationObserver variable to take whichever is supported / existing... + // unfortunately, this doesn't (currently) exist in Opera. + // this.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || null; + // At the time of writing WebKit's mutation observer leaks entire pages on refresh so it needs to be disabled. + this.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || null; + + // null out MutationObserver to test legacy DOMNodeInserted + // this.MutationObserver = null; + }, + searchString: function (data) { + for (var i=0;i' + data + '
RES can delete this data to stop errors from happening, but you might want to copy/paste it to a text file so you can more easily re-enter any lost information.'; + alert(msg, function() { + // back up a copy of the corrupt data + localStorage.setItem(localStorageSource + '.error', data); + // delete the corrupt data + RESStorage.removeItem(localStorageSource); + }); + } else { + alert('Error caught: JSON parse failure on the following data: ' + data); + } + return {}; + } + } +}; + +// array compare utility function for keyCode arrays +function keyArrayCompare(fromArr, toArr) { + // if we've passed in a number, fix that and make it an array with alt, shift and ctrl set to false. + if (typeof toArr === 'number') { + toArr = [toArr, false, false, false]; + } else if (toArr.length === 4) { + toArr.push(false); + } + if (fromArr.length !== toArr.length) return false; + for (var i = 0; i < toArr.length; i++) { + if (fromArr[i].compare) { + if (!fromArr[i].compare(toArr[i])) return false; + } + if (fromArr[i] !== toArr[i]) return false; + } + return true; +} + +// utility function for checking events against keyCode arrays +function checkKeysForEvent(event, keyArray) { + //[keycode, alt, ctrl, shift, meta] + // if we've passed in a number, fix that and make it an array with alt, shift and ctrl set to false. + if (typeof keyArray === 'number') { + keyArray = [keyArray, false, false, false, false]; + } else if (keyArray.length === 4) { + keyArray.push(false); + } + if (event.keyCode != keyArray[0]) return false; + else if (event.altKey != keyArray[1]) return false; + else if (event.ctrlKey != keyArray[2]) return false; + else if (event.shiftKey != keyArray[3]) return false; + else if (event.metaKey != keyArray[4]) return false; + else return true; +} + +function operaUpdateCallback(obj) { + RESUtils.compareVersion(obj); +} +function operaForcedUpdateCallback(obj) { + RESUtils.compareVersion(obj, true); +} + +// This object will store xmlHTTPRequest callbacks for Safari because Safari's extension architecture seems stupid. +// This really shouldn't be necessary, but I can't seem to hold on to an onload function that I pass to the background page... +xhrQueue = { count: 0, onloads: [] }; + + +// if this is a jetpack addon, add an event listener like Safari's message handler... +if (BrowserDetect.isFirefox()) { + self.on('message', function(msgEvent) { + switch (msgEvent.name) { + case 'GM_xmlhttpRequest': + // Fire the appropriate onload function for this xmlhttprequest. + xhrQueue.onloads[msgEvent.XHRID](msgEvent.response); + break; + case 'compareVersion': + var forceUpdate = false; + if (typeof msgEvent.message.forceUpdate !== 'undefined') forceUpdate = true; + RESUtils.compareVersion(msgEvent.message, forceUpdate); + break; + case 'loadTweet': + var tweet = msgEvent.response; + var thisExpando = modules['styleTweaks'].tweetExpando; + $(thisExpando).html(tweet.html); + thisExpando.style.display = 'block'; + thisExpando.classList.add('twitterLoaded'); + break; + // for now, commenting out the old way of handling tweets as AMO will not approve. + /* + var tweet = msgEvent.response; + var thisExpando = modules['styleTweaks'].tweetExpando; + thisExpando.innerHTML = ''; + // the iframe is to sandbox this remote javascript from accessing reddit's javascript, etc. + // this is done this way as requested by the AMO review team. + var sandboxFrame = document.createElement('iframe'); + var seamless = document.createAttribute('seamless'); + sandboxFrame.setAttribute('sandbox','allow-scripts allow-same-origin'); + sandboxFrame.setAttributeNode(seamless); + sandboxFrame.setAttribute('style','border: none;'); + sandboxFrame.setAttribute('width','480'); + sandboxFrame.setAttribute('height','260'); + sandboxFrame.setAttribute('src','data:text/html,'+encodeURIComponent(tweet.html)+""); + $(thisExpando).append(sandboxFrame); + // $(thisExpando).html(tweet.html); + thisExpando.style.display = 'block'; + thisExpando.classList.add('twitterLoaded'); + */ + case 'getLocalStorage': + // Does RESStorage have actual data in it? If it doesn't, they're a legacy user, we need to copy + // old school localStorage from the foreground page to the background page to keep their settings... + if (typeof msgEvent.message.importedFromForeground === 'undefined') { + // it doesn't exist.. copy it over... + var thisJSON = { + requestType: 'saveLocalStorage', + data: localStorage + }; + self.postMessage(thisJSON); + } else { + setUpRESStorage(msgEvent.message); + //RESInit(); + } + break; + case 'saveLocalStorage': + // Okay, we just copied localStorage from foreground to background, let's set it up... + setUpRESStorage(msgEvent.message); + break; + case 'localStorage': + RESStorage.setItem(msgEvent.itemName, msgEvent.itemValue, true); + break; + default: + // console.log('unknown event type in self.on'); + // console.log(msgEvent.toSource()); + break; + } + }); +} + +// This is the message handler for Safari - the background page calls this function with return data... +function safariMessageHandler(msgEvent) { + switch (msgEvent.name) { + case 'GM_xmlhttpRequest': + // Fire the appropriate onload function for this xmlhttprequest. + xhrQueue.onloads[msgEvent.message.XHRID](msgEvent.message); + break; + case 'compareVersion': + var forceUpdate = false; + if (typeof msgEvent.message.forceUpdate !== 'undefined') forceUpdate = true; + RESUtils.compareVersion(msgEvent.message, forceUpdate); + break; + case 'loadTweet': + var tweet = msgEvent.message; + var thisExpando = modules['styleTweaks'].tweetExpando; + $(thisExpando).html(tweet.html); + thisExpando.style.display = 'block'; + thisExpando.classList.add('twitterLoaded'); + break; + case 'getLocalStorage': + // Does RESStorage have actual data in it? If it doesn't, they're a legacy user, we need to copy + // old schol localStorage from the foreground page to the background page to keep their settings... + if (typeof msgEvent.message.importedFromForeground === 'undefined') { + // it doesn't exist.. copy it over... + var thisJSON = { + requestType: 'saveLocalStorage', + data: localStorage + }; + safari.self.tab.dispatchMessage('saveLocalStorage', thisJSON); + } else { + setUpRESStorage(msgEvent.message); + //RESInit(); + } + break; + case 'saveLocalStorage': + // Okay, we just copied localStorage from foreground to background, let's set it up... + setUpRESStorage(msgEvent.message); + //RESInit(); + break; + case 'addURLToHistory': + var url = msgEvent.message.url; + modules['showImages'].imageTrackFrame.contentWindow.location.replace(url); + break; + case 'localStorage': + RESStorage.setItem(msgEvent.message.itemName, msgEvent.message.itemValue, true); + break; + default: + // console.log('unknown event type in safariMessageHandler'); + break; + } +} + +// This is the message handler for Opera - the background page calls this function with return data... +function operaMessageHandler(msgEvent) { + var eventData = msgEvent.data; + switch (eventData.msgType) { + case 'GM_xmlhttpRequest': + // Fire the appropriate onload function for this xmlhttprequest. + xhrQueue.onloads[eventData.XHRID](eventData.data); + break; + case 'compareVersion': + var forceUpdate = false; + if (typeof eventData.data.forceUpdate !== 'undefined') forceUpdate = true; + RESUtils.compareVersion(eventData.data, forceUpdate); + break; + case 'loadTweet': + var tweet = eventData.data; + var thisExpando = modules['styleTweaks'].tweetExpando; + $(thisExpando).html(tweet.html); + thisExpando.style.display = 'block'; + thisExpando.classList.add('twitterLoaded'); + break; + case 'getLocalStorage': + // Does RESStorage have actual data in it? If it doesn't, they're a legacy user, we need to copy + // old schol localStorage from the foreground page to the background page to keep their settings... + if (typeof eventData.data.importedFromForeground === 'undefined') { + // it doesn't exist.. copy it over... + var thisJSON = { + requestType: 'saveLocalStorage', + data: localStorage + }; + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else { + if (location.hostname.match('reddit')) { + setUpRESStorage(eventData.data); + //RESInit(); + } + } + break; + case 'saveLocalStorage': + // Okay, we just copied localStorage from foreground to background, let's set it up... + setUpRESStorage(eventData.data); + if (location.hostname.match('reddit')) { + //RESInit(); + } + break; + case 'localStorage': + if ((typeof RESStorage !== 'undefined') && (typeof RESStorage.setItem === 'function')) { + RESStorage.setItem(eventData.itemName, eventData.itemValue, true); + } else { + // a change in opera requires this wait/timeout for the RESStorage grab to work... + var waitForRESStorage = function(eData) { + if ((typeof RESStorage !== 'undefined') && (typeof RESStorage.setItem === 'function')) { + RESStorage.setItem(eData.itemName, eData.itemValue, true); + } else { + setTimeout(function() { waitForRESStorage(eData); }, 200); + } + }; + var savedEventData = { + itemName: eventData.itemName, + itemValue: eventData.itemValue + }; + waitForRESStorage(savedEventData); + } + break; + case 'addURLToHistory': + var url = eventData.url; + if (! eventData.isPrivate) { + modules['showImages'].imageTrackFrame.contentWindow.location.replace(url); + } + break; + default: + // console.log('unknown event type in operaMessageHandler'); + break; + } +} + +// listen for messages from chrome background page +if (BrowserDetect.isChrome()) { + chrome.extension.onMessage.addListener( + function(request, sender, sendResponse) { + switch(request.requestType) { + case 'localStorage': + RESStorage.setItem(request.itemName, request.itemValue, true); + break; + default: + // sendResponse({status: "unrecognized request type"}); + break; + } + } + ); +} + +if (BrowserDetect.isSafari()) { + // Safari has a ridiculous bug that causes it to lose access to safari.self.tab if you click the back button. + // this stupid one liner fixes that. + window.onunload = function(){}; + safari.self.addEventListener("message", safariMessageHandler, false); +} +// we can't do this check for opera here because we need to wait until DOMContentLoaded is triggered, I think. Putting this in RESinit(); + +// opera compatibility +if (BrowserDetect.isOpera()) { + // removing this line for new localStorage methodology (store in extension localstorage) + sessionStorage = window.sessionStorage; + localStorage = window.localStorage; + location = window.location; + XMLHttpRequest = window.XMLHttpRequest; +} + +// Firebug stopped showing console.log for some reason. Need to use unsafeWindow if available. Not sure if this was due to a Firebug version update or what. +if (typeof unsafeWindow !== 'undefined') { + if ((typeof unsafeWindow.console !== 'undefined') && (!BrowserDetect.isFirefox())) { + console = unsafeWindow.console; + } else if (typeof console === 'undefined') { + console = { + log: function(str) { + return false; + } + }; + } +} + + + +// GreaseMonkey API compatibility for non-GM browsers (Chrome, Safari, Firefox) +// @copyright 2009, 2010 James Campos +// @modified 2010 Steve Sobel - added some missing gm_* functions +// @license cc-by-3.0; http://creativecommons.org/licenses/by/3.0/ +if ((typeof GM_deleteValue === 'undefined') || (typeof GM_addStyle === 'undefined')) { + GM_addStyle = function(css) { + var style = document.createElement('style'); + style.textContent = css; + var head = document.getElementsByTagName('head')[0]; + if (head) { + head.appendChild(style); + } + }; + + GM_deleteValue = function(name) { + localStorage.removeItem(name); + }; + + GM_getValue = function(name, defaultValue) { + var value = localStorage.getItem(name); + if (!value) + return defaultValue; + var type = value[0]; + value = value.substring(1); + switch (type) { + case 'b': + return value === 'true'; + case 'n': + return Number(value); + default: + return value; + } + }; + + GM_log = function(message) { + console.log(message); + }; + + GM_registerMenuCommand = function(name, funk) { + //todo + }; + + GM_setValue = function(name, value) { + value = (typeof value)[0] + value; + localStorage.setItem(name, value); + }; + + if (BrowserDetect.browser === "Explorer") { + GM_xmlhttpRequest = function(obj) { + var request, + crossDomain = (obj.url.indexOf(location.hostname) === -1); + if ((typeof obj.onload !== 'undefined') && (crossDomain)) { + obj.requestType = 'GM_xmlhttpRequest'; + request = new XDomainRequest(); + request.onload = function() {obj.onload(request);}; + request.onerror = function() {if (obj.onerror) {obj.onerror(request);}}; + request.open(obj.method,obj.url); + request.send(obj.data); + return request; + } else { + request = new XMLHttpRequest(); + request.onreadystatechange=function() { + if (obj.onreadystatechange) { + obj.onreadystatechange(request); + } + if (request.readyState === 4 && obj.onload) { + obj.onload(request); + } + }; + request.onerror = function() { + if(obj.onerror) { + obj.onerror(request); + } + }; + try { + request.open(obj.method,obj.url,true); + } catch(e) { + if(obj.onerror) { + obj.onerror({ + readyState:4, + responseHeaders:'', + responseText:'', + responseXML:'', + status:403, + statusText:'Forbidden' + }); + } + return; + } + if(obj.headers) { + for (var name in obj.headers) { + request.setRequestHeader(name,obj.headers[name]); + } + } + request.send(obj.data); + return request; + } + }; + } + if (BrowserDetect.isChrome()) { + GM_xmlhttpRequest = function(obj) { + var crossDomain = (obj.url.indexOf(location.hostname) === -1); + + if ((typeof obj.onload !== 'undefined') && (crossDomain)) { + obj.requestType = 'GM_xmlhttpRequest'; + if (typeof obj.onload !== 'undefined') { + chrome.extension.sendMessage(obj, function(response) { + obj.onload(response); + }); + } + } else { + var request=new XMLHttpRequest(); + request.onreadystatechange = function() { + if (obj.onreadystatechange) { + obj.onreadystatechange(request); + } + if(request.readyState === 4 && obj.onload) { + obj.onload(request); + } + }; + request.onerror = function() { + if(obj.onerror) { + obj.onerror(request); + } + }; + try { + request.open(obj.method,obj.url,true); + } catch(e) { + if (obj.onerror) { + obj.onerror({ + readyState:4, + responseHeaders:'', + responseText:'', + responseXML:'', + status:403, + statusText:'Forbidden' + }); + } + return; + } + if(obj.headers) { for(var name in obj.headers) { request.setRequestHeader(name,obj.headers[name]); } } + request.send(obj.data); return request; + } + }; + } else if (BrowserDetect.isSafari()) { + GM_xmlhttpRequest = function(obj) { + obj.requestType = 'GM_xmlhttpRequest'; + // Since Safari doesn't provide legitimate callbacks, I have to store the onload function here in the main + // userscript in a queue (see xhrQueue), wait for data to come back from the background page, then call the onload. + + // oy vey... another problem. When Safari sends xmlhttpRequests from the background page, it loses the cookies etc that it'd have + // had from the foreground page... so we need to write a bit of a hack here, and call different functions based on whether or + // not the request is cross domain... For same-domain requests, we'll call from the foreground... + var crossDomain = (obj.url.indexOf(location.hostname) === -1); + + if ((typeof obj.onload !== 'undefined') && (crossDomain)) { + obj.XHRID = xhrQueue.count; + xhrQueue.onloads[xhrQueue.count] = obj.onload; + safari.self.tab.dispatchMessage("GM_xmlhttpRequest", obj); + xhrQueue.count++; + } else { + var request=new XMLHttpRequest(); + request.onreadystatechange = function() { + if (obj.onreadystatechange) { + obj.onreadystatechange(request); + } + if (request.readyState === 4 && obj.onload) { + obj.onload(request); + } + }; + request.onerror = function() { + if(obj.onerror) { + obj.onerror(request); + } + }; + try { + request.open(obj.method,obj.url,true); + } catch(e) { + if (obj.onerror) { + obj.onerror({ + readyState:4, + responseHeaders:'', + responseText:'', + responseXML:'', + status:403, + statusText:'Forbidden' + }); + } + return; + } + if(obj.headers) { for(var name in obj.headers) { request.setRequestHeader(name,obj.headers[name]); } } + request.send(obj.data); return request; + } + }; + } else if (BrowserDetect.isOpera()) { + GM_xmlhttpRequest = function(obj) { + obj.requestType = 'GM_xmlhttpRequest'; + // Turns out, Opera works this way too, but I'll forgive them since their extensions are so young and they're awesome people... + + // oy vey... cross domain same issue with Opera. + var crossDomain = (obj.url.indexOf(location.hostname) === -1); + + if ((typeof obj.onload !== 'undefined') && (crossDomain)) { + obj.XHRID = xhrQueue.count; + xhrQueue.onloads[xhrQueue.count] = obj.onload; + opera.extension.postMessage(JSON.stringify(obj)); + xhrQueue.count++; + } else { + var request=new XMLHttpRequest(); + request.onreadystatechange = function() { + if (obj.onreadystatechange) { + obj.onreadystatechange(request); + } + if (request.readyState === 4 && obj.onload) { + obj.onload(request); + } + }; + request.onerror = function() { + if(obj.onerror) { + obj.onerror(request); + } + }; + try { + request.open(obj.method,obj.url,true); + } catch(e) { + if (obj.onerror) { + obj.onerror({ + readyState:4, + responseHeaders:'', + responseText:'', + responseXML:'', + status:403, + statusText:'Forbidden' + }); + } + return; + } + if (obj.headers) { + for (var name in obj.headers) { + request.setRequestHeader(name,obj.headers[name]); + } + } + request.send(obj.data); return request; + } + }; + } else if (BrowserDetect.isFirefox()) { + // we must be in a Firefox / jetpack addon... + GM_xmlhttpRequest = function(obj) { + var crossDomain = (obj.url.indexOf(location.hostname) === -1); + + if ((typeof obj.onload !== 'undefined') && (crossDomain)) { + obj.requestType = 'GM_xmlhttpRequest'; + // okay, firefox's jetpack addon does this same stuff... le sigh.. + if (typeof obj.onload !== 'undefined') { + obj.XHRID = xhrQueue.count; + xhrQueue.onloads[xhrQueue.count] = obj.onload; + self.postMessage(obj); + xhrQueue.count++; + } + } else { + var request=new XMLHttpRequest(); + request.onreadystatechange = function() { + if (obj.onreadystatechange) { + obj.onreadystatechange(request); + } + if(request.readyState === 4 && obj.onload) { + obj.onload(request); + } + }; + request.onerror = function() { + if(obj.onerror) { + obj.onerror(request); + } + }; + try { + request.open(obj.method,obj.url,true); + } catch(e) { + if (obj.onerror) { + obj.onerror({ + readyState:4, + responseHeaders:'', + responseText:'', + responseXML:'', + status:403, + statusText:'Forbidden' + }); + } + return; + } + if(obj.headers) { for(var name in obj.headers) { request.setRequestHeader(name,obj.headers[name]); } } + request.send(obj.data); return request; + } + }; + } +} else { + // this hack is to avoid an unsafeWindow error message if a gm_xhr is ever called as a result of a jQuery-induced ajax call. + // yes, it's ugly, but it's necessary if we're using Greasemonkey together with jQuery this way. + var oldgmx = GM_xmlhttpRequest; + GM_xmlhttpRequest = function(params) { + setTimeout(function() { + oldgmx(params); + }, 0); + }; +} + + +var modules = {}; + +// define common RESUtils - reddit related functions and data that may need to be accessed... +var RESUtils = { + preInit: function() { + // we store a localStorage key because the async call is too slow to add classes to + // the document prior to page load, thus the flash of unstyled content. + RESUtils.getDocHTML(); + }, + // to avoid the flash of unstyled content, the very first thing we should do is get a hold + // of the document object and add necessary classes... + getDocHTML: function() { + if (document) { + document.html = document.documentElement; + if (localStorage.getItem('RES_nightMode')) { + // no need to check the background - we're in night mode for sure. + modules['styleTweaks'].redditDark(); + } + } else { + setTimeout(RESUtils.getDocHTML, 1); + } + }, + // A cache variable to store CSS that will be applied at the end of execution... + randomHash: function(len) { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var numChars = len || 5; + var randomString = ''; + for (var i=0; i').html(css).appendTo('head'); + return { + remove: function() { style.remove(); } + }; + } else { + this.css += css; + } + }, + insertParam: function(href, key, value) { + var pre = '&'; + if (href.indexOf('?') === -1) pre = '?'; + return href + pre + key + '=' + value; + }, + // checks if script should run on current URL using exclude / include. + isMatchURL: function (moduleID) { + var i=0; + var currURL = location.href; + // get includes and excludes... + var excludes = modules[moduleID].exclude; + var includes = modules[moduleID].include; + // first check excludes... + if (typeof excludes !== 'undefined') { + for (i=0, len = excludes.length; i span.user > a'); + if ((userLink !== null) && (!userLink.classList.contains('login-required'))) { + this.loggedInUserCached = userLink.innerHTML; + this.loggedInUserHashCached = document.querySelector('[name=uh]').value; + } else { + if (tryingEarly) { + // trying early means we're trying before DOM load may be complete, so if we fail here + // we don't want to null this, we want to allow another try. + // currently the only place this is really used is username hider, which tries (if possible) + // to hide the username as early/fast as possible. + delete this.loggedInUserCached; + delete this.loggedInUserHashCached; + } else { + this.loggedInUserCached = null; + } + } + } + return this.loggedInUserCached; + }, + loggedInUserHash: function() { + this.loggedInUser(); + return this.loggedInUserHashCached; + }, + getUserInfo: function(callback, username, live) { + // Default to currently logged-in user, for backwards compatibility + username = (typeof username !== "undefined" ? username : RESUtils.loggedInUser()); + if (username === null) return false; + + // Default to getting live data (i.e. from reddit's server) + live = (typeof live === "boolean" ? live : true); + + if (!(username in RESUtils.userInfoCallbacks)) { + RESUtils.userInfoCallbacks[username] = []; + } + RESUtils.userInfoCallbacks[username].push(callback); + var cacheData = RESStorage.getItem('RESUtils.userInfoCache.' + username) || '{}'; + var userInfoCache = safeJSON.parse(cacheData); + var lastCheck = (userInfoCache !== null) ? parseInt(userInfoCache.lastCheck, 10) || 0 : 0; + var now = new Date(); + // 300000 = 5 minutes + if (live && (now.getTime() - lastCheck) > 300000) { + if (!RESUtils.userInfoRunning) { + RESUtils.userInfoRunning = true; + GM_xmlhttpRequest({ + method: "GET", + url: location.protocol + "//" + location.hostname + "/user/" + encodeURIComponent(username) + "/about.json?app=res", + onload: function(response) { + var thisResponse = JSON.parse(response.responseText); + var userInfoCache = { + lastCheck: now.getTime(), + userInfo: thisResponse + }; + RESStorage.setItem('RESUtils.userInfoCache.' + username, JSON.stringify(userInfoCache)); + while (RESUtils.userInfoCallbacks[username].length > 0) { + var thisCallback = RESUtils.userInfoCallbacks[username].pop(); + thisCallback(userInfoCache.userInfo); + } + RESUtils.userInfoRunning = false; + } + }); + } + } else { + while (RESUtils.userInfoCallbacks[username].length > 0) { + var thisCallback = RESUtils.userInfoCallbacks[username].pop(); + thisCallback(userInfoCache.userInfo); + } + } + }, + userInfoCallbacks: {}, + commentsRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]*comments\/?[-\w\.\/]*/i, + friendsCommentsRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/r\/friends\/*comments\/?/i, + inboxRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/message\/[-\w\.\/]*/i, + profileRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/user\/[-\w\.#=]*\/?(comments)?\/?(\?([a-z]+=[a-zA-Z0-9_%]*&?)*)?$/i, // fix to regex contributed by s_quark + submitRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/([-\w\.\/]*\/)?submit\/?(\?.*)?$/i, + prefsRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/prefs\/?/i, + wikiRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[-\w\.]+\/wiki?/i, + pageType: function() { + if (typeof this.pageTypeSaved === 'undefined') { + var pageType = ''; + var currURL = location.href.split('#')[0]; + if (RESUtils.profileRegex.test(currURL)) { + pageType = 'profile'; + } else if ((RESUtils.commentsRegex.test(currURL)) || (RESUtils.friendsCommentsRegex.test(currURL))) { + pageType = 'comments'; + } else if (RESUtils.inboxRegex.test(currURL)) { + pageType = 'inbox'; + } else if (RESUtils.submitRegex.test(currURL)) { + pageType = 'submit'; + } else if (RESUtils.prefsRegex.test(currURL)) { + pageType = 'prefs'; + } else if (RESUtils.wikiRegex.test(currURL)) { + pageType = 'wiki'; + } else { + pageType = 'linklist'; + } + this.pageTypeSaved = pageType; + } + return this.pageTypeSaved; + }, + commentPermalinkRegex: /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]*comments\/[a-z0-9]+\/[^\/]+\/[a-z0-9]+\/?$/i, + isCommentPermalinkPage: function() { + if (typeof this.isCommentPermalinkSaved === 'undefined') { + var currURL = location.href.split('#')[0]; + if (RESUtils.commentPermalinkRegex.test(currURL)) { + this.isCommentPermalinkSaved = true; + } else { + this.isCommentPermalinkSaved = false; + } + } + + return this.isCommentPermalinkSaved; + }, + matchRE: /^https?:\/\/(?:[a-z]+)\.reddit\.com\/r\/([\w\.\+]+).*/i, + matchDOM: /^https?:\/\/(?:[a-z]+)\.reddit\.com\/domain\/([\w\.\+]+).*/i, + currentSubreddit: function(check) { + if (typeof this.curSub === 'undefined') { + var match = location.href.match(RESUtils.matchRE); + if (match !== null) { + this.curSub = match[1]; + if (check) return (match[1].toLowerCase() === check.toLowerCase()); + return match[1]; + } else { + if (check) return false; + return null; + } + } else { + if (check) return (this.curSub.toLowerCase() === check.toLowerCase()); + return this.curSub; + } + }, + currentDomain: function(check) { + if (typeof this.curDom === 'undefined') { + var match = location.href.match(RESUtils.matchDOM); + if (match !== null) { + this.curDom = match[1]; + if (check) return (match[1].toLowerCase() === check.toLowerCase()); + return match[1]; + } else { + if (check) return false; + return null; + } + } else { + if (check) return (this.curDom.toLowerCase() === check.toLowerCase()); + return this.curDom; + } + }, + currentUserProfile: function() { + if (typeof this.curUserProfile === 'undefined') { + var match = location.href.match(/^https?:\/\/(?:[a-z]+)\.reddit\.com\/user\/([\w\.]+).*/i); + if (match !== null) { + this.curUserProfile = match[1]; + return match[1]; + } else { + return null; + } + } else { + return this.curUserProfile; + } + }, + getXYpos: function (obj) { + var topValue= 0,leftValue= 0; + while(obj) { + leftValue += obj.offsetLeft; + topValue += obj.offsetTop; + obj = obj.offsetParent; + } + return { 'x': leftValue, 'y': topValue }; + }, + elementInViewport: function (obj) { + // check the headerOffset - if we've pinned the subreddit bar, we need to add some pixels so the "visible" stuff is lower down the page. + var headerOffset = this.getHeaderOffset(); + var top = obj.offsetTop - headerOffset; + var left = obj.offsetLeft; + var width = obj.offsetWidth; + var height = obj.offsetHeight; + while(obj.offsetParent) { + obj = obj.offsetParent; + top += obj.offsetTop; + left += obj.offsetLeft; + } + return ( + top >= window.pageYOffset && + left >= window.pageXOffset && + (top + height) <= (window.pageYOffset + window.innerHeight - headerOffset) && + (left + width) <= (window.pageXOffset + window.innerWidth) + ); + }, + setMouseXY: function(e) { + e = e || window.event; + var cursor = {x:0, y:0}; + if (e.pageX || e.pageY) { + cursor.x = e.pageX; + cursor.y = e.pageY; + } else { + cursor.x = e.clientX + + (document.documentElement.scrollLeft || + document.body.scrollLeft) - + document.documentElement.clientLeft; + cursor.y = e.clientY + + (document.documentElement.scrollTop || + document.body.scrollTop) - + document.documentElement.clientTop; + } + RESUtils.mouseX = cursor.x; + RESUtils.mouseY = cursor.y; + }, + elementUnderMouse: function ( obj ) { + var $obj = $(obj), + top = $obj.offset().top, + left = $obj.offset().left, + width = $obj.outerWidth(), + height = $obj.outerHeight(), + right = left + width, + bottom = top + height; + if ((RESUtils.mouseX >= left) && (RESUtils.mouseX <= right) && (RESUtils.mouseY >= top) && (RESUtils.mouseY <= bottom)) { + return true; + } else { + return false; + } + }, + doElementsCollide: function (ele1, ele2, margin) { + margin = margin || 0; + ele1 = $(ele1); + ele2 = $(ele2); + + var dims1 = ele1.offset(); + dims1.right = dims1.left + ele1.width(); + dims1.bottom = dims1.top + ele1.height(); + + dims1.left -= margin; + dims1.top -= margin; + dims1.right += margin; + dims1.bottom += margin; + + + var dims2 = ele2.offset(); + dims2.right = dims2.left + ele2.width(); + dims2.bottom = dims2.top + ele2.height(); + + if ( + ( + (dims1.left < dims2.left && dims2.left < dims1.right) || + (dims1.left < dims2.right && dims2.right < dims1.right) || + (dims2.left < dims1.left && dims1.left < dims2.right) || + (dims2.left < dims1.right && dims1.right < dims2.right) + ) && + ( + (dims1.top < dims2.top && dims2.top < dims1.bottom) || + (dims1.top < dims2.bottom && dims2.bottom < dims1.bottom) || + (dims2.top < dims1.top && dims1.top < dims2.bottom) || + (dims2.top < dims1.bottom && dims1.bottom < dims2.bottom)) + ) + { + // In layman's terms: + // If one of the box's left/right borders is between the other box's left/right + // and same with top/bottom, + // then they collide. + // This could probably be logicked into a more compact form. + + return true; + } + + return false; + }, + scrollTo: function(x,y) { + var headerOffset = this.getHeaderOffset(); + window.scrollTo(x,y-headerOffset); + }, + getHeaderOffset: function() { + if (typeof this.headerOffset === 'undefined') { + this.headerOffset = 0; + switch (modules['betteReddit'].options.pinHeader.value) { + case 'none': + break; + case 'sub': + this.theHeader = document.querySelector('#sr-header-area'); + break; + case 'subanduser': + this.theHeader = document.querySelector('#sr-header-area'); + break; + case 'header': + this.theHeader = document.querySelector('#header'); + break; + } + if (this.theHeader) { + this.headerOffset = this.theHeader.offsetHeight + 6; + } + } + return this.headerOffset; + }, + setSelectValue: function(obj, value) { + for (var i=0, len=obj.length; i < len; i++) { + // for some reason in firefox, obj[0] is undefined... weird. adding a test for existence of obj[i]... + // okay, now as of ff8, it's even barfing here unless we console.log out a check - nonsensical. + // a bug has been filed to bugzilla at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=702847 + if ((obj[i]) && (obj[i].value == value)) { + obj[i].selected = true; + } + } + }, + stripHTML: function(str) { + var regExp = /<\/?[^>]+>/gi; + str = str.replace(regExp, ""); + return str; + }, + sanitizeHTML: function(htmlStr) { + if (!this.sanitizer) { + var SnuOwnd = window.SnuOwnd; + var redditCallbacks = SnuOwnd.getRedditCallbacks(); + var callbacks = SnuOwnd.createCustomCallbacks({ + paragraph: function(out, text, options){ + if (text) out.s += text.s; + }, + autolink: redditCallbacks.autolink, + raw_html_tag: redditCallbacks.raw_html_tag + }); + var rendererConfig = SnuOwnd.defaultRenderState(); + rendererConfig.flags = SnuOwnd.DEFAULT_WIKI_FLAGS; + rendererConfig.html_element_whitelist = [ + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'div', 'code', + 'br', 'hr', 'p', 'a', 'img', 'pre', 'blockquote', 'table', + 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'strong', 'em', + 'i', 'b', 'u', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', + 'font', 'center', 'small', 's', 'q', 'sub', 'sup', 'del' + ]; + rendererConfig.html_attr_whitelist = [ + 'href', 'title', 'src', 'alt', 'colspan', + 'rowspan', 'cellspacing', 'cellpadding', 'scope', + 'face', 'color', 'size', 'bgcolor', 'align' + ]; + this.sanitizer = SnuOwnd.getParser({ + callbacks: callbacks, + context: rendererConfig + }); + } + return this.sanitizer.render(htmlStr); + }, + fadeElementOut: function(obj, speed, callback) { + if (obj.getAttribute('isfading') === 'in') { + return false; + } + obj.setAttribute('isfading','out'); + speed = speed || 0.1; + if (obj.style.opacity === '') obj.style.opacity = '1'; + if (obj.style.opacity <= 0) { + obj.style.display = 'none'; + obj.setAttribute('isfading',false); + if (callback) callback(); + return true; + } else { + var newOpacity = parseFloat(obj.style.opacity) - speed; + if (newOpacity < speed) newOpacity = 0; + obj.style.opacity = newOpacity; + setTimeout(function() { RESUtils.fadeElementOut(obj, speed, callback); }, 100); + } + }, + fadeElementIn: function(obj, speed, finalOpacity) { + finalOpacity = finalOpacity || 1; + if (obj.getAttribute('isfading') === 'out') { + return false; + } + obj.setAttribute('isfading','in'); + speed = speed || 0.1; + if ((obj.style.display === 'none') || (obj.style.display === '')) { + obj.style.opacity = 0; + obj.style.display = 'block'; + } + if (obj.style.opacity >= finalOpacity) { + obj.setAttribute('isfading',false); + obj.style.opacity = finalOpacity; + return true; + } else { + var newOpacity = parseFloat(obj.style.opacity) + parseFloat(speed); + if (newOpacity > finalOpacity) newOpacity = finalOpacity; + obj.style.opacity = newOpacity; + setTimeout(function() { RESUtils.fadeElementIn(obj, speed, finalOpacity); }, 100); + } + }, + setCursorPosition: function(form, pos) { + elem = $(form)[0]; + if (!elem) return; + + if (elem.setSelectionRange) { + elem.setSelectionRange(pos, pos); + } else if (elem.createTextRange) { + var range = elem.createTextRange(); + range.collapse(true); + range.moveEnd('character', pos); + range.moveStart('character', pos); + range.select(); + } + + return form; + }, + setNewNotification: function() { + $('#RESSettingsButton, #RESMainGearOverlay .gearIcon').addClass('newNotification').click(function() { + location.href = '/r/RESAnnouncements'; + }); + }, + createMultiLock: function() { + var locks = {}; + var count = 0; + + return { + lock: function(lockname, value) { + if (typeof lockname === "undefined") return; + if (locks[lockname]) return; + + locks[lockname] = value || true; + count++; + return true; + }, + unlock: function(lockname) { + if (typeof lockname === "undefined") return; + if (!locks[lockname]) return; + + locks[lockname] = false; + count--; + return true; + }, + locked: function(lockname) { + if (typeof lockname !== "undefined") { + // Is this lock set? + return locks[lockname]; + } else { + // Is any lock set? + return count > 0; + } + } + }; + }, + indexOptionTable: function(moduleID, optionKey, keyFieldIndex) { + var source = modules[moduleID].options[optionKey].value; + var keyIsList = + modules[moduleID].options[optionKey].fields[keyFieldIndex].type === 'list' ? + ',' : + false; + return RESUtils.indexArrayByProperty(source, keyFieldIndex, keyIsList); + }, + indexArrayByProperty: function(source, keyIndex, keyValueSeparator) { + if (!source || !source.length) { + return function() { }; + } + + var index = createIndex(); + return getItem; + + function createIndex() { + var index = {}; + + for (var i = 0, length = source.length; i < length; i++) { + var item = source[i]; + var key = item && item[keyIndex]; + if (!key) continue; + + if (keyValueSeparator) { + var keys = key.toLowerCase().split(keyValueSeparator); + for (var ki = 0, klength = keys.length; ki < klength; ki++) { + key = keys[ki]; + index[key] = item; + } + } else { + index[key] = item; + } + } + + return index; + } + + function getItem(key) { + key = key && key.toLowerCase(); + var item = index[key]; + return item; + } + }, + inList: function(needle, haystack, separator, isCaseSensitive) { + if (!needle || !haystack) return false; + + separator = separator || ','; + + if (haystack.indexOf(separator) !== -1) { + var haystacks = haystack.split(separator); + if (RESUtils.inArray(needle, haystacks, isCaseSensitive)) { + return true; + } + } else { + if (caseSensitive) { + return (needle == haystack); + } else { + return (needle.toLowerCase() == haystack.toLowerCase()); + } + } + }, + inArray: function(needle, haystacks, isCaseSensitive) { + if (!isCaseSensitive) needle = needle.toLowerCase(); + + for (var i = 0, length = haystacks.length; i < length; i++) { + if (isCaseSensitive) { + if (needle == haystacks[i]) { + return true; + } + } else { + if (needle == haystacks[i].toLowerCase()) { + return true; + } + } + } + }, + firstRun: function() { + // if this is the first time this version has been run, pop open the what's new tab, background focused. + if (RESStorage.getItem('RES.firstRun.'+RESVersion) === null) { + RESStorage.setItem('RES.firstRun.'+RESVersion,'true'); + RESUtils.openLinkInNewTab('http://redditenhancementsuite.com/whatsnew.html?v='+RESVersion, false); + } + }, + // checkForUpdate: function(forceUpdate) { + checkForUpdate: function() { + if (RESUtils.currentSubreddit('RESAnnouncements')) { + RESStorage.removeItem('RES.newAnnouncement','true'); + } + var now = new Date(); + var lastCheck = parseInt(RESStorage.getItem('RESLastUpdateCheck'), 10) || 0; + // if we haven't checked for an update in 24 hours, check for one now! + // if (((now.getTime() - lastCheck) > 86400000) || (RESVersion > RESStorage.getItem('RESlatestVersion')) || ((RESStorage.getItem('RESoutdated') === 'true') && (RESVersion == RESStorage.getItem('RESlatestVersion'))) || forceUpdate) { + if ((now.getTime() - lastCheck) > 86400000) { + // now we're just going to check /r/RESAnnouncements for new posts, we're not checking version numbers... + var lastID = RESStorage.getItem('RES.lastAnnouncementID'); + $.getJSON('/r/RESAnnouncements/.json?limit=1&app=res', function(data) { + RESStorage.setItem('RESLastUpdateCheck',now.getTime()); + var thisID = data.data.children[0].data.id; + if (thisID != lastID) { + RESStorage.setItem('RES.newAnnouncement','true'); + RESUtils.setNewNotification(); + } + RESStorage.setItem('RES.lastAnnouncementID', thisID); + }); + /* + var jsonURL = 'http://reddit.honestbleeps.com/update.json?v=' + RESVersion; + // mark off that we've checked for an update... + RESStorage.setItem('RESLastUpdateCheck',now.getTime()); + var outdated = false; + if (BrowserDetect.isChrome()) { + // we've got chrome, so we need to hit up the background page to do cross domain XHR + var thisJSON = { + requestType: 'compareVersion', + url: jsonURL + }; + chrome.extension.sendMessage(thisJSON, function(response) { + // send message to background.html to open new tabs... + outdated = RESUtils.compareVersion(response, forceUpdate); + }); + } else if (BrowserDetect.isSafari()) { + // we've got safari, so we need to hit up the background page to do cross domain XHR + thisJSON = { + requestType: 'compareVersion', + url: jsonURL, + forceUpdate: forceUpdate + } + safari.self.tab.dispatchMessage("compareVersion", thisJSON); + } else if (BrowserDetect.isOpera()) { + // we've got opera, so we need to hit up the background page to do cross domain XHR + thisJSON = { + requestType: 'compareVersion', + url: jsonURL, + forceUpdate: forceUpdate + } + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else { + // we've got greasemonkey, so we can do cross domain XHR. + GM_xmlhttpRequest({ + method: "GET", + url: jsonURL, + onload: function(response) { + outdated = RESUtils.compareVersion(JSON.parse(response.responseText), forceUpdate); + } + }); + } + */ + } + }, + /* + compareVersion: function(response, forceUpdate) { + if (RESVersion < response.latestVersion) { + RESStorage.setItem('RESoutdated','true'); + RESStorage.setItem('RESlatestVersion',response.latestVersion); + RESStorage.setItem('RESmessage',response.message); + if (forceUpdate) { + $(RESConsole.RESCheckUpdateButton).html('You are out of date! [click to update]'); + } + return true; + } else { + RESStorage.setItem('RESlatestVersion',response.latestVersion); + RESStorage.setItem('RESoutdated','false'); + if (forceUpdate) { + $(RESConsole.RESCheckUpdateButton).html('You are up to date!'); + } + return false; + } + }, + */ + proEnabled: function() { + return ((typeof modules['RESPro'] !== 'undefined') && (modules['RESPro'].isEnabled())); + }, + niceKeyCode: function(charCode) { + var keyComboString = ''; + var testCode, niceString; + if (typeof charCode === 'string') { + var tempArray = charCode.split(','); + if (tempArray.length) { + if (tempArray[1] === 'true') keyComboString += 'alt-'; + if (tempArray[2] === 'true') keyComboString += 'ctrl-'; + if (tempArray[3] === 'true') keyComboString += 'shift-'; + if (tempArray[4] === 'true') keyComboString += 'command-'; + } + testCode = parseInt(charCode, 10); + } else if (typeof charCode === 'object') { + testCode = parseInt(charCode[0], 10); + if (charCode[1]) keyComboString += 'alt-'; + if (charCode[2]) keyComboString += 'ctrl-'; + if (charCode[3]) keyComboString += 'shift-'; + if (charCode[4]) keyComboString += 'command-'; + } + switch(testCode) { + case 8: + niceString = "backspace"; // backspace + break; + case 9: + niceString = "tab"; // tab + break; + case 13: + niceString = "enter"; // enter + break; + case 16: + niceString = "shift"; // shift + break; + case 17: + niceString = "ctrl"; // ctrl + break; + case 18: + niceString = "alt"; // alt + break; + case 19: + niceString = "pause/break"; // pause/break + break; + case 20: + niceString = "caps lock"; // caps lock + break; + case 27: + niceString = "escape"; // escape + break; + case 33: + niceString = "page up"; // page up, to avoid displaying alternate character and confusing people + break; + case 34: + niceString = "page down"; // page down + break; + case 35: + niceString = "end"; // end + break; + case 36: + niceString = "home"; // home + break; + case 37: + niceString = "left arrow"; // left arrow + break; + case 38: + niceString = "up arrow"; // up arrow + break; + case 39: + niceString = "right arrow"; // right arrow + break; + case 40: + niceString = "down arrow"; // down arrow + break; + case 45: + niceString = "insert"; // insert + break; + case 46: + niceString = "delete"; // delete + break; + case 91: + niceString = "left window"; // left window + break; + case 92: + niceString = "right window"; // right window + break; + case 93: + niceString = "select key"; // select key + break; + case 96: + niceString = "numpad 0"; // numpad 0 + break; + case 97: + niceString = "numpad 1"; // numpad 1 + break; + case 98: + niceString = "numpad 2"; // numpad 2 + break; + case 99: + niceString = "numpad 3"; // numpad 3 + break; + case 100: + niceString = "numpad 4"; // numpad 4 + break; + case 101: + niceString = "numpad 5"; // numpad 5 + break; + case 102: + niceString = "numpad 6"; // numpad 6 + break; + case 103: + niceString = "numpad 7"; // numpad 7 + break; + case 104: + niceString = "numpad 8"; // numpad 8 + break; + case 105: + niceString = "numpad 9"; // numpad 9 + break; + case 106: + niceString = "multiply"; // multiply + break; + case 107: + niceString = "add"; // add + break; + case 109: + niceString = "subtract"; // subtract + break; + case 110: + niceString = "decimal point"; // decimal point + break; + case 111: + niceString = "divide"; // divide + break; + case 112: + niceString = "F1"; // F1 + break; + case 113: + niceString = "F2"; // F2 + break; + case 114: + niceString = "F3"; // F3 + break; + case 115: + niceString = "F4"; // F4 + break; + case 116: + niceString = "F5"; // F5 + break; + case 117: + niceString = "F6"; // F6 + break; + case 118: + niceString = "F7"; // F7 + break; + case 119: + niceString = "F8"; // F8 + break; + case 120: + niceString = "F9"; // F9 + break; + case 121: + niceString = "F10"; // F10 + break; + case 122: + niceString = "F11"; // F11 + break; + case 123: + niceString = "F12"; // F12 + break; + case 144: + niceString = "num lock"; // num lock + break; + case 145: + niceString = "scroll lock"; // scroll lock + break; + case 186: + niceString = ";"; // semi-colon + break; + case 187: + niceString = "="; // equal-sign + break; + case 188: + niceString = ","; // comma + break; + case 189: + niceString = "-"; // dash + break; + case 190: + niceString = "."; // period + break; + case 191: + niceString = "/"; // forward slash + break; + case 192: + niceString = "`"; // grave accent + break; + case 219: + niceString = "["; // open bracket + break; + case 220: + niceString = "\\"; // back slash + break; + case 221: + niceString = "]"; // close bracket + break; + case 222: + niceString = "'"; // single quote + break; + default: + niceString = String.fromCharCode(testCode); + break; + } + return keyComboString + niceString; + }, + niceDate: function(d, usformat) { + d = d || new Date(); + var year = d.getFullYear(); + var month = (d.getMonth() + 1); + month = (month < 10) ? '0'+month : month; + var day = d.getDate(); + day = (day < 10) ? '0'+day : day; + var fullString = year+'-'+month+'-'+day; + if (usformat) { + fullString = month+'-'+day+'-'+year; + } + return fullString; + }, + niceDateTime: function(d, usformat) { + d = d || new Date(); + var dateString = RESUtils.niceDate(d); + var hours = d.getHours(); + hours = (hours < 10) ? '0'+hours : hours; + var minutes = d.getMinutes(); + minutes = (minutes < 10) ? '0'+minutes : minutes; + var seconds = d.getSeconds(); + seconds = (seconds < 10) ? '0'+seconds : seconds; + var fullString = dateString + ' ' + hours + ':'+minutes+':'+seconds; + return fullString; + }, + niceDateDiff: function(origdate, newdate) { + // Enter the month, day, and year below you want to use as + // the starting point for the date calculation + if (!newdate) { + newdate = new Date(); + } + + var amonth = origdate.getUTCMonth() + 1; + var aday = origdate.getUTCDate(); + var ayear = origdate.getUTCFullYear(); + + var tyear = newdate.getUTCFullYear(); + var tmonth = newdate.getUTCMonth() + 1; + var tday = newdate.getUTCDate(); + + var y = 1; + var mm = 1; + var d = 1; + var a2 = 0; + var a1 = 0; + var f = 28; + + if (((tyear % 4 === 0) && (tyear % 100 !== 0)) || (tyear % 400 === 0)) { + f = 29; + } + + var m = [31, f, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + var dyear = tyear - ayear; + + var dmonth = tmonth - amonth; + if (dmonth < 0 && dyear > 0) { + dmonth = dmonth + 12; + dyear--; + } + + var dday = tday - aday; + if (dday < 0) { + if (dmonth > 0) { + var ma = amonth + tmonth; + + if (ma >= 12) { ma = ma - 12; } + if (ma < 0) { ma = ma + 12; } + dday = dday + m[ma]; + dmonth--; + if (dmonth < 0) { + dyear--; + dmonth = dmonth + 12; + } + } else { + dday = 0; + } + } + + var returnString = ''; + + if (dyear === 0) { y = 0; } + if (dmonth === 0) { mm = 0; } + if (dday === 0) { d = 0; } + if ((y === 1) && (mm === 1)) { a1 = 1; } + if ((y === 1) && (d === 1)) { a1 = 1; } + if ((mm === 1) && (d === 1)) { a2 = 1; } + if (y === 1){ + if (dyear === 1) { + returnString += dyear + " year"; + } else { + returnString += dyear + " years"; + } + } + if ((a1 === 1) && (a2 === 0)) { returnString += " and "; } + if ((a1 === 1) && (a2 === 1)) { returnString += ", "; } + if (mm === 1){ + if (dmonth === 1) { + returnString += dmonth + " month"; + } else { + returnString += dmonth + " months"; + } + } + if (a2 === 1) { returnString += " and "; } + if (d === 1) { + if (dday === 1) { + returnString += dday + " day"; + } else { + returnString += dday + " days"; + } + } + if (returnString === '') { + returnString = '0 days'; + } + return returnString; + }, + checkIfSubmitting: function() { + this.checkedIfSubmitting = true; + if ((location.href.match(/\/r\/[\w]+\/submit\/?/i)) || (location.href.match(/reddit\.com\/submit\/?/i))) { + var thisSubRedditInput = document.getElementById('sr-autocomplete'); + if (thisSubRedditInput) { + var thisSubReddit = thisSubRedditInput.value; + var title = document.querySelector('textarea[name=title]'); + if (typeof this.thisSubRedditInputListener === 'undefined') { + this.thisSubRedditInputListener = true; + thisSubRedditInput.addEventListener('change', function(e) { + RESUtils.checkIfSubmitting(); + }, false); + } + if ((thisSubReddit.toLowerCase() === 'enhancement') || (thisSubReddit.toLowerCase() === 'resissues')) { + RESUtils.addCSS('#submittingToEnhancement { display: none; min-height: 300px; font-size: 14px; line-height: 15px; margin-top: 10px; width: 518px; position: absolute; z-index: 999; } #submittingToEnhancement ol { margin-left: 10px; margin-top: 15px; list-style-type: decimal; } #submittingToEnhancement li { margin-left: 25px; }'); + RESUtils.addCSS('.submittingToEnhancementButton { border: 1px solid #444; border-radius: 2px; padding: 3px 6px; cursor: pointer; display: inline-block; margin-top: 12px; }'); + RESUtils.addCSS('#RESBugReport, #RESFeatureRequest { display: none; }'); + RESUtils.addCSS('#RESSubmitOptions .submittingToEnhancementButton { margin-top: 30px; }'); + var textDesc = document.getElementById('text-desc'); + this.submittingToEnhancement = createElementWithID('div','submittingToEnhancement','RESDialogSmall'); + /*jshint multistr: true */ + var submittingHTML = " \ +

Submitting to r/Enhancement

\ +
\ +
\ + What kind of a post do you want to submit to r/Enhancement? So that we can better support you, please choose from the options below, and please take care to read the instructions, thanks!
\ +
I want to submit a bug report

\ +
I want to submit a feature request

\ +
I want to submit a general question or other item
\ +
\ +
\ + Are you sure you want to submit a bug report? We get a lot of duplicates and it would really help if you took a moment to read the following:
\ +
    \ +
  1. Have you searched /r/RESIssues to see if someone else has reported it?
  2. \ +
  3. Have you checked the RES FAQ?
  4. \ +
  5. Are you sure it's a bug with RES specifically? Do you have any other userscripts/extensions running? How about addons like BetterPrivacy, Ghostery, CCleaner, etc?
  6. \ +
\ +
\ + Please also check out the latest known / popular bugs first:
\ +
  • Loading...
\ + I still want to submit a bug! \ +
\ +
\ + So you want to request a feature, great! Please just consider the following, first:
\ +
    \ +
  1. Have you searched /r/Enhancement to see if someone else has requested it?
  2. \ +
  3. Is it something that would appeal to Reddit as a whole? Personal or subreddit specific requests usually aren't added to RES.
  4. \ +
\ +
\ + Please also check out the latest known popular feature requests first:
\ +
  • Loading...
\ + I still want to submit a feature request! \ +
\ +
"; + $(this.submittingToEnhancement).html(submittingHTML); + insertAfter(textDesc, this.submittingToEnhancement); + setTimeout(function() { + $('#RESSubmitBug').click( + function() { + $('#RESSubmitOptions').fadeOut( + function() { + $('#RESBugReport').fadeIn(); + GM_xmlhttpRequest({ + method: "GET", + url: 'http://redditenhancementsuite.com/knownbugs.json', + onload: function(response) { + $('#RESKnownBugs').html(''); + var data = safeJSON.parse(response.responseText); + $.each(data, function(key, val) { + $('#RESKnownBugs').append('
  • '+val.description+'
  • '); + }); + } + }); + } + ); + } + ); + $('#RESSubmitFeatureRequest').click( + function() { + $('#RESSubmitOptions').fadeOut( + function() { + $('#RESFeatureRequest').fadeIn(); + $.getJSON('http://redditenhancementsuite.com/knownfeaturerequests.json', function(data) { + $('#RESKnownFeatureRequests').html(''); + $.each(data, function(key, val) { + $('#RESKnownFeatureRequests').append('
  • '+val.description+'
  • '); + }); + }); + } + ); + } + ); + $('#submittingBug').click( + function() { + $('#sr-autocomplete').val('RESIssues'); + $('li a.text-button').click(); + $('#submittingToEnhancement').fadeOut(); + + var txt = "- RES Version: " + RESVersion + "\n"; + txt += "- Browser: " + BrowserDetect.browser + "\n"; + if (typeof navigator === 'undefined') navigator = window.navigator; + txt+= "- Browser Version: " + BrowserDetect.version + "\n"; + txt+= "- Cookies Enabled: " + navigator.cookieEnabled + "\n"; + txt+= "- Platform: " + BrowserDetect.OS + "\n"; + txt+= "- Did you search /r/RESIssues before submitting this: No. That, or I didn't notice this text here and edit it!\n\n"; + $('.usertext-edit textarea').val(txt); + title.value = '[bug] Please describe your bug here. If you have screenshots, please link them in the selftext.'; + } + ); + $('#submittingFeature').click( + function() { + $('#sr-autocomplete').val('Enhancement'); + $('#submittingToEnhancement').fadeOut(); + title.value = '[feature request] Please summarize your feature request here, and elaborate in the selftext.'; + } + ); + $('#RESSubmitOther').click( + function() { + $('#sr-autocomplete').val('Enhancement'); + $('#submittingToEnhancement').fadeOut(); + title.value = ''; + } + ); + $('#submittingToEnhancement').fadeIn(); + }, 1000); + } else if (typeof this.submittingToEnhancement !== 'undefined') { + this.submittingToEnhancement.parentNode.removeChild(this.submittingToEnhancement); + if (title.value === 'Submitting a bug? Please read the box above...') { + title.value = ''; + } + } + } + } + }, + isEmpty: function(obj) { + for(var prop in obj) { + if(obj.hasOwnProperty(prop)) + return false; + } + return true; + }, + deleteCookie: function(cookieName) { + var requestJSON = { + requestType: 'deleteCookie', + cname: cookieName + }; + + if (BrowserDetect.isChrome()) { + chrome.extension.sendMessage(requestJSON); + } else if (BrowserDetect.isSafari()) { + document.cookie = cookieName + '=null;expires=' + new Date() +'; path=/;domain=reddit.com'; + } else if (BrowserDetect.isOpera()) { + document.cookie = cookieName + '=null;expires=' + new Date() +'; path=/;domain=reddit.com'; + } else if (BrowserDetect.isFirefox()) { + self.postMessage(requestJSON); + } + }, + openLinkInNewTab: function(url, focus) { + var thisJSON; + if (BrowserDetect.isChrome()) { + thisJSON = { + requestType: 'openLinkInNewTab', + linkURL: url, + button: focus + }; + // send message to background.html to open new tabs... + chrome.extension.sendMessage(thisJSON); + } else if (BrowserDetect.isSafari()) { + thisJSON = { + requestType: 'openLinkInNewTab', + linkURL: url, + button: focus + }; + safari.self.tab.dispatchMessage("openLinkInNewTab", thisJSON); + } else if (BrowserDetect.isOpera()) { + thisJSON = { + requestType: 'openLinkInNewTab', + linkURL: url, + button: focus + }; + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else if (BrowserDetect.isFirefox()) { + thisJSON = { + requestType: 'openLinkInNewTab', + linkURL: url, + button: focus + }; + self.postMessage(thisJSON); + } else { + window.open(url); + } + }, + notification: function(contentObj, delay) { + var content; + if (typeof contentObj.message === 'undefined') { + if (typeof contentObj === 'string') { + content = contentObj; + } else { + return false; + } + } else { + content = contentObj.message; + } + + var header; + if (contentObj.header) { + header = contentObj.header; + } else { + header = []; + + if (contentObj.moduleID && modules[contentObj.moduleID]) { + header.push(modules[contentObj.moduleID].moduleName); + } + + if (contentObj.type === 'error') { + header.push('Error'); + } else { + header.push('Notification'); + } + + + header = header.join(' '); + } + + if (contentObj.moduleID && modules[contentObj.moduleID]) { + header += modules['settingsNavigation'].makeUrlHashLink(contentObj.moduleID, contentObj.optionKey, ' ', 'gearIcon'); + } + + + if (typeof this.notificationCount === 'undefined') { + this.adFrame = document.body.querySelector('#ad-frame'); + if (this.adFrame) { + this.adFrame.style.display = 'none'; + } + this.notificationCount = 0; + this.notificationTimers = []; + this.RESNotifications = createElementWithID('div','RESNotifications'); + document.body.appendChild(this.RESNotifications); + } + var thisNotification = document.createElement('div'); + thisNotification.classList.add('RESNotification'); + thisNotification.setAttribute('id','RESNotification-'+this.notificationCount); + $(thisNotification).html('

    '+header+'

    ×
    '+content+'
    '); + var thisNotificationCloseButton = thisNotification.querySelector('.RESNotificationClose'); + thisNotificationCloseButton.addEventListener('click', function(e) { + var thisNotification = e.target.parentNode.parentNode; + RESUtils.closeNotification(thisNotification); + }, false); + this.setCloseNotificationTimer(thisNotification, delay); + this.RESNotifications.style.display = 'block'; + this.RESNotifications.appendChild(thisNotification); + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'notification'); + RESUtils.fadeElementIn(thisNotification, 0.2, 1); + this.notificationCount++; + }, + setCloseNotificationTimer: function(e, delay) { + delay = delay || 3000; + var thisNotification = (typeof e.currentTarget !== 'undefined') ? e.currentTarget : e; + var thisNotificationID = thisNotification.getAttribute('id').split('-')[1]; + thisNotification.classList.add('timerOn'); + clearTimeout(RESUtils.notificationTimers[thisNotificationID]); + var thisTimer = setTimeout(function() { + RESUtils.closeNotification(thisNotification); + }, delay); + RESUtils.notificationTimers[thisNotificationID] = thisTimer; + thisNotification.addEventListener('mouseover',RESUtils.cancelCloseNotificationTimer, false); + thisNotification.removeEventListener('mouseout',RESUtils.setCloseNotification,false); + }, + cancelCloseNotificationTimer: function(e) { + var thisNotificationID = e.currentTarget.getAttribute('id').split('-')[1]; + e.currentTarget.classList.remove('timerOn'); + clearTimeout(RESUtils.notificationTimers[thisNotificationID]); + e.target.removeEventListener('mouseover',RESUtils.cancelCloseNotification,false); + e.currentTarget.addEventListener('mouseout',RESUtils.setCloseNotificationTimer, false); + }, + closeNotification: function(ele) { + RESUtils.fadeElementOut(ele, 0.1, RESUtils.notificationClosed); + }, + notificationClosed: function(ele) { + var notifications = RESUtils.RESNotifications.querySelectorAll('.RESNotification'); + var destroyed = 0; + for (var i=0, len=notifications.length; i'+onText+''+offText+''); + thisToggle.addEventListener('click', function(e) { + var thisCheckbox = this.querySelector('input[type=checkbox]'); + var enabled = thisCheckbox.checked; + thisCheckbox.checked = !enabled; + if (enabled) { + this.classList.remove('enabled'); + } else { + this.classList.add('enabled'); + } + }, false); + if (enabled) thisToggle.classList.add('enabled'); + return thisToggle; + }, + addCommas: function(nStr) { + nStr += ''; + var x = nStr.split('.'); + var x1 = x[0]; + var x2 = x.length > 1 ? '.' + x[1] : ''; + var rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; + }, + generateTable: function(items, call, context) { + if (!items || !call) return; + // Sanitize single item into items array + if (!(items.length && typeof items !== "string")) items = [ items ]; + + var description = []; + description.push(''); + + for (var i = 0; i < items.length; i++) { + var item = call(items[i], i, items, context); + if (typeof item === "string") { + description.push(item); + } else if (item.length) { + description = description.concat(item); + } + } + description.push('
    '); + description = description.join('\n'); + + return description; + }, + xhrCache: function(operation) { + var thisJSON = { + requestType: 'XHRCache', + operation: operation + }; + if (BrowserDetect.isChrome()) { + chrome.extension.sendMessage(thisJSON); + } else if (BrowserDetect.isSafari()) { + safari.self.tab.dispatchMessage('XHRCache', thisJSON); + } else if (BrowserDetect.isOpera()) { + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else if (BrowserDetect.isFirefox()) { + self.postMessage(thisJSON); + } + }, + initObservers: function() { + var siteTable, observer; + if (RESUtils.pageType() !== 'comments') { + // initialize sitetable observer... + siteTable = document.querySelector('#siteTable'); + var stMultiCheck = document.querySelectorAll('#siteTable'); + if (stMultiCheck.length === 2) { + siteTable = stMultiCheck[1]; + } + + if (BrowserDetect.MutationObserver && siteTable) { + observer = new BrowserDetect.MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes[0].id.indexOf('siteTable') !== -1) { + // when a new sitetable is loaded, we need to add new observers for selftexts within that sitetable... + $(mutation.addedNodes[0]).find('.entry div.expando').each(function() { + RESUtils.addSelfTextObserver(this); + }); + RESUtils.watchers.siteTable.forEach(function(callback) { + if (callback) callback(mutation.addedNodes[0]); + }); + } + }); + }); + + observer.observe(siteTable, { + attributes: false, + childList: true, + characterData: false + }); + } else { + // Opera doesn't support MutationObserver - so we need this for Opera support. + if (siteTable) { + siteTable.addEventListener('DOMNodeInserted', function(event) { + if ((event.target.tagName === 'DIV') && (event.target.getAttribute('id') && event.target.getAttribute('id').indexOf('siteTable') !== -1)) { + RESUtils.watchers.siteTable.forEach(function(callback) { + if (callback) callback(event.target); + }); + } + }, true); + } + } + } else { + // initialize sitetable observer... + siteTable = document.querySelector('.commentarea > .sitetable'); + + if (BrowserDetect.MutationObserver && siteTable) { + observer = new BrowserDetect.MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes.length > 0 && mutation.addedNodes[0].classList.contains('thing')) { + var thing = mutation.addedNodes[0]; + var newCommentEntry = thing.querySelector('.entry'); + if (!$(newCommentEntry).data('alreadyDetected')) { + $(newCommentEntry).data('alreadyDetected', true); + $(thing).find('.child').each(function() { + RESUtils.addNewCommentFormObserver(this); + }); + RESUtils.watchers.newComments.forEach(function(callback) { + if (callback) callback(newCommentEntry); + }); + } + } + }); + }); + + observer.observe(siteTable, { + attributes: false, + childList: true, + characterData: false + }); + } else { + // Opera doesn't support MutationObserver - so we need this for Opera support. + if (siteTable) { + siteTable.addEventListener('DOMNodeInserted', RESUtils.mutationEventCommentHandler, false); + } + } + } + + $('.entry div.expando').each(function() { + RESUtils.addSelfTextObserver(this); + }); + + // initialize new comments observers on demand, by first wiring up click listeners to "load more comments" buttons. + // on click, we'll add a mutation observer... + $('.morecomments a').click(RESUtils.addNewCommentObserverToTarget); + + // initialize new comments forms observers on demand, by first wiring up click listeners to reply buttons. + // on click, we'll add a mutation observer... + // $('body').delegate('ul.flat-list li a[onclick*=reply]', 'click', RESUtils.addNewCommentFormObserver); + $('.thing .child').each(function() { + RESUtils.addNewCommentFormObserver(this); + }); + + }, + // Opera doesn't support MutationObserver - so we need this for Opera support. + mutationEventCommentHandler: function (event) { + if ((event.target.tagName === 'DIV') && (event.target.classList.contains('thing'))) { + // we've found a matching element - stop propagation. + event.stopPropagation(); + // because nested DOMNodeInserted events are an absolute CLUSTER to manage, + // only send individual comments through to the callback. + // Otherwise, we end up calling functions on a parent, then its child (which + // already got scanned when we passed in the parent), etc. + var thisComment = event.target.querySelector('.entry'); + if (! $(thisComment).data('alreadyDetected')) { + $(thisComment).data('alreadyDetected', true); + // wire up listeners for new "more comments" links... + $(event.target).find('.morecomments a').click(RESUtils.addNewCommentObserverToTarget); + RESUtils.watchers.newComments.forEach(function(callback) { + RESUtils.addNewCommentFormObserver(event.target); + if (callback) callback(thisComment); + }); + } + } + }, + addNewCommentObserverToTarget: function (e) { + var ele = $(e.currentTarget).closest('.sitetable')[0]; + // mark this as having an observer so we don't add multiples... + if (! $(ele).hasClass('hasObserver')) { + $(ele).addClass('hasObserver'); + RESUtils.addNewCommentObserver(ele); + } + }, + addNewCommentObserver: function(ele) { + var mutationNodeToObserve = ele; + if (BrowserDetect.MutationObserver) { + var observer = new BrowserDetect.MutationObserver(function(mutations) { + // we need to get ONLY the nodes that are new... + // get the nodeList from each mutation, find comments within it, + // then call our callback on it. + for (var i=0, len=mutations.length; i div.sitetable > .thing:first-child'); // assumes new comment will be prepended to sitetable's children + if ((newOwnComment) && (newOwnComment.length === 1)) { + // new comment detected from the current user... + RESUtils.watchers.newComments.forEach(function(callback) { + callback(newOwnComment[0]); + }); + } + } + }); + + observer.observe(commentsFormParent, { + attributes: false, + childList: true, + characterData: false + }); + } else { + // Opera doesn't support MutationObserver - so we need this for Opera support. + commentsFormParent.addEventListener('DOMNodeInserted', function(event) { + // TODO: proper tag filtering here, it's currently all wrong. + if (event.target.tagName === 'FORM') { + RESUtils.watchers.newCommentsForms.forEach(function(callback) { + if (callback) callback(event.target); + }); + } else { + var newOwnComment = $(event.target).find(' > div.sitetable > .thing:first-child'); // assumes new comment will be prepended to sitetable's children + if ((newOwnComment) && (newOwnComment.length === 1)) { + // new comment detected from the current user... + RESUtils.watchers.newComments.forEach(function(callback) { + callback(newOwnComment[0]); + }); + } + } + }, true); + } + }, + addSelfTextObserver: function(ele) { + var selfTextParent = ele; + if (BrowserDetect.MutationObserver) { + // var mutationNodeToObserve = moreCommentsParent.parentNode.parentNode.parentNode.parentNode; + var observer = new BrowserDetect.MutationObserver(function(mutations) { + var form = $(mutations[0].target).find('form'); + if ((form) && (form.length > 0)) { + RESUtils.watchers.selfText.forEach(function(callback) { + callback(form[0]); + }); + } + }); + + observer.observe(selfTextParent, { + attributes: false, + childList: true, + characterData: false + }); + } else { + // Opera doesn't support MutationObserver - so we need this for Opera support. + selfTextParent.addEventListener('DOMNodeInserted', function(event) { + // TODO: proper tag filtering here, it's currently all wrong. + if (event.target.tagName === 'FORM') { + RESUtils.watchers.selfText.forEach(function(callback) { + if (callback) callback(event.target); + }); + } + }, true); + } + }, + watchForElement: function(type, callback) { + switch(type) { + case 'siteTable': + RESUtils.watchers.siteTable.push(callback); + break; + case 'newComments': + RESUtils.watchers.newComments.push(callback); + break; + case 'selfText': + RESUtils.watchers.selfText.push(callback); + break; + case 'newCommentsForms': + RESUtils.watchers.newCommentsForms.push(callback); + break; + } + }, + watchers: { + siteTable: [], + newComments: [], + selfText: [], + newCommentsForms: [] + }, + // A link is a comment code if all these conditions are true: + // * It has no content (i.e. content.length === 0) + // * Its href is of the form "/code" + // + // In case it's not clear, here is a list of some common comment + // codes on a specific subreddit: + // http://www.reddit.com/r/metarage/comments/p3eqe/full_updated_list_of_comment_faces_wcodes/ + COMMENT_CODE_REGEX: /^\/\w+$/, + isCommentCode: function (link) { + var content = link.innerHTML; + + // Note that link.href will return the full href (which includes the + // reddit.com domain). We don't want that. + var href = link.getAttribute("href"); + + return !content && this.COMMENT_CODE_REGEX.test(href); + }, + /* + Starts a unique named timeout. + If there is a running timeout with the same name cancel the old one in favor of the new. + Call with no time/call parameter (null/undefined/missing) to and existing one with the given name. + Used to derfer an action until a series of events has stopped. + e.g. wait until a user a stopped typing to update a comment preview. + (name based on similar function in underscore.js) + */ + debounceTimeouts: {}, + debounce: function(name, time, call, data) { + if (name == null) return; + if (RESUtils.debounceTimeouts[name] !== undefined) { + window.clearTimeout(RESUtils.debounceTimeouts[name]); + delete RESUtils.debounceTimeouts[name]; + } + if (time !== null && call !== null) { + RESUtils.debounceTimeouts[name] = window.setTimeout(function() { + delete RESUtils.debounceTimeouts[name]; + call(data); + }, time); + } + }, + toolTipTimers: {}, + /* + Iterate through an array in chunks, executing a callback on each element. + Each chunk is handled asynchronously from the others with a delay betwen each batch. + If the provided callback returns false iteration will be halted. + */ + forEachChunked: function(array, chunkSize, delay, call) { + if (typeof array === 'undefined' || array === null) return; + if (typeof chunkSize === 'undefined' || chunkSize === null || chunkSize < 1) return; + if (typeof delay === 'undefined' || delay === null || delay < 0) return; + if (typeof call === 'undefined' || call === null) return; + var counter = 0; + var length = array.length; + function doChunk() { + for (var end = Math.min(array.length, counter+chunkSize); counter < end; counter++) { + var ret = call(array[counter], counter, array); + if (ret === false) return; + } + if (counter < array.length) { + window.setTimeout(doChunk, delay); + } + } + window.setTimeout(doChunk, delay); + }, + getComputedStyle: function(elem, property){ + if (elem.constructor === String) { + elem = document.querySelector(elem); + } else if (!(elem instanceof Node)) { + return undefined; + } + var strValue; + if(document.defaultView && document.defaultView.getComputedStyle) { + strValue = document.defaultView.getComputedStyle(elem, "").getPropertyValue(property); + } else if(elem.currentStyle){ + property = property.replace(/\-(\w)/g, function(strMatch, p1){ + return p1.toUpperCase(); + }); + strValue = oElm.currentStyle[property]; + } + return strValue; + }, + hover: { + defaults: { + openDelay: 500, + fadeDelay: 500, + fadeSpeed: 0.3, + width: 512, + closeOnMouseOut: true + }, + container: null, + /* + The contents of state are as follows: + state: { + //The DOM element that triggered the hover popup. + element: null, + //Resolved values for timing, etc. + options: null, + //Usecase specific object + context: null, + callback: null, + }*/ + state: null, + showTimer: null, + hideTimer: null, + begin: function(onElement, conf, callback, context) { + var hover = RESUtils.hover; + if (hover.container === null) hover.create(); + if (hover.state !== null) { + hover.close(false); + } + var state = hover.state = { + element: onElement, + options: $.extend({}, hover.defaults, conf), + context: context, + callback: callback, + }; + hover.showTimer = setTimeout(function() { + hover.cancelShowTimer(); + hover.clearShowListeners(); + hover.open(); + + hover.state.element.addEventListener('mouseout', hover.startHideTimer, false); + }, state.options.openDelay); + + state.element.addEventListener('click', hover.cancelShow, false); + state.element.addEventListener('mouseout', hover.cancelShow, false); + }, + + create: function() { + var container = $('
    \ +

    \ +
    x
    \ +
    \ +
    ').get(0); + + document.body.appendChild(container); + + $(container).hover(function() { + if (RESUtils.hover.state !== null) { + RESUtils.hover.cancelHideTimer(); + } + }, function() { + if (RESUtils.hover.state !== null) { + RESUtils.hover.cancelHideTimer(); + if (RESUtils.hover.state.options.closeOnMouseOut === true) { + RESUtils.hover.startHideTimer(); + } + } + }); + + $(container).on('click', '.RESCloseButton', function() { + RESUtils.hover.close(true); + }); + RESUtils.hover.container = container; + + var css = ''; + + css += '#RESHoverContainer { display: none; position: absolute; z-index: 10001; }'; + css += '#RESHoverContainer:before { content: ""; position: absolute; top: 10px; left: -26px; border-style: solid; border-width: 10px 29px 10px 0; border-color: transparent #c7c7c7; display: block; width: 0; z-index: 1; }'; + css += '#RESHoverContainer:after { content: ""; position: absolute; top: 10px; left: -24px; border-style: solid; border-width: 10px 29px 10px 0; border-color: transparent #f0f3fc; display: block; width: 0; z-index: 1; }'; + css += '#RESHoverContainer.right:before { content: ""; position: absolute; top: 10px; right: -26px; left: auto; border-style: solid; border-width: 10px 0 10px 29px; border-color: transparent #c7c7c7; display: block; width: 0; z-index: 1; }'; + css += '#RESHoverContainer.right:after { content: ""; position: absolute; top: 10px; right: -24px; left: auto; border-style: solid; border-width: 10px 0 10px 29px; border-color: transparent #f0f3fc; display: block; width: 0; z-index: 1; }'; + + RESUtils.addCSS(css); + }, + open: function() { + var hover = RESUtils.hover; + var def = $.Deferred(); + def.promise() + .progress(hover.set) + .done(hover.set) + .fail(hover.close); + hover.state.callback(def, hover.state.element, hover.state.context); + }, + set: function(header, body) { + var hover = RESUtils.hover; + var container = hover.container; + if (header != null) $('#RESHoverTitle').empty().append(header); + if (body != null) $('#RESHoverBody').empty().append(body); + + var XY=RESUtils.getXYpos(hover.state.element); + + var width = $(hover.state.element).width(); + var tooltipWidth = $(container).width(); + tooltipWidth = hover.state.options.width; + + RESUtils.fadeElementIn(hover.container, hover.state.options.fadeSpeed); + if((window.innerWidth-XY.x-width)<=tooltipWidth){ + // tooltip would go off right edge - reverse it. + container.classList.add('right'); + $(container).css({ + top: XY.y - 14, + left: XY.x - tooltipWidth - 30, + width: tooltipWidth + }); + } else { + container.classList.remove('right'); + $(container).css({ + top: XY.y - 14, + left: XY.x + width + 25, + width: tooltipWidth + }); + } + }, + cancelShow: function(e) { + RESUtils.hover.close(true); + }, + clearShowListeners: function() { + if (RESUtils.hover.state === null) return; + var element = RESUtils.hover.state.element; + var func = RESUtils.hover.cancelShow; + + element.removeEventListener('click', func, false); + element.removeEventListener('mouseout', func, false); + }, + cancelShowTimer: function() { + if (RESUtils.hover.showTimer === null) return; + clearTimeout(RESUtils.hover.showTimer); + RESUtils.hover.showTimer = null; + }, + startHideTimer: function() { + if (RESUtils.hover.state !== null) { + RESUtils.hover.hideTimer = setTimeout(function() { + RESUtils.hover.cancelHideTimer(); + RESUtils.hover.close(true); + }, RESUtils.hover.state.options.fadeDelay); + } + }, + cancelHideTimer: function() { + var hover = RESUtils.hover; + if (RESUtils.hover.state !== null) { + hover.state.element.removeEventListener('mouseout', hover.startHideTimer, false); + } + if (hover.hideTimer === null) return; + clearTimeout(hover.hideTimer); + hover.hideTimer = null; + }, + close: function(fade) { + var hover = RESUtils.hover; + function afterHide() { + $('#RESHoverTitle, #RESHoverBody').empty(); + hover.clearShowListeners(); + hover.cancelShowTimer(); + hover.cancelHideTimer(); + hover.state = null; + } + if (fade && hover.state !== null) { + RESUtils.fadeElementOut(hover.container, hover.state.options.fadeSpeed, afterHide); + } else { + $(hover.container).hide(afterHide); + } + } + } +}; +// end RESUtils; + +// Create a nice alert function... +var gdAlert = { + container: false, + overlay: "", + + init: function(callback) { + //init + var alertCSS = '#alert_message { ' + + 'display: none;' + + 'opacity: 0.0;' + + 'background-color: #EFEFEF;' + + 'border: 1px solid black;' + + 'color: black;' + + 'font-size: 10px;' + + 'padding: 20px;' + + 'padding-left: 60px;' + + 'padding-right: 60px;' + + 'position: fixed!important;' + + 'position: absolute;' + + 'width: 400px;' + + 'float: left;' + + 'z-index: 1000000201;' + + 'text-align: left;' + + 'left: auto;' + + 'top: auto;' + + '}' + + '#alert_message .button {' + + 'border: 1px solid black;' + + 'font-weight: bold;' + + 'font-size: 10px;' + + 'padding: 4px;' + + 'padding-left: 7px;' + + 'padding-right: 7px;' + + 'float: left;' + + 'background-color: #DFDFDF;' + + 'cursor: pointer;' + + '}' + + '#alert_message span {' + + 'display: block;' + + 'margin-bottom: 15px; ' + + '}'+ + '#alert_message_background {' + + 'position: fixed; top: 0; left: 0; bottom: 0; right: 0;' + + 'background-color: #333333; z-index: 100000200;' + + '}'; + + GM_addStyle(alertCSS); + + gdAlert.populateContainer(callback); + + }, + + populateContainer: function(callback) { + gdAlert.container = createElementWithID('div','alert_message'); + gdAlert.container.appendChild(document.createElement('span')); + if (typeof callback === 'function') { + this.okButton = document.createElement('input'); + this.okButton.setAttribute('type','button'); + this.okButton.setAttribute('value','confirm'); + this.okButton.addEventListener('click',callback, false); + this.okButton.addEventListener('click',gdAlert.close, false); + var closeButton = document.createElement('input'); + closeButton.setAttribute('type','button'); + closeButton.setAttribute('value','cancel'); + closeButton.addEventListener('click',gdAlert.close, false); + gdAlert.container.appendChild(this.okButton); + gdAlert.container.appendChild(closeButton); + } else { + /* if (this.okButton) { + gdAlert.container.removeChild(this.okButton); + delete this.okButton; + } */ + var closeButton = document.createElement('input'); + closeButton.setAttribute('type','button'); + closeButton.setAttribute('value','ok'); + closeButton.addEventListener('click',gdAlert.close, false); + gdAlert.container.appendChild(closeButton); + } + var br = document.createElement('br'); + br.setAttribute('style','clear: both'); + gdAlert.container.appendChild(br); + document.body.appendChild(gdAlert.container); + }, + open: function(text, callback) { + if (gdAlert.isOpen) { + return; + } + gdAlert.isOpen = true; + gdAlert.populateContainer(callback); + + //set message + // gdAlert.container.getElementsByTagName("SPAN")[0].innerHTML = text; + $(gdAlert.container.getElementsByTagName("SPAN")[0]).html(text); + gdAlert.container.getElementsByTagName("INPUT")[0].focus(); + gdAlert.container.getElementsByTagName("INPUT")[0].focus(); + + //create site overlay + gdAlert.overlay = createElementWithID("div", "alert_message_background"); + document.body.appendChild(gdAlert.overlay); + + // center messagebox (requires prototype functions we don't have, so we'll redefine...) + // var arrayPageScroll = document.viewport.getScrollOffsets(); + // var winH = arrayPageScroll[1] + (document.viewport.getHeight()); + // var lightboxLeft = arrayPageScroll[0]; + var arrayPageScroll = [ document.documentElement.scrollLeft , document.documentElement.scrollTop ]; + var winH = arrayPageScroll[1] + (window.innerHeight); + var lightboxLeft = arrayPageScroll[0]; + + gdAlert.container.style.top = ((winH / 2) - 90) + "px"; + gdAlert.container.style.left = ((gdAlert.getPageSize()[0] / 2) - 155) + "px"; + + /* + new Effect.Appear(gdAlert.container, {duration: 0.2}); + new Effect.Opacity(gdAlert.overlay, {duration: 0.2, to: 0.8}); + */ + RESUtils.fadeElementIn(gdAlert.container, 0.3); + RESUtils.fadeElementIn(gdAlert.overlay, 0.3); + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'gdAlert'); + }, + + close: function() { + gdAlert.isOpen = false; + /* + new Effect.Fade(gdAlert.container, {duration: 0.3}); + new Effect.Fade(gdAlert.overlay, {duration: 0.3, afterFinish: function() { + document.body.removeChild(gdAlert.overlay); + }}); + */ + RESUtils.fadeElementOut(gdAlert.container, 0.3); + RESUtils.fadeElementOut(gdAlert.overlay, 0.3); + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'gdAlert'); + }, + + getPageSize: function() { + var xScroll, yScroll; + if (window.innerHeight && window.scrollMaxY) { + xScroll = window.innerWidth + window.scrollMaxX; + yScroll = window.innerHeight + window.scrollMaxY; + } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac + xScroll = document.body.scrollWidth; + yScroll = document.body.scrollHeight; + } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari + xScroll = document.body.offsetWidth; + yScroll = document.body.offsetHeight; + } + + var windowWidth, windowHeight; + + if (self.innerHeight) { // all except Explorer + if(document.documentElement.clientWidth){ + windowWidth = document.documentElement.clientWidth; + } else { + windowWidth = self.innerWidth; + } + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowWidth = document.documentElement.clientWidth; + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowWidth = document.body.clientWidth; + windowHeight = document.body.clientHeight; + } + + // for small pages with total height less then height of the viewport + if(yScroll < windowHeight){ + pageHeight = windowHeight; + } else { + pageHeight = yScroll; + } + + // for small pages with total width less then width of the viewport + if(xScroll < windowWidth){ + pageWidth = xScroll; + } else { + pageWidth = windowWidth; + } + return [pageWidth,pageHeight]; + } +}; + +//overwrite the alert function +var alert = function(text, callback) { + if (gdAlert.container === false) { + gdAlert.init(callback); + } + gdAlert.open(text, callback); +}; + +// this function copies localStorage (from the GM import script) to FF addon simplestorage... +function GMSVtoFFSS() { + var console = unsafeWindow.console; + for (var key in localStorage) { + RESStorage.setItem(key, localStorage[key]); + } + localStorage.setItem('copyComplete','true'); + localStorage.removeItem('RES.lsTest'); + RESUtils.notification('Data transfer complete. You may now uninstall the Greasemonkey script'); +} + +// jquery plugin CSS +RESUtils.addCSS(tokenizeCSS); +RESUtils.addCSS(guidersCSS); + +// define the RESConsole class +var RESConsole = { + modalOverlay: '', + RESConsoleContainer: '', + RESMenuItems: [], + RESConfigPanelOptions: null, + // make the modules panel accessible to this class for updating (i.e. when preferences change, so we can redraw it) + RESConsoleConfigPanel: createElementWithID('div', 'RESConsoleConfigPanel', 'RESPanel'), + RESConsoleAboutPanel: createElementWithID('div', 'RESConsoleAboutPanel', 'RESPanel'), + RESConsoleProPanel: createElementWithID('div', 'RESConsoleProPanel', 'RESPanel'), + addConsoleLink: function() { + this.userMenu = document.querySelector('#header-bottom-right'); + if (this.userMenu) { + var RESPrefsLink = $("") + .mouseenter(RESConsole.showPrefsDropdown); + $(this.userMenu).find("ul").after(RESPrefsLink).after("|"); + this.RESPrefsLink = RESPrefsLink[0]; + } + }, + addConsoleDropdown: function() { + this.gearOverlay = createElementWithID('div','RESMainGearOverlay'); + this.gearOverlay.setAttribute('class','RESGearOverlay'); + $(this.gearOverlay).html('
    '); + + this.prefsDropdown = createElementWithID('div','RESPrefsDropdown','RESDropdownList'); + $(this.prefsDropdown).html('
    • settings console
    • donate to RES
    '); + var thisSettingsButton = this.prefsDropdown.querySelector('#SettingsConsole'); + this.settingsButton = thisSettingsButton; + thisSettingsButton.addEventListener('click', function() { + RESConsole.hidePrefsDropdown(); + RESConsole.open(); + }, true); + var thisDonateButton = this.prefsDropdown.querySelector('#RES-donate'); + thisDonateButton.addEventListener('click', function() { + RESUtils.openLinkInNewTab('http://redditenhancementsuite.com/contribute.html', true); + }, true); + $(this.prefsDropdown).mouseleave(function() { + RESConsole.hidePrefsDropdown(); + }); + $(this.prefsDropdown).mouseenter(function() { + clearTimeout(RESConsole.prefsTimer); + }); + $(this.gearOverlay).mouseleave(function() { + RESConsole.prefsTimer = setTimeout(function() { + RESConsole.hidePrefsDropdown(); + }, 1000); + }); + document.body.appendChild(this.gearOverlay); + document.body.appendChild(this.prefsDropdown); + if (RESStorage.getItem('RES.newAnnouncement','true')) { + RESUtils.setNewNotification(); + } + }, + showPrefsDropdown: function(e) { + var thisTop = parseInt($(RESConsole.userMenu).offset().top + 1, 10); + // var thisRight = parseInt($(window).width() - $(RESConsole.RESPrefsLink).offset().left); + // thisRight = 175-thisRight; + var thisLeft = parseInt($(RESConsole.RESPrefsLink).offset().left - 6, 10); + // $('#RESMainGearOverlay').css('left',thisRight+'px'); + $('#RESMainGearOverlay').css('height',$('#header-bottom-right').outerHeight()+'px'); + $('#RESMainGearOverlay').css('left',thisLeft+'px'); + $('#RESMainGearOverlay').css('top',thisTop+'px'); + RESConsole.prefsDropdown.style.top = parseInt(thisTop+$(RESConsole.userMenu).outerHeight(), 10)+'px'; + RESConsole.prefsDropdown.style.right = '0px'; + RESConsole.prefsDropdown.style.display = 'block'; + $('#RESMainGearOverlay').show(); + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'prefsDropdown'); + }, + hidePrefsDropdown: function(e) { + RESConsole.RESPrefsLink.classList.remove('open'); + $('#RESMainGearOverlay').hide(); + RESConsole.prefsDropdown.style.display = 'none'; + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'prefsDropdown'); + }, + resetModulePrefs: function() { + prefs = { + 'userTagger': true, + 'betteReddit': true, + 'singleClick': true, + 'subRedditTagger': true, + 'uppersAndDowners': true, + 'keyboardNav': true, + 'commentPreview': true, + 'showImages': true, + 'showKarma': true, + 'usernameHider': false, + 'accountSwitcher': true, + 'styleTweaks': true, + 'filteReddit': true, + 'spamButton': false, + 'bitcointip': false, + 'RESPro': false + }; + this.setModulePrefs(prefs); + return prefs; + }, + getAllModulePrefs: function(force) { + var storedPrefs; + // if we've done this before, just return the cached version + if ((!force) && (typeof this.getAllModulePrefsCached !== 'undefined')) return this.getAllModulePrefsCached; + // get the stored preferences out first. + if (RESStorage.getItem('RES.modulePrefs') !== null) { + storedPrefs = safeJSON.parse(RESStorage.getItem('RES.modulePrefs'), 'RES.modulePrefs'); + } else if (RESStorage.getItem('modulePrefs') !== null) { + // Clean up old moduleprefs. + storedPrefs = safeJSON.parse(RESStorage.getItem('modulePrefs'), 'modulePrefs'); + RESStorage.removeItem('modulePrefs'); + this.setModulePrefs(storedPrefs); + } else { + // looks like this is the first time RES has been run - set prefs to defaults... + storedPrefs = this.resetModulePrefs(); + } + if (storedPrefs === null) { + storedPrefs = {}; + } + // create a new JSON object that we'll use to return all preferences. This is just in case we add a module, and there's no pref stored for it. + var prefs = {}; + // for any stored prefs, drop them in our prefs JSON object. + for (var module in modules) { + if (storedPrefs[module]) { + prefs[module] = storedPrefs[module]; + } else if ((! modules[module].disabledByDefault) && ((storedPrefs[module] == null) || (module === 'dashboard'))) { + // looks like a new module, or no preferences. We'll default it to on. + // we also default dashboard to on. It's not really supposed to be disabled. + prefs[module] = true; + } else { + prefs[module] = false; + } + } + if ((typeof prefs !== 'undefined') && (prefs !== 'undefined') && (prefs)) { + this.getAllModulePrefsCached = prefs; + return prefs; + } + }, + getModulePrefs: function(moduleID) { + if (moduleID) { + var prefs = this.getAllModulePrefs(); + return prefs[moduleID]; + } else { + alert('no module name specified for getModulePrefs'); + } + }, + setModulePrefs: function(prefs) { + if (prefs !== null) { + RESStorage.setItem('RES.modulePrefs', JSON.stringify(prefs)); + return prefs; + } else { + alert('error - no prefs specified'); + } + }, + create: function() { + // create the console container + this.RESConsoleContainer = createElementWithID('div', 'RESConsole'); + // hide it by default... + // this.RESConsoleContainer.style.display = 'none'; + // create a modal overlay + this.modalOverlay = createElementWithID('div', 'modalOverlay'); + this.modalOverlay.addEventListener('click', function(e) { + e.preventDefault(); + return false; + }, true); + document.body.appendChild(this.modalOverlay); + // create the header + var RESConsoleHeader = createElementWithID('div', 'RESConsoleHeader'); + // create the top bar and place it in the header + var RESConsoleTopBar = createElementWithID('div', 'RESConsoleTopBar'); + this.logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAeCAMAAABHRo19AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACxFBMVEXw8/wAAAD+//8EBAQSEhIPDw/w8/v+/v4JCQkHBwcCAgKSk5W8vLz9SADz8/MtLS0iIiIcHBz/VAAYGBmRkZFkZGUkJCQVFhZiYmOZmp2QkpfQ09r9/f3n6vA5OTkvLy//TAAxMTEUFRTl5eVqa2zu8fnt7/fV19ydnqCen6Lt8Pj/TwDk5ORaWlrg4ug1NTUpKSrX19cgICDp6/J6enrFxcW1trpDQ0M7OzwnJyenp6f6TQAXFxj/WACFhojr6+uNjpBHR0cfHx+vr7GSkpJMTEwYGBg+Pj5cXF3CwsJISEj29vYQEBDe3t7+SwBmZmixsbH19fXo6OhQUFAgICJgYWXHyM3q7PTs7vW3uLvb3eKqq650dXbS09js7/aTlJY5OjmUlJeenp7r7vWWl5n8/Px4eHihoqWEhYfO0NTj5euDg4Pa3OGRkpTJy8/g4ODe4Obc3Nzv8vqjo6O1tbW3uLyrq6t1dXX5ya5/f3/5xqxZWVqKiopra2v4uJb99vLCw8fFxsouLS6Oj5Hs7OzY2t+jpKZ4eXv2tY8NDQ35WQny8vJkZGT2lWGQkJB8fHzi5OrLzNFAQUPm6O/3f0W7u7v3oXP4dTb2nXH62MX3pHb87+bn5+dWV1dvb3E0NDT4lWP3jFP4vJn2cS79+vaJioxNTU376d72f0H4Wwf2fT7759z9+fX1lmH4XAv2bSb40bheX2A6Ojr9+vj76t/9+vf76+H5XxVGRkZxcnPQ0te+vr52dnaztLfExMT2tZFYWFhSUlLV1dVwcXL52MS4uLiysrKam5rW1tZPT1CVlZWYmJiUlJRHR0ipqq0qKiqzs7P39/fq6urj4+P89fH09PT6+vo4ODjq7PNsbW4oKCh0dHTv7++3t7fk5u2IiYtFRUU3NzdPT0/Kysru7u6NjY1tbW1gYGBfX19sbGyHh4fh4eEzPXfuAAACPElEQVR4Xq3SQ9fkQBTH4bpVSdru17Zt28bYtm3btm3btm37S8yk0oteTKc7c+a3uf/Nc3JyEvT/48KF69Uhu7dk3AfaZ48PRiHgUwLdpGLdtFbecrkPOxvjuSRcmp2vaIsQt6gdLME4UtlGGs6NFW7+GIw7Qidp2BAq3KaQWg650mwC9LSs6JpRfZG03PTo32reMrmzIW3IlGaSZY/W+aCcoY/xq1SCKXAC5xAaGObkFoSmZoK3uaxqlgzL6vol3UohjIpDLWq6J4jaaNZUnsb4syMCsHU5o10q4015sZAshp2LuuCu4DSZFzJrrh0GURj3Ai8BNHrQ08TdyvZXDsDzYBD+W4OJK5bFh9nGIaRuKKTTxw5fOtJTUCtWjh3H31NQiCdOso2DiVlXSsXGDN+M6XRdnlmtmUNXYrGaLPhD3IFvoQfQrH4KkMdRsjgiK2IZXcurs4zHVvFrdSasQTaeTFu7DtPWa4yaDXSd0xh9N22mMyUVieItWwW8bfuOnbvo2r1n7779mOZ6QByHHsRChw4fsXwsz6OPsdDxE0i0kyQA20rLFIhjzuW0TVxIgpB4Z+AsBRXn1RZTdeEivXFyFbLXJTaJvmkDNJgLrly95iR3juTt9eIbyH6ucJPq2hJGQQiru63lbbriDocc6C7cu1/BgwcPH9U/4cdT9TNQIcd6/oK8fFWbg4Vev0n0I6VvkcO9A38Fq495X5T3wZkhLvAROZ6KYT59Lvvy9VvU9x8/1fW/DEygHfEbNdeCkgdk4HMAAAAASUVORK5CYII='; + // this string is split because a specific sequence of characters screws up some git clients into thinking this file is binary. + this.loader = 'data:image/gif;base64,R0lGODlhHQAWANUAAESatESetEyetEyitEyivFSivFSmvFymvFyqvGSqvGSqxGSuxGyuxGyyxHSyxHS2xHS2zHy2zHy6zIS6zIS+zIy+zIzCzIzC1JTG1JzK1JzK3JzO3KTO3KTS3KzS3KzW3LTW3LTW5LTa5Lza5Lze5MTe5MTi5MTi7Mzi7Mzm7NTm7NTq7Nzq7Nzq9Nzu9OTu9OTy9Ozy9Oz29Oz2/PT2/PT6/Pz6/Pz+/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH'; + this.loader += '/C05FVFNDQVBFMi4wAwEAAAAh/h1CdWlsdCB3aXRoIEdJRiBNb3ZpZSBHZWFyIDQuMAAh+QQIBgAAACwAAAAAHQAWAAAG/sCbcEgs3myyEIzjQr2MUGjrgpFMrJIMhxTtei4SbPhKwXCeXaLren00GIuHlSLxzNJDD4NOWST8CwsUgxEjeEIcDYN0ICkjFA4UFYMcRXckIS8XKysTCJKSGCMkHBUXpwwXRC8UGheLpgsMDBKmF6YWF7kODYY3LmawoKcXCxIKFMSnkBIELDczIxODk2SmpoMFbg8XDg4SAAoTNTUY1BcTDQsKCw2nGGAMBAUJDQcCDZ8yNzESya8NFDCAEFAChoO6GGSowEDDggsq0HhIZisVixkwQFDBkIHCARQ1XICosSIGEYe5MFjAsE8IigwcYWa402VEyoNmRozgkEFDbs8MBRS0jJJCwAOcMn1u4MBTA4UHNdLIgIAOg08NGphqZWAggohDHBIEqMCRqZYMEjZMMPBgaJcYcDAcQMBhwgMOGOg9AOHrUIkQ8hJQQKDgQaQFEQ4ZuRABxSwREtqWcKHYiIwaWm6UGBG18o0gACH5BAgGAAAALAAAAAAdABYAAAb'; + this.loader += '+wJtwSCwKXabWBjaS2YxQowqDkUysEg4GFe1+LtgrVkKddYsvCRbSYCwcEgpl4jGfhR3GnLJILP4JchQQJXdCHhCCEiApIxUNFZESGkUzNCsaMBwjMRQFE3IVGCMkHBYXFBcQGEM1NhRUexWqCRAQsxcWuBcXEQgkQjEXGYIUFanIDxENEry5F48SByo3MCWCx1fGzlcHCxKQEggUAgYWrqjGcg0LCguQuVUNBwUJbgIKDBFmMKi4DfnYKCBDhUqDCRgWYFDmAoYQDs2cMcCwYkaMEBYKUjiAAsaMDzFgxCDiocEpDBcwjBSSIkMGDRkwWHDYJUSqghg2jBjB4eVzSwwKINA4Y0JAhIIuYcLkoKFnAwc1zsyYYCFC0pccsmZNcNCDoQ4FCmAQ1TPr2A4JClCIeufFggcUAkDg8ECCBwkF4F4YYYhlCAQFHEwwwECCAwcINDzpK2QGBQ4gFEwAsSDDDA4vGBOxUaMfFw5cNN8IAgAh+QQIBgAAACwAAAAAHQAWAAAG/sCbcEgsClcqlAc2qtWMUCOKc5FYrZyK6xmFhizWiURMxmBm3SIMMp48GoyFQ0Kpc9BpIcchpiz+'; + this.loader += 'gHUUESd5Qh4QghIhKCMUDhQVFBIYRTMvMxgtIxw1GAJ0khkiJRwUF6gRGUNOGRUYghQYEQgSEBcWFBa7uGAEIUI1p7GSFRUXg3MRqKgWFwoRCSs3LiPIkhRkyKgSDggFj3UHEwcEFk8ZoXUNCn8OqBjIDQj0Cg0CCA8PMTctsMcX4jBwwI6SGQsZAnJYcKrBCn43ODxgFvBCixkwvpjJQIGBChU3RqioAVFIiAjOMFjAIGNICgwZNGTA4ABGmhATzZjhMIJTacyYNClwiVLCgKyNP2VyWIqhgIOhUGQkwyBT6VIOGRSA4WCIg4AGHDNgZYrBawEMUKO0aCCBAYALGRiUZVCLwoMRhoS80IDgQIQGBuY0SJDgRMm8MCiguJAgZgIUL23mlcLyBQbJk28EAQAh+QQIBgAAACwAAAAAHQAWAAAG/sCbcEgsClWwEElFstWMUGPpM5FUJxTMBUaLRkcUq2QsplwwXS8R5hBDGoxFm0LXyNRDj4OCXSQWgAl0FBEpeEIce3QSISlgDhUUFRAXRTQqNRwlKhgzGgUQgxkjJRxmFxcTHEMzLyRmgxQaFIIQFReRqBcWFxIDH0MYsZKSu2MMhLoWtwzNKjctHsJ0FWPFqBMLCAIXDxEXBw4MARhPHhKSkXCADbdnFA4KfggNBaASMDecxBcN8g7+JGAYiArEggwOHHRogOLODQ8NdF1YgKHFjCRnBlqQ0MKEjRRN8g0JcWoghhhDUmTIoCEDBQUio3hQYMEkhg0jRnBgyTMLcEovJhbUHLiypQYNOzlIABDhiZcYLx/wbMmh6k4IGbAe0jBgQi+kGapi4FABAAIOP9WsiCDBnksHHDAceEABAgMTh4TMqIBggYQDCCREWHBgAYxneYW0wPCiwQIQEh686FAusREQHmyE4FDDhuUbQQAAIfkECAYAAAAsAAAAAB0AFgAABv7Am3BILN5sqhlHVUrVaMaosSSSUCTYygUTm0mlKKxkIiZTKJrat/hqkCcPhrxhpVQw3rXwA6FMKAoLgoJnVyl6QhwMhRIfKCQUDhV2EBdFNSc0IhwvGiocCH12GSMlHBQXqRIcQzMoKhMWhRQZFwwSERd2uhcWvRQFHkMef4UVkxcVVgtXqRYYWg4HDSs3LRgYs2apvRMGCgJjDxcKoQIYNjcjEWe6DQyBDVpbFg8JDAsGDAcCDxQuN1DwSgVvwYMGCiRgyyYBxQILExR8iBBCzY0QDXz5YoChxQwYIZ5hyAANRokYLkQ8IfJhHoZnMYagyEBTA4QDMNZwMCAS23aGESM6ZNAwlGaFPGByLaRZMwMHDRwaBKCQ7osMCQUk1NQAlYPXlxoUaECE4QCGCKuccqDpwUEABh5eIFoRKUCCqBKIJbgg4V4LREJmPFAQ4UGBRQ0QIJjgggTgISpGmFDwwAODCy0mbHhshIaHQxdG3KhRFXAQACH5BAgGAAAALAAAAAAdABYAAAb+wJtwSCzeaiwYxwVyxWrGqBEVklAkksmFspxJpalHdoydZDu0b7HlME8ejAVDTKFULlC1MAShTCgLCguDC3V+J182QxmFdRIeKSMUDnYUEBhGJy4rGDAeJRwMlHYZI6B3FxcPHUM0ISwVlXUYGA0QWhRbFhe7FhUIHkI1JVaGsbEXERILf6mpuxEDDCs3LncWdRVYuc4WBgsCDxUNFA8CEAUXNzYnVrEUDXEKDXcYFxURB3IICgoCDRhY3EDRLFUDQRAOSqCFAV4KZRgQcMDAYQiJB7xSMcCwggaMEBVoZaAlA0XHEDBqKBLSAZU9DDGGoNCAIYMGBwdiftFQwAJ1Q4ojRnDIYLOoBC9fVORiOFKDTQ0coi44oE7NjAYCKBB1CnVD1JoVDlTUcwEgAy4Zog7lcMDAQhd6qmFIAEBCBgUWODhokKHBgQY648Jg0CCCvwgUEhxIwCFoXCIqXGRIUFOBBxINSDyO4mnGCgoubMDYLCQIACH5BAgGAAAALAAAAAAdABYAAAb+wJtwSCzeaq+W59WZuWrGqFHFkVAkkolFMkrRpFIUZJLFlsmiGLi4gmApjwaD0ZhQ7hfbejhyUOwLCQuDC3d3JWB6QhoIhhEgKCMUfhUVEBlGKCcwFyonHhwOEHcVGCMkHBUXFxUNHEM1HigZFBWGpRENFKsXFr2/FA0hQjAtdoa1uxcSDwyjqr4XfwIKLDcxyYZktau+CgkGDRcPERQBDo1HJ8fSDQsKCw2qGNIQBQsMCQcMAggaLTdQlOPFQIGzBgokYFhIYQGIDA0yFAqR4csNExC6XWBwgcUMGCFKLVwYo0WJGiVW2FB0Q4OWVQtlDJmFQUOGCAlgrOFw4MJ9SAwcRozokEGDhg0cLDiYsWbFlpEZMBQtyoFDBgYOLkABM+NAAQsZpmqoWjUDhwYFPuy5sYwCgppmrVot8EBCBRdrX2AoIADDhAVhGZQ6YEDC1rUrGEwyUIBChAUIFpAwtZaIixkQHEpYUOKqC5aVh7AoYcNDhRozXoQWEgQAIfkECAYAAAAsAAAAAB0AFgAABv7Am3BILN5ostNo5ZmtbMaosZWhUCQTSUVSItWk0hIES5aQJ6UXuLgyZyONBcMhsVIw37VwBJlYFwmACwt2FCNgUEIZCFZZICkjFA4UFRQRG0YuITIaIi0eGBARdhohJRwXqRcLGUQeIRx+dn4SCxWptxYXt1sRIUIuK5V2FZWpEw0OCxYUqbpWBgYsR8NWW3W4FxYOCIMWEg4XAggMFDY1IpW3FHEKCw23GBeSAgoNDAINBQcbLTcqD5rNY6CAAQSCEjAopMAAg4cFGBw0QJFhhpATE1StwrBiRgwQdzBkwEABBo0QNFacKILhgSqFMYak0JAhg4YIEGKC8cDggnZChRxGjOBQk6aGWjLWrKDw4OdIoxqIcnBgwUIeKTEMKFBo0yaHr0Q1GCBwSA9JBwe6fs3AwcKBC+Bc6LkRg0IBBBrmcGDHoYKAtDrnomhwAd8yBggUPAjxoMRcIjFgJJAAYgEEE2NqWHzMpkWNCx5usFDD+UYQACH5BAgGAAAALAAAAAAdABYAAAb+wJtwSCzeajWRqjSKqYxQ6OuCkVgnFMlpVItGR1fJxCrJUkYvb3EliYwfjLijPN501cKQw7zo+ymAEyJqNkIaCYBZICgjFHsVFRIcRjQcMCEbMSESD1gVFBkiJRwWFxQXCxhEIRkeiaeOEgqnFRcVpbUXViBCLSUYr5+fpgsQCqYXyaYUCQQsR8CAn2MUuRcWEgcOC4ALFgcEDBI2NRymtRQNfg25GBMNAQgMDQUJCAUZaS4OFsMMfQ4aKJCAoaAFCBJGLPiEoIQHGEJInFKWqsUMTRQKZrjg4IUNES1klCiCgYGygjGGoMigIUOGahC9bLJQsOCGESM6tGSpYYFwgRlqUgSs6ZKlSw4tQU24EyXGAQgYXGpoqYGDVXMCDozEA+yAggwYrlqV0CBDgwZp8MyQUOABBgMUODiI0MGBgAQhVuAZUqKaAgEQKCBI0CAjA717h9QogaBqggshEnCwkTYxkRU0VkxQYcNETMtBAAAh+QQIBgAAACwAAAAAHQAWAAAG/sCbcEgs3mo0kAuEaq2MUOiLgpFYKZLLaBTthrATSViMrYRe3WILLHk0GAuHhILt1NLDDyNMWSgWCQsLFBNYXHg3HIN0EiApIxQOFBWEHEU1Nh4oKRgvJREMk5MYIyUclBcXCxdEKBcedIUXFAwPCpOpFhSpqQ8Qhy0dHHR0lKgXChIIu7kYWA4DLUcchaJ8vLoUBhELEhYMEg0A4DY1GbMVsw2CCg3pGFUMAgftBgcLBxcyNzEQzBQNFDBwEFACPAwXJjTwEOEBhgQeSMAQIoKChXQXGGBYMSOGiAoHLSxQcePECRsoZhDBoCAVQgwxhqDAoCGDBngqu0A6CI/DdJYONoMaKLCvS4oDDQ5moGlzA4cNSzNEuNNFhoIKFjAE1eCUg9cIARaUQMTBgQAIN716lZr1gIOJeGY0yBehgFaNHBAMYEBiLKIbJDg8KGBgwgMECRxUgNAg5l8hNjQwgAQRw4IUMKQ9JuLiRsUaMEYUfRwEADs='; + RESConsoleTopBar.setAttribute('class','RESDialogTopBar'); + $(RESConsoleTopBar).html('

    reddit enhancement suite

    '); + RESConsoleHeader.appendChild(RESConsoleTopBar); + this.RESConsoleVersion = createElementWithID('div','RESConsoleVersion'); + $(this.RESConsoleVersion).text('v' + RESVersion); + RESConsoleTopBar.appendChild(this.RESConsoleVersion); + + // Create the search bar and place it in the top bar + var RESSearchContainer = modules['settingsNavigation'].renderSearchForm(); + RESConsoleTopBar.appendChild(RESSearchContainer); + + var RESSubredditLink = createElementWithID('a','RESConsoleSubredditLink'); + $(RESSubredditLink).text('/r/Enhancement'); + RESSubredditLink.setAttribute('href','http://reddit.com/r/Enhancement'); + RESSubredditLink.setAttribute('alt','The RES Subreddit'); + RESConsoleTopBar.appendChild(RESSubredditLink); + // create the close button and place it in the header + var RESClose = createElementWithID('span', 'RESClose', 'RESCloseButton'); + $(RESClose).text('×'); + RESClose.addEventListener('click', function(e) { + e.preventDefault(); + RESConsole.close(); + }, true); + RESConsoleTopBar.appendChild(RESClose); + this.categories = []; + for (var module in modules) { + if ((typeof modules[module].category !== 'undefined') && (this.categories.indexOf(modules[module].category) === -1)) { + this.categories.push(modules[module].category); + } + } + this.categories.sort(); + // create the menu + // var menuItems = this.categories.concat(['RES Pro','About RES')); + var menuItems = this.categories.concat(['About RES']); + var RESMenu = createElementWithID('ul', 'RESMenu'); + for (var item = 0; item < menuItems.length; item++) { + var thisMenuItem = document.createElement('li'); + $(thisMenuItem).text(menuItems[item]); + thisMenuItem.setAttribute('id', 'Menu-' + menuItems[item]); + thisMenuItem.addEventListener('click', function(e) { + e.preventDefault(); + RESConsole.menuClick(this); + }, true); + RESMenu.appendChild(thisMenuItem); + } + RESConsoleHeader.appendChild(RESMenu); + this.RESConsoleContainer.appendChild(RESConsoleHeader); + // Store the menu items in a global variable for easy access by the menu selector function. + RESConsole.RESMenuItems = RESMenu.querySelectorAll('li'); + // Create a container for each management panel + this.RESConsoleContent = createElementWithID('div', 'RESConsoleContent'); + this.RESConsoleContainer.appendChild(this.RESConsoleContent); + // Okay, the console is done. Add it to the document body. + document.body.appendChild(this.RESConsoleContainer); + + window.addEventListener("keydown", function(e) { + if ((RESConsole.captureKey) && (e.keyCode !== 16) && (e.keyCode !== 17) && (e.keyCode !== 18)) { + // capture the key, display something nice for it, and then close the popup... + e.preventDefault(); + var keyArray = [e.keyCode, e.altKey, e.ctrlKey, e.shiftKey, e.metaKey]; + document.getElementById(RESConsole.captureKeyID).value = keyArray.join(","); + document.getElementById(RESConsole.captureKeyID+'-display').value = RESUtils.niceKeyCode(keyArray); + RESConsole.keyCodeModal.style.display = 'none'; + RESConsole.captureKey = false; + } + }); + + $("#RESConsoleContent").delegate(".keycode + input[type=text][displayonly]", { + focus: function(e) { + var thisXY=RESUtils.getXYpos(this, true); + // show dialog box to grab keycode, but display something nice... + $(RESConsole.keyCodeModal).css({ + display: "block", + top: RESUtils.mouseY + "px", + left: RESUtils.mouseX + "px;" + }); + // RESConsole.keyCodeModal.style.display = 'block'; + RESConsole.captureKey = true; + RESConsole.captureKeyID = this.getAttribute('capturefor'); + }, + blur: function(e) { + $(RESConsole.keyCodeModal).css("display", "none"); + } + }); + + this.keyCodeModal = createElementWithID('div', 'keyCodeModal'); + $(this.keyCodeModal).text('Press a key (or combination with shift, alt and/or ctrl) to assign this action.'); + document.body.appendChild(this.keyCodeModal); + }, + drawConfigPanel: function(category) { + if (!category) return; + + this.drawConfigPanelCategory(category); + }, + getModuleIDsByCategory: function(category) { + var moduleList = []; + for (var i in modules) { + if (modules[i].category == category && !modules[i].hidden) moduleList.push(i); + } + moduleList.sort(function(a,b) { + if (modules[a].moduleName.toLowerCase() > modules[b].moduleName.toLowerCase()) return 1; + return -1; + }); + + return moduleList; + }, + drawConfigPanelCategory: function(category, moduleList) { + $(this.RESConsoleConfigPanel).empty(); + + /* + var moduleTest = RESStorage.getItem('moduleTest'); + if (moduleTest) { + console.log(moduleTest); + // TEST loading stored modules... + var evalTest = eval(moduleTest); + } + */ + moduleList = moduleList || this.getModuleIDsByCategory(category); + + this.RESConfigPanelModulesPane = createElementWithID('div', 'RESConfigPanelModulesPane'); + for (var i=0, len=moduleList.length; iRES Module Configuration Select a module from the column at the left to enable or disable it, and configure its various options.'); + this.RESConsoleConfigPanel.appendChild(this.RESConfigPanelOptions); + this.RESConsoleContent.appendChild(this.RESConsoleConfigPanel); + }, + updateSelectedModule: function (moduleID) { + var moduleButtons = $(RESConsole.RESConsoleConfigPanel).find('.moduleButton'); + moduleButtons.removeClass('active'); + moduleButtons.filter(function() { return this.getAttribute('moduleID') === moduleID; }) + .addClass('active'); + }, + drawOptionInput: function(moduleID, optionName, optionObject, isTable) { + var thisOptionFormEle; + switch(optionObject.type) { + case 'textarea': + // textarea... + thisOptionFormEle = createElementWithID('textarea', optionName); + thisOptionFormEle.setAttribute('type','textarea'); + thisOptionFormEle.setAttribute('moduleID',moduleID); + $(thisOptionFormEle).html(escapeHTML(optionObject.value)); + break; + case 'text': + // text... + thisOptionFormEle = createElementWithID('input', optionName); + thisOptionFormEle.setAttribute('type','text'); + thisOptionFormEle.setAttribute('moduleID',moduleID); + thisOptionFormEle.setAttribute('placeHolder',optionObject.placeHolder || ''); + thisOptionFormEle.setAttribute('value',optionObject.value); + break; + case 'button': + // button... + thisOptionFormEle = createElementWithID('button', optionName); + thisOptionFormEle.classList.add('RESConsoleButton'); + thisOptionFormEle.setAttribute('moduleID',moduleID); + thisOptionFormEle.innerText = optionObject.text; + thisOptionFormEle.addEventListener('click', optionObject.callback, false); + break; + case 'list': + // list... + thisOptionFormEle = createElementWithID('input', optionName); + thisOptionFormEle.setAttribute('class','RESInputList'); + thisOptionFormEle.setAttribute('type','text'); + thisOptionFormEle.setAttribute('moduleID',moduleID); + // thisOptionFormEle.setAttribute('value',optionObject.value); + existingOptions = optionObject.value; + if (typeof existingOptions === 'undefined') existingOptions = ''; + var prepop = []; + var optionArray = existingOptions.split(','); + for (var i=0, len=optionArray.length; i").attr({ + id: optionName, + type: "text", + class: "keycode", + moduleID: moduleID + }).css({ + border: "1px solid red", + display: "none" + }).val(optionObject.value); + if (isTable) realOptionFormEle.attr('tableOption','true'); + + var thisKeyCodeDisplay = $("").attr({ + id: optionName+"-display", + type: "text", + capturefor: optionName, + displayonly: "true" + }).val(RESUtils.niceKeyCode(optionObject.value)); + thisOptionFormEle = $("
    ").append(realOptionFormEle).append(thisKeyCodeDisplay)[0]; + break; + default: + console.log('misconfigured option in module: ' + moduleID); + break; + } + if (isTable) { + thisOptionFormEle.setAttribute('tableOption','true'); + } + return thisOptionFormEle; + }, + enableModule: function(moduleID, onOrOff) { + var prefs = this.getAllModulePrefs(true); + prefs[moduleID] = !!onOrOff; + this.setModulePrefs(prefs); + }, + showConfigOptions: function(moduleID) { + if (!modules[moduleID]) return; + RESConsole.drawConfigOptions(moduleID); + RESConsole.updateSelectedModule(moduleID); + RESConsole.currentModule = moduleID; + + RESConsole.RESConsoleContent.scrollTop = 0; + + modules['settingsNavigation'].setUrlHash(moduleID); + }, + drawConfigOptions: function(moduleID) { + if (modules[moduleID] && modules[moduleID].hidden) return; + var thisOptions = RESUtils.getOptions(moduleID); + var optCount = 0; + + this.RESConfigPanelOptions.setAttribute('style','display: block;'); + $(this.RESConfigPanelOptions).html(''); + // put in the description, and a button to enable/disable the module, first.. + var thisHeader = document.createElement('div'); + thisHeader.classList.add('moduleHeader'); + $(thisHeader).html('' + modules[moduleID].moduleName + ''); + var thisToggle = document.createElement('div'); + thisToggle.classList.add('moduleToggle'); + if (moduleID === 'dashboard') thisToggle.style.display = 'none'; + $(thisToggle).html('onoff'); + if (modules[moduleID].isEnabled()) thisToggle.classList.add('enabled'); + thisToggle.setAttribute('moduleID',moduleID); + thisToggle.addEventListener('click', function(e) { + var activePane = RESConsole.RESConfigPanelModulesPane.querySelector('.active'); + var enabled = this.classList.contains('enabled'); + if (enabled) { + activePane.classList.remove('enabled'); + this.classList.remove('enabled'); + RESConsole.moduleOptionsScrim.classList.add('visible'); + $('#moduleOptionsSave').hide(); + } else { + activePane.classList.add('enabled'); + this.classList.add('enabled'); + RESConsole.moduleOptionsScrim.classList.remove('visible'); + $('#moduleOptionsSave').fadeIn(); + } + RESConsole.enableModule(this.getAttribute('moduleID'), !enabled); + }, true); + thisHeader.appendChild(thisToggle); + // not really looping here, just only executing if there's 1 or more options... + for (var i in thisOptions) { + var thisSaveButton = createElementWithID('input','moduleOptionsSave'); + thisSaveButton.setAttribute('type','button'); + thisSaveButton.setAttribute('value','save options'); + thisSaveButton.addEventListener('click', function(e) { + RESConsole.saveCurrentModuleOptions(e); + }, true); + this.RESConsoleConfigPanel.appendChild(thisSaveButton); + var thisSaveStatus = createElementWithID('div','moduleOptionsSaveStatus','saveStatus'); + thisHeader.appendChild(thisSaveStatus); + break; + } + var thisDescription = document.createElement('div'); + thisDescription.classList.add('moduleDescription'); + $(thisDescription).html(modules[moduleID].description); + thisHeader.appendChild(thisDescription); + this.RESConfigPanelOptions.appendChild(thisHeader); + var allOptionsContainer = createElementWithID('div', 'allOptionsContainer'); + this.RESConfigPanelOptions.appendChild(allOptionsContainer); + // now draw all the options... + for (var i in thisOptions) { + if (!(thisOptions[i].noconfig)) { + optCount++; + var thisOptionContainer = createElementWithID('div', null, 'optionContainer'); + var thisLabel = document.createElement('label'); + thisLabel.setAttribute('for',i); + $(thisLabel).text(i); + var thisOptionDescription = createElementWithID('div', null, 'optionDescription'); + $(thisOptionDescription).html(thisOptions[i].description); + thisOptionContainer.appendChild(thisLabel); + if (thisOptions[i].type === 'table') { + thisOptionDescription.classList.add('table'); + // table - has a list of fields (headers of table), users can add/remove rows... + if (typeof thisOptions[i].fields === 'undefined') { + alert('misconfigured table option in module: ' + moduleID + ' - options of type "table" must have fields defined'); + } else { + // get field names... + var fieldNames = []; + // now that we know the field names, get table rows... + var thisTable = document.createElement('table'); + thisTable.setAttribute('moduleID',moduleID); + thisTable.setAttribute('optionName',i); + thisTable.setAttribute('class','optionsTable'); + var thisThead = document.createElement('thead'); + var thisTableHeader = document.createElement('tr'), thisTH; + thisTable.appendChild(thisThead); + for (var j=0;j---", + placeholderTemplate: "---", + }); + })(moduleID, i); + } else { + if ((thisOptions[i].type === 'text') || (thisOptions[i].type === 'password') || (thisOptions[i].type === 'keycode')) thisOptionDescription.classList.add('textInput'); + var thisOptionFormEle = this.drawOptionInput(moduleID, i, thisOptions[i]); + thisOptionContainer.appendChild(thisOptionFormEle); + thisOptionContainer.appendChild(thisOptionDescription); + } + var thisClear = document.createElement('div'); + thisClear.setAttribute('class','clear'); + thisOptionContainer.appendChild(thisClear); + allOptionsContainer.appendChild(thisOptionContainer); + } + } + + if (optCount === 0) { + var noOptions = createElementWithID('div','noOptions'); + noOptions.classList.add('optionContainer'); + $(noOptions).text('There are no configurable options for this module'); + this.RESConfigPanelOptions.appendChild(noOptions); + } else { + // var thisSaveStatusBottom = createElementWithID('div','moduleOptionsSaveStatusBottom','saveStatus'); + // this.RESConfigPanelOptions.appendChild(thisBottomSaveButton); + // this.RESConfigPanelOptions.appendChild(thisSaveStatusBottom); + this.moduleOptionsScrim = createElementWithID('div','moduleOptionsScrim'); + if (modules[moduleID].isEnabled()) { + RESConsole.moduleOptionsScrim.classList.remove('visible'); + $('#moduleOptionsSave').fadeIn(); + } else { + RESConsole.moduleOptionsScrim.classList.add('visible'); + $('#moduleOptionsSave').fadeOut(); + } + allOptionsContainer.appendChild(this.moduleOptionsScrim); + // console.log($(thisSaveButton).position()); + } + }, + deleteOptionRow: function(e) { + var thisRow = e.target.parentNode.parentNode; + $(thisRow).remove(); + }, + saveCurrentModuleOptions: function(e) { + e.preventDefault(); + var panelOptionsDiv = this.RESConfigPanelOptions; + // first, go through inputs that aren't a part of a "table of options"... + var inputs = panelOptionsDiv.querySelectorAll('input, textarea'); + for (var i=0, len=inputs.length;ian API that allows you to contribute and include your own modules!

    \ +

    If you\'ve got bug reports or issues with RES, please see the RESIssues subreddit. If you\'d like to follow progress on RES, or you\'d like to converse with other users, please see the Enhancement subreddit. You can also check the wiki for the FAQ, and more detailed info on each module.

    \ +

    If you want to contribute to the RES code base, submit bug reports, or make suggestions, you can also visit RES at Github.

    \ +

    If you want to contact me directly with suggestions, bug reports or just want to say you appreciate the work, an email would be great.

    \ +

    License: Reddit Enhancement Suite is released under the GPL v3.0.

    \ +

    Note: Reddit Enhancement Suite will check, at most once a day, to see if a new version is available. No data about you is sent to me nor is it stored.

    \ +
    \ +
    \ +

    About the RES Team

    \ +

    Steve Sobel (honestbleeps) is the primary developer of RES. Beyond that, there are a number of people who have contributed code, design and/or great ideas to RES. To read more about the RES team, visit the RES website.

    \ +
    \ +
    \ +
    \ + '; + $(RESConsoleAboutPanel).html(AboutPanelHTML); + var searchPanel = modules['settingsNavigation'].renderSearchPanel(); + $('#SearchRES', RESConsoleAboutPanel).append(searchPanel); + $(RESConsoleAboutPanel).find('.moduleButton').click(function(e) { + $('.moduleButton').removeClass('active'); + $(this).addClass('active'); + var thisID = $(this).attr('id'); + var thisPanel = thisID.replace('Button-',''); + var visiblePanel = $(this).parent().parent().find('.aboutPanel:visible'); + + var duration = (e.data && e.data.duration) || $(this).hasClass('active') ? 0 : 400; + $(visiblePanel).fadeOut(duration, function () { + $('#'+thisPanel).fadeIn(); + }); + }); + this.RESConsoleContent.appendChild(RESConsoleAboutPanel); + }, + drawProPanel: function() { + RESConsoleProPanel = this.RESConsoleProPanel; + var proPanelHeader = document.createElement('div'); + $(proPanelHeader).html('RES Pro allows you to save your preferences to the RES Pro server.

    Please note: this is beta functionality right now. Please don\'t consider this to be a "backup" solution just yet. To start, you will need to register for a PRO account first, then email steve@honestbleeps.com with your RES Pro username to get access.'); + RESConsoleProPanel.appendChild(proPanelHeader); + this.proSetupButton = createElementWithID('div','RESProSetup'); + this.proSetupButton.setAttribute('class','RESButton'); + $(this.proSetupButton).text('Configure RES Pro'); + this.proSetupButton.addEventListener('click', function(e) { + e.preventDefault(); + modules['RESPro'].configure(); + }, false); + RESConsoleProPanel.appendChild(this.proSetupButton); + /* + this.proAuthButton = createElementWithID('div','RESProAuth'); + this.proAuthButton.setAttribute('class','RESButton'); + $(this.proAuthButton).html('Authenticate'); + this.proAuthButton.addEventListener('click', function(e) { + e.preventDefault(); + modules['RESPro'].authenticate(); + }, false); + RESConsoleProPanel.appendChild(this.proAuthButton); + */ + this.proSaveButton = createElementWithID('div','RESProSave'); + this.proSaveButton.setAttribute('class','RESButton'); + $(this.proSaveButton).text('Save Module Options'); + this.proSaveButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].savePrefs(); + modules['RESPro'].authenticate(modules['RESPro'].savePrefs()); + }, false); + RESConsoleProPanel.appendChild(this.proSaveButton); + + /* + this.proUserTaggerSaveButton = createElementWithID('div','RESProSave'); + this.proUserTaggerSaveButton.setAttribute('class','RESButton'); + $(this.proUserTaggerSaveButton).html('Save user tags to Server'); + this.proUserTaggerSaveButton.addEventListener('click', function(e) { + e.preventDefault(); + modules['RESPro'].saveModuleData('userTagger'); + }, false); + RESConsoleProPanel.appendChild(this.proUserTaggerSaveButton); + */ + + this.proSaveCommentsSaveButton = createElementWithID('div','RESProSaveCommentsSave'); + this.proSaveCommentsSaveButton.setAttribute('class','RESButton'); + $(this.proSaveCommentsSaveButton).text('Save saved comments to Server'); + this.proSaveCommentsSaveButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].saveModuleData('saveComments'); + modules['RESPro'].authenticate(modules['RESPro'].saveModuleData('saveComments')); + }, false); + RESConsoleProPanel.appendChild(this.proSaveCommentsSaveButton); + + this.proSubredditManagerSaveButton = createElementWithID('div','RESProSubredditManagerSave'); + this.proSubredditManagerSaveButton.setAttribute('class','RESButton'); + $(this.proSubredditManagerSaveButton).text('Save subreddits to server'); + this.proSubredditManagerSaveButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].saveModuleData('SubredditManager'); + modules['RESPro'].authenticate(modules['RESPro'].saveModuleData('subredditManager')); + }, false); + RESConsoleProPanel.appendChild(this.proSubredditManagerSaveButton); + + this.proSaveCommentsGetButton = createElementWithID('div','RESProGetSavedComments'); + this.proSaveCommentsGetButton.setAttribute('class','RESButton'); + $(this.proSaveCommentsGetButton).text('Get saved comments from Server'); + this.proSaveCommentsGetButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].getModuleData('saveComments'); + modules['RESPro'].authenticate(modules['RESPro'].getModuleData('saveComments')); + }, false); + RESConsoleProPanel.appendChild(this.proSaveCommentsGetButton); + + this.proSubredditManagerGetButton = createElementWithID('div','RESProGetSubredditManager'); + this.proSubredditManagerGetButton.setAttribute('class','RESButton'); + $(this.proSubredditManagerGetButton).text('Get subreddits from Server'); + this.proSubredditManagerGetButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].getModuleData('SubredditManager'); + modules['RESPro'].authenticate(modules['RESPro'].getModuleData('subredditManager')); + }, false); + RESConsoleProPanel.appendChild(this.proSubredditManagerGetButton); + + this.proGetButton = createElementWithID('div','RESProGet'); + this.proGetButton.setAttribute('class','RESButton'); + $(this.proGetButton).text('Get options from Server'); + this.proGetButton.addEventListener('click', function(e) { + e.preventDefault(); + // modules['RESPro'].getPrefs(); + modules['RESPro'].authenticate(modules['RESPro'].getPrefs()); + }, false); + RESConsoleProPanel.appendChild(this.proGetButton); + this.RESConsoleContent.appendChild(RESConsoleProPanel); + }, + open: function(moduleIdOrCategory) { + var category, moduleID; + if (moduleIdOrCategory === 'search') { + moduleID = moduleIdOrCategory; + category = 'About RES'; + } else { + var module = modules[moduleIdOrCategory]; + moduleID = module && module.moduleID; + category = module && module.category; + } + category = category || moduleIdOrCategory || this.categories[0]; + moduleID = moduleID || this.getModuleIDsByCategory(category)[0]; + + + // Draw the config panel + this.drawConfigPanel(); + // Draw the about panel + this.drawAboutPanel(); + // Draw the RES Pro panel + // this.drawProPanel(); + this.openCategoryPanel(category); + this.showConfigOptions(moduleID); + + this.isOpen = true; + // hide the ad-frame div in case it's flash, because then it covers up the settings console and makes it impossible to see the save button! + var adFrame = document.getElementById('ad-frame'); + if ((typeof adFrame !== 'undefined') && (adFrame !== null)) { + adFrame.style.display = 'none'; + } + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'RESConsole'); + // var leftCentered = Math.floor((window.innerWidth - 720) / 2); + // modalOverlay.setAttribute('style','display: block; height: ' + document.documentElement.scrollHeight + 'px'); + this.modalOverlay.classList.remove('fadeOut'); + this.modalOverlay.classList.add('fadeIn'); + + // this.RESConsoleContainer.setAttribute('style','display: block; left: ' + leftCentered + 'px'); + // this.RESConsoleContainer.setAttribute('style','display: block; left: 1.5%;'); + this.RESConsoleContainer.classList.remove('slideOut'); + this.RESConsoleContainer.classList.add('slideIn'); + + RESStorage.setItem('RESConsole.hasOpenedConsole', true); + + document.body.addEventListener('keyup', RESConsole.handleEscapeKey, false); + }, + handleEscapeKey: function(e) { + // don't close if the user is in a token input field (e.g. adding subreddits to a list) + // because they probably just want to cancel the dropdown list + if (e.keyCode === 27 && (document.activeElement.id.indexOf('token-input') === -1)) { + RESConsole.close(); + document.body.removeEventListener('keyup', RESConsole.handleEscapeKey, false); + } + }, + close: function() { + $('#moduleOptionsSave').fadeOut(); + this.isOpen = false; + // Let's be nice to reddit and put their ad frame back now... + var adFrame = document.getElementById('ad-frame'); + if ((typeof adFrame !== 'undefined') && (adFrame !== null)) { + adFrame.style.display = 'block'; + } + + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'RESConsole'); + + // this.RESConsoleContainer.setAttribute('style','display: none;'); + this.modalOverlay.classList.remove('fadeIn'); + this.modalOverlay.classList.add('fadeOut'); + this.RESConsoleContainer.classList.remove('slideIn'); + this.RESConsoleContainer.classList.add('slideOut'); + // just in case the user was in the middle of setting a key and decided to close the dialog, clean that up. + if (typeof RESConsole.keyCodeModal !== 'undefined') { + RESConsole.keyCodeModal.style.display = 'none'; + RESConsole.captureKey = false; + } + + modules['settingsNavigation'].resetUrlHash(); + }, + menuClick: function(obj) { + if (!obj) return; + + var objID = obj.getAttribute('id'); + var category = objID.split('-'); category = category[category.length - 1]; + var moduleID = this.getModuleIDsByCategory(category)[0]; + this.openCategoryPanel(category); + this.showConfigOptions(moduleID); + }, + openCategoryPanel: function(category) { + // make all menu items look unselected + $(RESConsole.RESMenuItems).removeClass('active'); + + // make selected menu item look selected + $(RESConsole.RESMenuItems).filter(function() { + var thisCategory = (this.getAttribute('id') || '').split('-'); + thisCategory = thisCategory[thisCategory.length - 1]; + + if (thisCategory == category) return true; + }).addClass('active'); + + // hide all console panels + $(RESConsole.RESConsoleContent).find('.RESPanel').hide(); + + switch(category) { + case 'Menu-About RES': // cruft + case 'About RES': + // show the about panel + $(this.RESConsoleAboutPanel).show(); + break; + case 'Menu-RES Pro': // cruft + case 'RES Pro': + // show the pro panel + $(this.RESConsoleProPanel).show(); + break; + default: + // show the config panel for the given category + $(this.RESConsoleConfigPanel).show(); + this.drawConfigPanelCategory(category); + break; + } + } +}; + + +/************************************************************************************************************ + +Creating your own module: + +Modules must have the following format, with required functions: +- moduleID - the name of the module, i.e. myModule +- moduleName - a "nice name" for your module... +- description - for the config panel, explains what the module is +- isEnabled - should always return RESConsole.getModulePrefs('moduleID') - where moduleID is your module name. +- isMatchURL - should always return RESUtils.isMatchURL('moduleID') - checks your include and exclude URL matches. +- include - an array of regexes to match against location.href (basically like include in GM) +- exclude (optional) - an array of regexes to exclude against location.href +- go - always checks both if isEnabled() and if RESUtils.isMatchURL(), and if so, runs your main code. + +modules['myModule'] = { + moduleID: 'myModule', + moduleName: 'my module', + category: 'CategoryName', + options: { + // any configurable options you have go here... + // options must have a type and a value.. + // valid types are: text, boolean (if boolean, value must be true or false) + // for example: + defaultMessage: { + type: 'text', + value: 'this is default text', + description: 'explanation of what this option is for' + }, + doSpecialStuff: { + type: 'boolean', + value: false, + description: 'explanation of what this option is for' + } + }, + description: 'This is my module!', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/user\/[-\w\.]+/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/message\/comments\/[-\w\.]+/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // do stuff now! + // this is where your code goes... + } + } +}; // note: you NEED this semicolon at the end! + +************************************************************************************************************/ + + +modules['subRedditTagger'] = { + moduleID: 'subRedditTagger', + moduleName: 'Subreddit Tagger', + category: 'Filters', + options: { + subReddits: { + type: 'table', + addRowText: '+add tag', + fields: [ + { name: 'subreddit', type: 'text' }, + { name: 'doesntContain', type: 'text' }, + { name: 'tag', type: 'text' } + ], + value: [ + /* + ['somebodymakethis','SMT','[SMT]'], + ['pics','pic','[pic]'] + */ + ], + description: 'Set your subreddits below. For that subreddit, if the title of the post doesn\'t contain what you place in the "doesn\'t contain" field, the subreddit will be tagged with whatever you specify.' + } + }, + description: 'Adds tags to posts on subreddits (i.e. [SMT] on SomebodyMakeThis when the user leaves it out)', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[\?]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + this.checkForOldSettings(); + this.SRTDoesntContain = []; + this.SRTTagWith = []; + this.loadSRTRules(); + + RESUtils.watchForElement('siteTable', modules['subRedditTagger'].scanTitles); + this.scanTitles(); + + } + }, + loadSRTRules: function () { + var subReddits = this.options.subReddits.value; + for (var i=0, len=subReddits.length; i 0) { + RESUtils.setOption('subRedditTagger', 'subReddits', settingsCopy); + } + } +}; + + +modules['uppersAndDowners'] = { + moduleID: 'uppersAndDowners', + moduleName: 'Uppers and Downers Enhanced', + category: 'UI', + options: { + showSigns: { + type: 'boolean', + value: false, + description: 'Show +/- signs next to upvote/downvote tallies.' + }, + applyToLinks: { + type: 'boolean', + value: true, + description: 'Uppers and Downers on links.' + }, + postUpvoteStyle: { + type: 'text', + value: 'color:rgb(255, 139, 36); font-weight:normal;', + description: 'CSS style for post upvotes' + }, + postDownvoteStyle: { + type: 'text', + value: 'color:rgb(148, 148, 255); font-weight:normal;', + description: 'CSS style for post upvotes' + }, + commentUpvoteStyle: { + type: 'text', + value: 'color:rgb(255, 139, 36); font-weight:bold;', + description: 'CSS style for comment upvotes' + }, + commentDownvoteStyle: { + type: 'text', + value: 'color:rgb(148, 148, 255); font-weight:bold;', + description: 'CSS style for comment upvotes' + }, + forceVisible: { + type: 'boolean', + value: false, + description: 'Force upvote/downvote counts to be visible (when subreddit CSS tries to hide them)' + } + }, + description: 'Displays up/down vote counts on comments.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/?(?:\??[\w]+=[\w]+&?)*/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[\w]+\/?(?:\??[\w]+=[\w]+&?)*$/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/user\/[-\w\.]+/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]+\/comments\/?[-\w\.]*/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/comments\/[-\w\.]+/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // added code to force inline-block and opacity: 1 to prevent CSS from hiding .res_* classes... + var forceVisible = (this.options.forceVisible.value) ? '; visibility: visible !important; opacity: 1 !important; display: inline-block !important;' : ''; + var css = '.res_comment_ups { '+this.options.commentUpvoteStyle.value+forceVisible+' } .res_comment_downs { '+this.options.commentDownvoteStyle.value+forceVisible+' }'; + css += '.res_post_ups { '+this.options.postUpvoteStyle.value+forceVisible+' } .res_post_downs { '+this.options.postDownvoteStyle.value+forceVisible+' }'; + RESUtils.addCSS(css); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // get rid of the showTimeStamp options since Reddit now has this feature natively. + if (typeof this.options.showTimestamp !== 'undefined') { + delete this.options.showTimestamp; + RESStorage.setItem('RESoptions.uppersAndDowners', JSON.stringify(modules['uppersAndDowners'].options)); + } + if (RESUtils.pageType() === 'comments') { + this.commentsWithMoos = []; + this.moreCommentsIDs = []; + this.applyUppersAndDownersToComments(); + RESUtils.watchForElement('newComments', modules['uppersAndDowners'].applyUppersAndDownersToComments); + } else if (RESUtils.pageType() === 'profile') { + this.commentsWithMoos = []; + this.moreCommentsIDs = []; + this.applyUppersAndDownersToMixed(); + RESUtils.watchForElement('siteTable', modules['uppersAndDowners'].applyUppersAndDownersToMixed); + + } else if ((RESUtils.pageType() === 'linklist') && (this.options.applyToLinks.value)) { + this.linksWithMoos = []; + this.applyUppersAndDownersToLinks(); + RESUtils.watchForElement('siteTable', modules['uppersAndDowners'].applyUppersAndDownersToLinks); + } + } + }, + applyUppersAndDownersToComments: function(ele) { + if (!ele) { + ele = document.body; + } + if (ele.classList.contains('comment')) { + modules['uppersAndDowners'].showUppersAndDownersOnComment(ele); + } else if (ele.classList.contains('entry')) { + modules['uppersAndDowners'].showUppersAndDownersOnComment(ele.parentNode); + } else { + var allComments = ele.querySelectorAll('div.comment'); + RESUtils.forEachChunked(allComments, 15, 1000, function(comment, i, array) { + modules['uppersAndDowners'].showUppersAndDownersOnComment(comment); + }); + } + }, + applyUppersAndDownersToMixed: function(ele) { + ele = ele || document.body; + var linkList = ele.querySelectorAll('div.thing.link, div.thing.comment'), + displayType = 'regular', + thisPlus, thisMinus; + if (modules['uppersAndDowners'].options.showSigns.value) { + thisPlus = '+'; + thisMinus = '-'; + } else { + thisPlus = ''; + thisMinus = ''; + } + for (var i=0, len=linkList.length; i ("+thisPlus+thisups+"|"+thisMinus+thisdowns+") "); + if (displayType === 'regular') { + // thisTagline.insertBefore(upsAndDownsEle, thisTagline.firstChild); + $(thisTagline).prepend(upsAndDownsEle); + } else { + $(thisTagline).after(upsAndDownsEle); + } + } + } else { + modules['uppersAndDowners'].showUppersAndDownersOnComment(linkList[i]); + } + } + + }, + showUppersAndDownersOnComment: function(commentEle) { + // if this is not a valid comment (e.g. a load more comments div, which has the same classes for some reason) + if ((commentEle.getAttribute('data-votesvisible') === 'true') + || (commentEle.classList.contains('morechildren')) + || (commentEle.classList.contains('morerecursion')) + || (commentEle.classList.contains('score-hidden'))) { + return; + } + commentEle.setAttribute('data-votesvisible', 'true'); + var tagline = commentEle.querySelector('p.tagline'); + var ups = commentEle.getAttribute('data-ups'); + var downs = commentEle.getAttribute('data-downs'); + var openparen, closeparen, mooups, moodowns, voteUps, voteDowns, pipe; + var frag = document.createDocumentFragment(); //using a fragment speeds this up by a factor of about 2 + + + if (modules['uppersAndDowners'].options.showSigns.value) { + ups = '+'+ups; + downs = '-'+downs; + } + + openparen = document.createTextNode(" ("); + frag.appendChild(openparen); + + mooups = document.createElement("span"); + mooups.className = "res_comment_ups"; + voteUps = document.createTextNode(ups); + + mooups.appendChild(voteUps); + frag.appendChild(mooups); + + pipe = document.createTextNode("|"); + tagline.appendChild(pipe); + + moodowns = document.createElement("span"); + moodowns.className = "res_comment_downs"; + + voteDowns = document.createTextNode(downs); + moodowns.appendChild(voteDowns); + + frag.appendChild(moodowns); + + closeparen = document.createTextNode(")"); + frag.appendChild(closeparen); + + frag.appendChild(openparen); + frag.appendChild(mooups); + frag.appendChild(pipe); + frag.appendChild(moodowns); + frag.appendChild(closeparen); + + tagline.appendChild(frag); + }, + applyUppersAndDownersToLinks: function(ele) { + // Since we're dealing with max 100 links at a time, we don't need a chunker here... + ele = ele || document.body; + var linkList = ele.querySelectorAll('div.thing.link'), + displayType = 'regular', + thisPlus, thisMinus; + if (modules['uppersAndDowners'].options.showSigns.value) { + thisPlus = '+'; + thisMinus = '-'; + } else { + thisPlus = ''; + thisMinus = ''; + } + for (var i=0, len=linkList.length; i ("+thisPlus+thisups+"|"+thisMinus+thisdowns+") "); + if (displayType === 'regular') { + // thisTagline.insertBefore(upsAndDownsEle, thisTagline.firstChild); + $(thisTagline).prepend(upsAndDownsEle); + } else { + $(thisTagline).after(upsAndDownsEle); + } + } + } + } +}; + +modules['keyboardNav'] = { + moduleID: 'keyboardNav', + moduleName: 'Keyboard Navigation', + category: 'UI', + options: { + // any configurable options you have go here... + // options must have a type and a value.. + // valid types are: text, boolean (if boolean, value must be true or false) + // for example: + focusBGColor: { + type: 'text', + value: '#F0F3FC', + description: 'Background color of focused element' + }, + focusBorder: { + type: 'text', + value: '', + description: 'border style (e.g. 1px dashed gray) for focused element' + }, + focusBGColorNight: { + type: 'text', + value: '#666', + description: 'Background color of focused element in Night Mode' + }, + focusFGColorNight: { + type: 'text', + value: '#DDD', + description: 'Foreground color of focused element in Night Mode' + }, + focusBorderNight: { + type: 'text', + value: '', + description: 'border style (e.g. 1px dashed gray) for focused element' + }, + autoSelectOnScroll: { + type: 'boolean', + value: false, + description: 'Automatically select the topmost element for keyboard navigation on window scroll' + }, + scrollOnExpando: { + type: 'boolean', + value: true, + description: 'Scroll window to top of link when expando key is used (to keep pics etc in view)' + }, + scrollStyle: { + type: 'enum', + values: [ + { name: 'directional', value: 'directional' }, + { name: 'page up/down', value: 'page' }, + { name: 'lock to top', value: 'top' } + ], + value: 'directional', + description: 'When moving up/down with keynav, when and how should RES scroll the window?' + }, + commentsLinkNumbers: { + type: 'boolean', + value: true, + description: 'Assign number keys (e.g. [1]) to links within selected comment' + }, + commentsLinkNumberPosition: { + type: 'enum', + values: [ + { name: 'Place on right', value: 'right' }, + { name: 'Place on left', value: 'left' } + ], + value: 'right', + description: 'Which side commentsLinkNumbers are displayed' + }, + commentsLinkNewTab: { + type: 'boolean', + value: true, + description: 'Open number key links in a new tab' + }, + clickFocus: { + type: 'boolean', + value: true, + description: 'Move keyboard focus to a link or comment when clicked with the mouse' + }, + onHideMoveDown: { + type: 'boolean', + value: true, + description: 'After hiding a link, automatically select the next link' + }, + onVoteMoveDown: { + type: 'boolean', + value: false, + description: 'After voting on a link, automatically select the next link' + }, + toggleHelp: { + type: 'keycode', + value: [191, false, false, true], // ? (note the true in the shift slot) + description: 'Show help for keyboard shortcuts' + }, + toggleCmdLine: { + type: 'keycode', + value: [190, false, false, false], // . + description: 'Show/hide commandline box' + }, + hide: { + type: 'keycode', + value: [72, false, false, false], // h + description: 'Hide link' + }, + moveUp: { + type: 'keycode', + value: [75, false, false, false], // k + description: 'Move up (previous link or comment)' + }, + moveDown: { + type: 'keycode', + value: [74, false, false, false], // j + description: 'Move down (next link or comment)' + }, + moveTop: { + type: 'keycode', + value: [75, false, false, true], // shift-k + description: 'Move to top of list (on link pages)' + }, + moveBottom: { + type: 'keycode', + value: [74, false, false, true], // shift-j + description: 'Move to bottom of list (on link pages)' + }, + moveUpSibling: { + type: 'keycode', + value: [75, false, false, true], // shift-k + description: 'Move to previous sibling (in comments) - skips to previous sibling at the same depth.' + }, + moveDownSibling: { + type: 'keycode', + value: [74, false, false, true], // shift-j + description: 'Move to next sibling (in comments) - skips to next sibling at the same depth.' + }, + moveUpThread: { + type: 'keycode', + value: [75, true, false, true], // shift-alt-k + description: 'Move to the topmost comment of the previous thread (in comments).' + }, + moveDownThread: { + type: 'keycode', + value: [74, true, false, true], // shift-alt-j + description: 'Move to the topmost comment of the next thread (in comments).' + }, + moveToTopComment: { + type: 'keycode', + value: [84, false, false, false], // t + description: 'Move to the topmost comment of the current thread (in comments).' + }, + moveToParent: { + type: 'keycode', + value: [80, false, false, false], // p + description: 'Move to parent (in comments).' + }, + showParents: { + type: 'keycode', + value: [80, false, false, true], // p + description: 'Display parent comments.' + }, + followLink: { + type: 'keycode', + value: [13, false, false, false], // enter + description: 'Follow link (hold shift to open it in a new tab) (link pages only)' + }, + followLinkNewTab: { + type: 'keycode', + value: [13, false, false, true], // shift-enter + description: 'Follow link in new tab (link pages only)' + }, + followLinkNewTabFocus: { + type: 'boolean', + value: true, + description: 'When following a link in new tab - focus the tab?' + }, + toggleExpando: { + type: 'keycode', + value: [88, false, false, false], // x + description: 'Toggle expando (image/text/video) (link pages only)' + }, + imageSizeUp: { + type: 'keycode', + value: [187, false, false, false], + description: 'Increase the size of image(s) in the highlighted post area' + }, + imageSizeDown: { + type: 'keycode', + value: [189, false, false, false], + description: 'Increase the size of image(s) in the highlighted post area' + }, + imageSizeUpFine: { + type: 'keycode', + value: [187, false, false, true], + description: 'Increase the size of image(s) in the highlighted post area (finer control)' + }, + imageSizeDownFine: { + type: 'keycode', + value: [189, false, false, true], + description: 'Increase the size of image(s) in the highlighted post area (finer control)' + }, + previousGalleryImage: { + type: 'keycode', + value: [219, false, false, false], //[ + description: 'View the previous image of an inline gallery.' + }, + nextGalleryImage: { + type: 'keycode', + value: [221, false, false, false], //] + description: 'View the next image of an inline gallery.' + }, + toggleViewImages: { + type: 'keycode', + value: [88, false, false, true], // shift-x + description: 'Toggle "view images" button' + }, + toggleChildren: { + type: 'keycode', + value: [13, false, false, false], // enter + description: 'Expand/collapse comments (comments pages only)' + }, + followComments: { + type: 'keycode', + value: [67, false, false, false], // c + description: 'View comments for link (shift opens them in a new tab)' + }, + followCommentsNewTab: { + type: 'keycode', + value: [67, false, false, true], // shift-c + description: 'View comments for link in a new tab' + }, + followLinkAndCommentsNewTab: { + type: 'keycode', + value: [76, false, false, false], // l + description: 'View link and comments in new tabs' + }, + followLinkAndCommentsNewTabBG: { + type: 'keycode', + value: [76, false, false, true], // shift-l + description: 'View link and comments in new background tabs' + }, + upVote: { + type: 'keycode', + value: [65, false, false, false], // a + description: 'Upvote selected link or comment' + }, + downVote: { + type: 'keycode', + value: [90, false, false, false], // z + description: 'Downvote selected link or comment' + }, + save: { + type: 'keycode', + value: [83, false, false, false], // s + description: 'Save the current link' + }, + reply: { + type: 'keycode', + value: [82, false, false, false], // r + description: 'Reply to current comment (comment pages only)' + }, + openBigEditor: { + type: 'keycode', + value: [69, false, true, false], // control-e + description: 'Open the current markdown field in the big editor. (Only when a markdown form is focused)' + }, + followSubreddit: { + type: 'keycode', + value: [82, false, false, false], // r + description: 'Go to subreddit of selected link (link pages only)' + }, + followSubredditNewTab: { + type: 'keycode', + value: [82, false, false, true], // shift-r + description: 'Go to subreddit of selected link in a new tab (link pages only)' + }, + inbox: { + type: 'keycode', + value: [73, false, false, false], // i + description: 'Go to inbox' + }, + inboxNewTab: { + type: 'keycode', + value: [73, false, false, true], // shift+i + description: 'Go to inbox in a new tab' + }, + profile: { + type: 'keycode', + value: [85, false, false, false], // u + description: 'Go to profile' + }, + profileNewTab: { + type: 'keycode', + value: [85, false, false, true], // shift+u + description: 'Go to profile in a new tab' + }, + frontPage: { + type: 'keycode', + value: [70, false, false, false], // f + description: 'Go to front page' + }, + subredditFrontPage: { + type: 'keycode', + value: [70, false, false, true], // shift-f + description: 'Go to subreddit front page' + }, + nextPage: { + type: 'keycode', + value: [78, false, false, false], // n + description: 'Go to next page (link list pages only)' + }, + prevPage: { + type: 'keycode', + value: [80, false, false, false], // p + description: 'Go to prev page (link list pages only)' + }, + link1: { + type: 'keycode', + value: [49, false, false, false], // 1 + description: 'Open first link within comment.', + noconfig: true + }, + link2: { + type: 'keycode', + value: [50, false, false, false], // 2 + description: 'Open link #2 within comment.', + noconfig: true + }, + link3: { + type: 'keycode', + value: [51, false, false, false], // 3 + description: 'Open link #3 within comment.', + noconfig: true + }, + link4: { + type: 'keycode', + value: [52, false, false, false], // 4 + description: 'Open link #4 within comment.', + noconfig: true + }, + link5: { + type: 'keycode', + value: [53, false, false, false], // 5 + description: 'Open link #5 within comment.', + noconfig: true + }, + link6: { + type: 'keycode', + value: [54, false, false, false], // 6 + description: 'Open link #6 within comment.', + noconfig: true + }, + link7: { + type: 'keycode', + value: [55, false, false, false], // 7 + description: 'Open link #7 within comment.', + noconfig: true + }, + link8: { + type: 'keycode', + value: [56, false, false, false], // 8 + description: 'Open link #8 within comment.', + noconfig: true + }, + link9: { + type: 'keycode', + value: [57, false, false, false], // 9 + description: 'Open link #9 within comment.', + noconfig: true + }, + link10: { + type: 'keycode', + value: [48, false, false, false], // 0 + description: 'Open link #10 within comment.', + noconfig: true + } + }, + description: 'Keyboard navigation for reddit!', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + var focusFGColorNight, focusBGColor, focusBGColorNight; + if (typeof this.options.focusBGColor === 'undefined') { + focusBGColor = '#F0F3FC'; + } else { + focusBGColor = this.options.focusBGColor.value; + } + var borderType = 'outline'; + if (BrowserDetect.isOpera()) borderType = 'border'; + if (typeof this.options.focusBorder === 'undefined') { + focusBorder = ''; + } else { + focusBorder = borderType+': ' + this.options.focusBorder.value + ';'; + } + if (!(this.options.focusBGColorNight.value)) { + focusBGColorNight = '#666'; + } else { + focusBGColorNight = this.options.focusBGColorNight.value; + } + if (!(this.options.focusFGColorNight.value)) { + focusFGColorNight = '#DDD'; + } else { + focusFGColorNight = this.options.focusFGColorNight.value; + } + if (typeof this.options.focusBorderNight === 'undefined') { + focusBorderNight = ''; + } else { + focusBorderNight = borderType+': ' + this.options.focusBorderNight.value + ';'; + } + // old style: .RES-keyNav-activeElement { '+borderType+': '+focusBorder+'; background-color: '+focusBGColor+'; } \ + // this new pure CSS arrow will not work because to position it we must have .RES-keyNav-activeElement position relative, but that screws up image viewer's absolute positioning to + // overlay over the sidebar... yikes. + // .RES-keyNav-activeElement:after { content: ""; float: right; margin-right: -5px; border-color: transparent '+focusBorderColor+' transparent transparent; border-style: solid; border-width: 3px 4px 3px 0; } \ + + // why !important on .RES-keyNav-activeElement? Because some subreddits are unfortunately using !important for no good reason on .entry divs... + RESUtils.addCSS(' \ + .entry { padding-right: 5px; } \ + .RES-keyNav-activeElement, .commentarea .RES-keyNav-activeElement .md, .commentarea .RES-keyNav-activeElement.entry .noncollapsed { background-color: '+focusBGColor+' !important; } \ + .RES-keyNav-activeElement { '+focusBorder+' } \ + .res-nightmode .RES-keyNav-activeElement { '+focusBorderNight+' } \ + .res-nightmode .RES-keyNav-activeElement, .res-nightmode .RES-keyNav-activeElement .usertext-body, .res-nightmode .RES-keyNav-activeElement .usertext-body .md, .res-nightmode .RES-keyNav-activeElement .usertext-body .md p, .res-nightmode .commentarea .RES-keyNav-activeElement .noncollapsed, .res-nightmode .RES-keyNav-activeElement .noncollapsed .md, .res-nightmode .RES-keyNav-activeElement .noncollapsed .md p { background-color: '+focusBGColorNight+' !important; color: '+focusFGColorNight+' !important;} \ + .res-nightmode .RES-keyNav-activeElement a.title:first-of-type {color: ' + focusFGColorNight + ' !important; } \ + #keyHelp { display: none; position: fixed; height: 90%; overflow-y: auto; right: 20px; top: 20px; z-index: 1000; border: 2px solid #aaa; border-radius: 5px; width: 300px; padding: 5px; background-color: #fff; } \ + #keyHelp th { font-weight: bold; padding: 2px; border-bottom: 1px dashed #ddd; } \ + #keyHelp td { padding: 2px; border-bottom: 1px dashed #ddd; } \ + #keyHelp td:first-child { width: 70px; } \ + #keyCommandLineWidget { font-size: 14px; display: none; position: fixed; top: 200px; left: 50%; margin-left: -275px; z-index: 100000110; width: 550px; border: 3px solid #555; border-radius: 10px; padding: 10px; background-color: #333; color: #CCC; opacity: 0.95; } \ + #keyCommandInput { width: 240px; background-color: #999; margin-right: 10px; } \ + #keyCommandInputTip { margin-top: 5px; color: #9F9; } \ + #keyCommandInputTip ul { font-size: 11px; list-style-type: disc; } \ + #keyCommandInputTip li { margin-left: 15px; } \ + #keyCommandInputError { margin-top: 5px; color: red; font-weight: bold; } \ + .keyNavAnnotation { font-size: 9px; position: relative; top: -6px; } \ + '); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // get rid of antequated option we've removed + this.keyboardNavLastIndexCache = safeJSON.parse(RESStorage.getItem('RESmodules.keyboardNavLastIndex'), false, true); + var idx, now = new Date().getTime(); + if (! this.keyboardNavLastIndexCache) { + // this is a one time function to delete old keyboardNavLastIndex junk. + this.keyboardNavLastIndexCache = {}; + for (idx in RESStorage) { + if (idx.match(/keyboardNavLastIndex/)) { + var url = idx.replace('RESmodules.keyboardNavLastIndex.',''); + this.keyboardNavLastIndexCache[url] = { + index: RESStorage[idx], + updated: now + }; + RESStorage.removeItem(idx); + } + } + this.keyboardNavLastIndexCache.lastScan = now; + RESStorage.setItem('RESmodules.keyboardNavLastIndex', JSON.stringify(this.keyboardNavLastIndexCache)); + } else { + // clean cache every 6 hours - delete any urls that haven't been visited in an hour. + if ((typeof this.keyboardNavLastIndexCache.lastScan === 'undefined') || (now - this.keyboardNavLastIndexCache.lastScan > 21600000)) { + for (idx in this.keyboardNavLastIndexCache) { + if ((typeof this.keyboardNavLastIndexCache[idx] === 'object') && (now - this.keyboardNavLastIndexCache[idx].updated > 3600000)) { + delete this.keyboardNavLastIndexCache[idx]; + } + } + this.keyboardNavLastIndexCache.lastScan = now; + RESStorage.setItem('RESmodules.keyboardNavLastIndex', JSON.stringify(this.keyboardNavLastIndexCache)); + } + } + + if (this.options.autoSelectOnScroll.value) { + window.addEventListener('scroll', modules['keyboardNav'].handleScroll, false); + } + if (typeof this.options.scrollTop !== 'undefined') { + if (this.options.scrollTop.value) this.options.scrollStyle.value = 'top'; + delete this.options.scrollTop; + RESStorage.setItem('RESoptions.keyboardNav', JSON.stringify(modules['keyboardNav'].options)); + } + this.drawHelp(); + this.attachCommandLineWidget(); + window.addEventListener('keydown', function(e) { + // console.log(e.keyCode); + modules['keyboardNav'].handleKeyPress(e); + }, true); + this.scanPageForKeyboardLinks(); + // listen for new DOM nodes so that modules like autopager, never ending reddit, "load more comments" etc still get keyboard nav. + if (RESUtils.pageType() === 'comments') { + RESUtils.watchForElement('newComments', modules['keyboardNav'].scanPageForNewKeyboardLinks); + } else { + RESUtils.watchForElement('siteTable', modules['keyboardNav'].scanPageForNewKeyboardLinks); + } + } + }, + scanPageForNewKeyboardLinks: function() { + modules['keyboardNav'].scanPageForKeyboardLinks(true); + }, + setKeyIndex: function() { + var trimLoc = location.href; + // remove any trailing slash from the URL + if (trimLoc.substr(-1) === '/') trimLoc = trimLoc.substr(0,trimLoc.length-1); + if (typeof this.keyboardNavLastIndexCache[trimLoc] === 'undefined') { + this.keyboardNavLastIndexCache[trimLoc] = {}; + } + var now = new Date().getTime(); + this.keyboardNavLastIndexCache[trimLoc] = { + index: this.activeIndex, + updated: now + }; + RESStorage.setItem('RESmodules.keyboardNavLastIndex', JSON.stringify(this.keyboardNavLastIndexCache)); + }, + handleScroll: function(e) { + if (modules['keyboardNav'].scrollTimer) clearTimeout(modules['keyboardNav'].scrollTimer); + modules['keyboardNav'].scrollTimer = setTimeout(modules['keyboardNav'].handleScrollAfterTimer, 300); + }, + handleScrollAfterTimer: function() { + if ((! modules['keyboardNav'].recentKeyPress) && (! RESUtils.elementInViewport(modules['keyboardNav'].keyboardLinks[modules['keyboardNav'].activeIndex]))) { + for (var i=0, len=modules['keyboardNav'].keyboardLinks.length; i 0 ? '/' : '') + command; + this.cmdLineShowTip(str); + } else if (command.slice(0,2) === 'm/') { + str = 'navigate to multi-reddit: /me/' + command; + this.cmdLineShowTip(str); + } else if (command.slice(0,2) === 'u/') { + // get the user name they've typed so far (anything after u/)... + var userString = command.slice(2); + this.cmdLineShowTip('navigate to user profile: ' + userString); + } else if (command.slice(0,1) === '/') { + srString = command.slice(1); + this.cmdLineShowTip('sort by ([n]ew, [t]op, [h]ot, [c]ontroversial): ' + srString); + } else if (command === 'tag') { + if ((typeof this.cmdLineTagUsername === 'undefined') || (this.cmdLineTagUsername === '')) { + var searchArea = modules['keyboardNav'].keyboardLinks[modules['keyboardNav'].activeIndex]; + var authorLink = searchArea.querySelector('a.author'); + this.cmdLineTagUsername = authorLink.innerHTML; + } + str = 'tag user ' + this.cmdLineTagUsername; + if (val) { + str += ' as: ' + val; + } + this.cmdLineShowTip(str); + } else if (command === 'user') { + str = 'go to profile'; + if (val) { + str += ' for: ' + val; + } + this.cmdLineShowTip(str); + } else if (command === 'sw') { + this.cmdLineShowTip('Switch users to: ' + val); + } else if (command === 'm') { + this.cmdLineShowTip('View messages.'); + } else if (command === 'mm') { + this.cmdLineShowTip('View moderator mail.'); + } else if (command === 'ls') { + this.cmdLineShowTip('Toggle lightSwitch.'); + } else if (command === 'nsfw') { + this.cmdLineShowTip('Toggle nsfw filter on or off'); + } else if (command === 'srstyle') { + str = 'toggle subreddit style'; + if (val) { + str += ' for: ' + val; + } else { + if (RESUtils.currentSubreddit()) { + str += ' for: ' + RESUtils.currentSubreddit(); + } + } + this.cmdLineShowTip(str); + } else if (command === 'search') { + this.cmdLineShowTip('Search RES settings for: ' + val); + } else if (command === 'XHRCache') { + this.cmdLineShowTip('clear - clear the cache (use if inline images aren\'t loading properly)'); + } else if (command.slice(0,1) === '?') { + str = 'Currently supported commands:'; + str += '
      '; + str += '
    • r/[subreddit] - navigates to subreddit
    • '; + str += '
    • /n, /t, /h or /c - goes to new, top, hot or controversial sort of current subreddit
    • '; + str += '
    • [number] - navigates to the link with that number (comments pages) or rank (link pages)
    • '; + str += '
    • tag [text] - tags author of currently selected link/comment as text
    • '; + str += '
    • sw [username] - switch users to [username]
    • '; + str += '
    • user [username] or u/[username] - view profile for [username]
    • '; + str += '
    • u/[username]/m/[multi] - view the multireddit [multi] curated by [username]
    • '; + str += '
    • m/[multi] - view your multireddit [multi]'; + str += '
    • m - go to inbox
    • '; + str += '
    • mm - go to moderator mail
    • '; + str += '
    • ls - toggle lightSwitch
    • '; + str += '
    • nsfw [on|off] - toggle nsfw filter on/off
    • '; + str += '
    • srstyle [subreddit] [on|off] - toggle subreddit style on/off (if no subreddit is specified, uses current subreddit)
    • '; + str += '
    • search [words to search for]- search RES settings
    • '; + str += '
    • RESStorage [get|set|update|remove] [key] [value] - For debug use only, you shouldn\'t mess with this unless you know what you\'re doing.
    • '; + str += '
    • XHRCache clear - manipulate the XHR cache
    • '; + str += '
    '; + this.cmdLineShowTip(str); + } else { + this.cmdLineShowTip(''); + } + }, + cmdLineShowTip: function(str) { + $(this.commandLineInputTip).html(str); + }, + cmdLineShowError: function(str) { + $(this.commandLineInputError).html(str); + }, + toggleCmdLine: function(force) { + var open = ((force == null || force) && (this.commandLineWidget.style.display !== 'block')); + delete this.cmdLineTagUsername; + if (open) { + this.cmdLineShowError(''); + this.commandLineWidget.style.display = 'block'; + setTimeout(function() { + modules['keyboardNav'].commandLineInput.focus(); + }, 20); + this.commandLineInput.value = ''; + } else { + modules['keyboardNav'].commandLineInput.blur(); + this.commandLineWidget.style.display = 'none'; + } + modules['styleTweaks'].setSRStyleToggleVisibility(!open, 'cmdline'); + }, + cmdLineSubmit: function(e) { + e.preventDefault(); + $(modules['keyboardNav'].commandLineInputError).html(''); + var theInput = modules['keyboardNav'].commandLineInput.value; + // see what kind of input it is: + if (theInput.match('^\/?r/')) { + // subreddit? (r/subreddit or /r/subreddit) + theInput = theInput.replace(/^\/?r\//,''); + location.href = '/r/'+theInput; + } else if (theInput.match('^\/?m/')) { + theInput = theInput.replace(/^\/?m\//,''); + location.href = '/me/m/'+theInput; + } else if (theInput.match('^\/?u/')) { + // subreddit? (r/subreddit or /r/subreddit) + theInput = theInput.replace(/^\/?u\//,''); + location.href = '/u/'+theInput; + } else if (theInput.indexOf('/') === 0) { + // sort... + theInput = theInput.slice(1); + switch (theInput) { + case 'n': + theInput = 'new'; + break; + case 't': + theInput = 'top'; + break; + case 'h': + theInput = 'hot'; + break; + case 'c': + theInput = 'controversial'; + break; + } + validSorts = ['new','top','hot','controversial']; + if (validSorts.indexOf(theInput) !== -1) { + if (RESUtils.currentUserProfile()) { + location.href = '/user/'+RESUtils.currentUserProfile()+'?sort='+theInput; + } else if (RESUtils.currentSubreddit()) { + location.href = '/r/'+RESUtils.currentSubreddit()+'/'+theInput; + } else { + location.href = '/'+theInput; + } + } else { + modules['keyboardNav'].cmdLineShowError('invalid sort command - must be [n]ew, [t]op, [h]ot or [c]ontroversial'); + return false; + } + } else if (!(isNaN(parseInt(theInput, 10)))) { + if (RESUtils.pageType() === 'comments') { + // comment link number? (integer) + modules['keyboardNav'].commentLink(parseInt(theInput, 10)-1); + } else if (RESUtils.pageType() === 'linklist') { + modules['keyboardNav'].keyUnfocus(modules['keyboardNav'].keyboardLinks[modules['keyboardNav'].activeIndex]); + modules['keyboardNav'].activeIndex = parseInt(theInput, 10) - 1; + modules['keyboardNav'].keyFocus(modules['keyboardNav'].keyboardLinks[modules['keyboardNav'].activeIndex]); + modules['keyboardNav'].followLink(); + } + } else { + var splitWords = theInput.split(' '); + var command = splitWords[0]; + splitWords.splice(0,1); + var val = splitWords.join(' '); + switch (command) { + case 'tag': + var searchArea = modules['keyboardNav'].keyboardLinks[modules['keyboardNav'].activeIndex]; + var tagLink = searchArea.querySelector('a.userTagLink'); + if (tagLink) { + RESUtils.click(tagLink); + setTimeout(function() { + if (val !== '') { + document.getElementById('userTaggerTag').value = val; + } + }, 20); + } + break; + case 'sw': + // switch accounts (username is required) + if (val.length <= 1) { + modules['keyboardNav'].cmdLineShowError('No username specified.'); + return false; + } else { + // first make sure the account exists... + var accounts = modules['accountSwitcher'].options.accounts.value; + var found = false; + for (var i=0, len=accounts.length; i 2) { + splitWords.splice(0,2); + var value = splitWords.join(' '); + } + // console.log(command); + if (command === 'get') { + alert('Value of RESStorage['+key+']:

    '); + } else if (command === 'update') { + var now = new Date().getTime(); + alert('Value of RESStorage['+key+']:

    ', function() { + var textArea = document.getElementById('RESStorageUpdate'+now); + if (textArea) { + var value = textArea.value; + RESStorage.setItem(key, value); + } + }); + } else if (command === 'remove') { + RESStorage.removeItem(key); + alert('RESStorage['+key+'] deleted'); + } else if (command === 'set') { + RESStorage.setItem(key, value); + alert('RESStorage['+key+'] set to:

    '); + } else { + modules['keyboardNav'].cmdLineShowError('You must specify either "get [key]" or "set [key] [value]"'); + } + } + break; + case 'XHRCache': + splitWords = val.split(' '); + if (splitWords.length < 1) { + modules['keyboardNav'].cmdLineShowError('Operation required [clear]'); + } else { + switch (splitWords[0]) { + case 'clear': + RESUtils.xhrCache('clear'); + break; + default: + modules['keyboardNav'].cmdLineShowError('The only accepted operation is clear'); + break; + } + } + break; + case '?': + // user is already looking at help... do nothing. + return false; + break; + default: + modules['keyboardNav'].cmdLineShowError('unknown command - type ? for help'); + return false; + break; + } + } + // hide the commandline tool... + modules['keyboardNav'].toggleCmdLine(false); + }, + scanPageForKeyboardLinks: function(isNew) { + if (typeof isNew === 'undefined') { + isNew = false; + } + // check if we're on a link listing (regular page, subreddit page, etc) or comments listing... + this.pageType = RESUtils.pageType(); + switch(this.pageType) { + case 'linklist': + case 'profile': + // get all links into an array... + var siteTable = document.querySelector('#siteTable'); + var stMultiCheck = document.querySelectorAll('#siteTable'); + // stupid sponsored links create a second div with ID of sitetable (bad reddit! you should never have 2 IDs with the same name! naughty, naughty reddit!) + if (stMultiCheck.length === 2) { + siteTable = stMultiCheck[1]; + } + if (siteTable) { + this.keyboardLinks = document.body.querySelectorAll('div.linklisting .entry'); + if (!isNew) { + if ((this.keyboardNavLastIndexCache[location.href]) && (this.keyboardNavLastIndexCache[location.href].index > 0)) { + this.activeIndex = this.keyboardNavLastIndexCache[location.href].index; + } else { + this.activeIndex = 0; + } + if ((this.keyboardNavLastIndexCache[location.href]) && (this.keyboardNavLastIndexCache[location.href].index >= this.keyboardLinks.length)) { + this.activeIndex = 0; + } + } + } + break; + case 'comments': + // get all links into an array... + this.keyboardLinks = document.body.querySelectorAll('#siteTable .entry, div.content > div.commentarea .entry'); + if (!(isNew)) { + this.activeIndex = 0; + } + break; + case 'inbox': + var siteTable = document.querySelector('#siteTable'); + if (siteTable) { + this.keyboardLinks = siteTable.querySelectorAll('.entry'); + this.activeIndex = 0; + } + break; + } + // wire up keyboard links for mouse clicky selecty goodness... + if ((typeof this.keyboardLinks !== 'undefined') && (this.options.clickFocus.value)) { + for (var i=0, len=this.keyboardLinks.length;i span > a'); + RESUtils.click(hideLink); + // if ((this.options.onHideMoveDown.value) && (!modules['betteReddit'].options.fixHideLink.value)) { + if (this.options.onHideMoveDown.value) { + this.moveDown(); + } + }, + followSubreddit: function(newWindow) { + // find the subreddit link and click it... + var srLink = this.keyboardLinks[this.activeIndex].querySelector('A.subreddit'); + if (srLink) { + var thisHREF = srLink.getAttribute('href'); + if (newWindow) { + var button = (this.options.followLinkNewTabFocus.value) ? 0 : 1, + thisJSON; + if (BrowserDetect.isChrome()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF, + button: button + }; + chrome.extension.sendMessage(thisJSON); + } else if (BrowserDetect.isSafari()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF, + button: button + }; + safari.self.tab.dispatchMessage("keyboardNav", thisJSON); + } else if (BrowserDetect.isOpera()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF, + button: button + }; + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else if (BrowserDetect.isFirefox()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF, + button: button + }; + self.postMessage(thisJSON); + } else { + window.open(thisHREF); + } + } else { + location.href = thisHREF; + } + } + }, + moveUp: function() { + if (this.activeIndex > 0) { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + this.activeIndex--; + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + // skip over hidden elements... + while ((thisXY.x === 0) && (thisXY.y === 0) && (this.activeIndex > 0)) { + this.activeIndex--; + thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + } + this.keyFocus(this.keyboardLinks[this.activeIndex]); + if ((!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) || (this.options.scrollStyle.value === 'top')) { + RESUtils.scrollTo(0,thisXY.y); + } + + modules['keyboardNav'].recentKey(); + } + }, + moveDown: function() { + if (this.activeIndex < this.keyboardLinks.length-1) { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + this.activeIndex++; + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + // skip over hidden elements... + while ((thisXY.x === 0) && (thisXY.y === 0) && (this.activeIndex < this.keyboardLinks.length-1)) { + this.activeIndex++; + thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + } + this.keyFocus(this.keyboardLinks[this.activeIndex]); + // console.log('xy: ' + RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]).toSource()); + /* + if ((!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) || (this.options.scrollTop.value)) { + RESUtils.scrollTo(0,thisXY.y); + } + */ + if (this.options.scrollStyle.value === 'top') { + RESUtils.scrollTo(0,thisXY.y); + } else if ((!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex])))) { + var thisHeight = this.keyboardLinks[this.activeIndex].offsetHeight; + if (this.options.scrollStyle.value === 'page') { + RESUtils.scrollTo(0,thisXY.y); + } else { + RESUtils.scrollTo(0,thisXY.y - window.innerHeight + thisHeight + 5); + } + } + if ((RESUtils.pageType() === 'linklist') && (this.activeIndex == (this.keyboardLinks.length-1) && (modules['neverEndingReddit'].isEnabled() && modules['neverEndingReddit'].options.autoLoad.value))) { + this.nextPage(); + } + modules['keyboardNav'].recentKey(); + } + }, + moveTop: function() { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + this.activeIndex = 0; + this.keyFocus(this.keyboardLinks[this.activeIndex]); + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + if (!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) { + RESUtils.scrollTo(0,thisXY.y); + } + modules['keyboardNav'].recentKey(); + }, + moveBottom: function() { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + this.activeIndex = this.keyboardLinks.length-1; + this.keyFocus(this.keyboardLinks[this.activeIndex]); + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + if (!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) { + RESUtils.scrollTo(0,thisXY.y); + } + modules['keyboardNav'].recentKey(); + }, + moveDownSibling: function() { + if (this.activeIndex < this.keyboardLinks.length-1) { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + var thisParent = this.keyboardLinks[this.activeIndex].parentNode; + var childCount = thisParent.querySelectorAll('.entry').length; + this.activeIndex += childCount; + // skip over hidden elements... + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + while ((thisXY.x === 0) && (thisXY.y === 0) && (this.activeIndex < this.keyboardLinks.length-1)) { + this.activeIndex++; + thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + } + if ((this.pageType === 'linklist') || (this.pageType === 'profile')) { + this.setKeyIndex(); + } + this.keyFocus(this.keyboardLinks[this.activeIndex]); + if (!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) { + RESUtils.scrollTo(0,thisXY.y); + } + } + modules['keyboardNav'].recentKey(); + }, + moveUpSibling: function() { + if (this.activeIndex < this.keyboardLinks.length-1) { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + var thisParent = this.keyboardLinks[this.activeIndex].parentNode, + childCount; + if (thisParent.previousSibling !== null) { + childCount = thisParent.previousSibling.previousSibling.querySelectorAll('.entry').length; + } else { + childCount = 1; + } + this.activeIndex -= childCount; + // skip over hidden elements... + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + while ((thisXY.x === 0) && (thisXY.y === 0) && (this.activeIndex < this.keyboardLinks.length-1)) { + this.activeIndex++; + thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + } + if ((this.pageType === 'linklist') || (this.pageType === 'profile')) { + this.setKeyIndex(); + } + this.keyFocus(this.keyboardLinks[this.activeIndex]); + if (!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) { + RESUtils.scrollTo(0,thisXY.y); + } + } + modules['keyboardNav'].recentKey(); + }, + moveUpThread: function() { + if ((this.activeIndex < this.keyboardLinks.length-1) && (this.activeIndex > 1)) { + this.moveToTopComment(); + } + this.moveUpSibling(); + }, + moveDownThread: function() { + if ((this.activeIndex < this.keyboardLinks.length-1) && (this.activeIndex > 1)) { + this.moveToTopComment(); + } + this.moveDownSibling(); + }, + moveToTopComment: function() { + if ((this.activeIndex < this.keyboardLinks.length-1) && (this.activeIndex > 1)) { + var firstParent = this.keyboardLinks[this.activeIndex].parentNode; + //goes up to the root of the current thread + while (!firstParent.parentNode.parentNode.parentNode.classList.contains('content') && (firstParent !== null)) { + this.moveToParent(); + firstParent = this.keyboardLinks[this.activeIndex].parentNode; + } + } + }, + moveToParent: function() { + if ((this.activeIndex < this.keyboardLinks.length-1) && (this.activeIndex > 1)) { + var firstParent = this.keyboardLinks[this.activeIndex].parentNode; + // check if we're at the top parent, first... if the great grandparent has a class of content, do nothing. + if (!firstParent.parentNode.parentNode.parentNode.classList.contains('content')) { + if (firstParent !== null) { + this.keyUnfocus(this.keyboardLinks[this.activeIndex]); + var thisParent = firstParent.parentNode.parentNode.previousSibling; + var newKeyIndex = parseInt(thisParent.getAttribute('keyindex'), 10); + this.activeIndex = newKeyIndex; + this.keyFocus(this.keyboardLinks[this.activeIndex]); + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + if (!(RESUtils.elementInViewport(this.keyboardLinks[this.activeIndex]))) { + RESUtils.scrollTo(0,thisXY.y); + } + } + } + } + modules['keyboardNav'].recentKey(); + }, + showParents: function() { + if ((this.activeIndex < this.keyboardLinks.length-1) && (this.activeIndex > 1)) { + var firstParent = this.keyboardLinks[this.activeIndex].parentNode; + if (firstParent != null) { + var button = $(this.keyboardLinks[this.activeIndex]).find('.buttons :not(:first-child) .bylink:first').get(0); + RESUtils.hover.begin(button, {}, modules['showParent'].showCommentHover, {}); + } + } + }, + toggleChildren: function() { + if (this.activeIndex === 0) { + // Ahh, we're not in a comment, but in the main story... that key should follow the link. + this.followLink(); + } else { + // find out if this is a collapsed or uncollapsed view... + var thisCollapsed = this.keyboardLinks[this.activeIndex].querySelector('div.collapsed'); + var thisNonCollapsed = this.keyboardLinks[this.activeIndex].querySelector('div.noncollapsed'); + if (thisCollapsed.style.display !== 'none') { + thisToggle = thisCollapsed.querySelector('a.expand'); + } else { + // check if this is a "show more comments" box, or just contracted content... + moreComments = thisNonCollapsed.querySelector('span.morecomments > a'); + if (moreComments) { + thisToggle = moreComments; + } else { + thisToggle = thisNonCollapsed.querySelector('a.expand'); + } + // 'continue this thread' links + contThread = thisNonCollapsed.querySelector('span.deepthread > a'); + if(contThread){ + thisToggle = contThread; + } + } + RESUtils.click(thisToggle); + } + }, + toggleExpando: function() { + var thisExpando = this.keyboardLinks[this.activeIndex].querySelector('.expando-button'); + if (thisExpando) { + RESUtils.click(thisExpando); + if (this.options.scrollOnExpando.value) { + var thisXY=RESUtils.getXYpos(this.keyboardLinks[this.activeIndex]); + RESUtils.scrollTo(0,thisXY.y); + } + } + }, + imageResize: function(factor) { + var images = $(this.activeElement).find('.RESImage.loaded'), + thisWidth; + + for (var i=0, len=images.length; i span > a'); + if (saveLink) RESUtils.click(saveLink); + }, + saveComment: function() { + var saveComment = this.keyboardLinks[this.activeIndex].querySelector('.saveComments'); + if (saveComment) RESUtils.click(saveComment); + }, + reply: function() { + // activeIndex = 0 means we're at the original post, not a comment + if ((this.activeIndex > 0) || (RESUtils.pageType() !== 'comments')) { + if ((RESUtils.pageType() === 'comments') && (this.activeIndex === 0) && (! location.href.match('/message/'))) { + $('.usertext-edit textarea:first').focus(); + } else { + var commentButtons = this.keyboardLinks[this.activeIndex].querySelectorAll('ul.buttons > li > a'); + for (var i=0, len=commentButtons.length;i li > a.comments'); + location.href = commentButton.getAttribute('href'); + } else { + var firstCommentBox = document.querySelector('.commentarea textarea[name=text]'); + firstCommentBox.focus(); + } + } + }, + navigateTo: function(newWindow,thisHREF) { + if (newWindow) { + var thisJSON; + if (BrowserDetect.isChrome()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF + }; + chrome.extension.sendMessage(thisJSON); + } else if (BrowserDetect.isSafari()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF + }; + safari.self.tab.dispatchMessage("keyboardNav", thisJSON); + } else if (BrowserDetect.isOpera()) { + thisJSON = { + requestType: 'keyboardNav', + linkURL: thisHREF + }; + opera.extension.postMessage(JSON.stringify(thisJSON)); + } else { + window.open(thisHREF); + } + } else { + location.href = thisHREF; + } + }, + inbox: function(newWindow) { + var thisHREF = location.protocol + '//'+location.hostname+'/message/inbox/'; + modules['keyboardNav'].navigateTo(newWindow,thisHREF); + }, + profile: function(newWindow) { + var thisHREF = location.protocol + '//'+location.hostname+'/user/'+RESUtils.loggedInUser(); + modules['keyboardNav'].navigateTo(newWindow,thisHREF); + }, + frontPage: function(subreddit) { + var newhref = location.protocol + '//'+location.hostname+'/'; + if (subreddit) { + newhref += 'r/' + RESUtils.currentSubreddit(); + } + location.href = newhref; + }, + nextPage: function() { + // if Never Ending Reddit is enabled, just scroll to the bottom. Otherwise, click the 'next' link. + if ((modules['neverEndingReddit'].isEnabled()) && (modules['neverEndingReddit'].progressIndicator)) { + RESUtils.click(modules['neverEndingReddit'].progressIndicator); + this.moveBottom(); + } else { + // get the first link to the next page of reddit... + var nextPrevLinks = document.body.querySelectorAll('.content .nextprev a'); + if (nextPrevLinks.length > 0) { + var nextLink = nextPrevLinks[nextPrevLinks.length-1]; + // RESUtils.click(nextLink); + location.href = nextLink.getAttribute('href'); + } + } + }, + prevPage: function() { + // if Never Ending Reddit is enabled, do nothing. Otherwise, click the 'prev' link. + if (modules['neverEndingReddit'].isEnabled()) { + return false; + } else { + // get the first link to the next page of reddit... + var nextPrevLinks = document.body.querySelectorAll('.content .nextprev a'); + if (nextPrevLinks.length > 0) { + var prevLink = nextPrevLinks[0]; + // RESUtils.click(prevLink); + location.href = prevLink.getAttribute('href'); + } + } + }, + getCommentLinks: function(obj) { + if (!obj) obj = this.keyboardLinks[this.activeIndex]; + return obj.querySelectorAll('div.md a:not(.expando-button):not(.madeVisible):not([href^="javascript:"])'); + }, + commentLink: function(num) { + if (this.options.commentsLinkNumbers.value) { + var links = this.getCommentLinks(); + if (typeof links[num] !== 'undefined') { + var thisLink = links[num]; + if ((thisLink.nextSibling) && (typeof thisLink.nextSibling.tagName !== 'undefined') && (thisLink.nextSibling.classList.contains('expando-button'))) { + thisLink = thisLink.nextSibling; + } + // RESUtils.click(thisLink); + this.handleKeyLink(thisLink); + } + } + } +}; + +// user tagger functions +modules['userTagger'] = { + moduleID: 'userTagger', + moduleName: 'User Tagger', + category: 'Users', + options: { + /* + defaultMark: { + type: 'text', + value: '_', + description: 'clickable mark for users with no tag' + }, + */ + hardIgnore: { + type: 'boolean', + value: false, + description: 'If "hard ignore" is off, only post titles and comment text is hidden. If it is on, the entire block is hidden (or in comments, collapsed).' + }, + colorUser: { + type: 'boolean', + value: true, + description: 'Color users based on cumulative upvotes / downvotes' + }, + storeSourceLink: { + type: 'boolean', + value: true, + description: 'By default, store a link to the link/comment you tagged a user on' + }, + hoverInfo: { + type: 'boolean', + value: true, + description: 'Show information on user (karma, how long they\'ve been a redditor) on hover.' + }, + hoverDelay: { + type: 'text', + value: 800, + description: 'Delay, in milliseconds, before hover tooltip loads. Default is 800.' + }, + fadeDelay: { + type: 'text', + value: 200, + description: 'Delay, in milliseconds, before hover tooltip fades away. Default is 200.' + }, + fadeSpeed: { + type: 'text', + value: 0.3, + description: 'Fade animation\'s speed. Default is 0.3, the range is 0-1. Setting the speed to 1 will disable the animation.' + }, + gildComments: { + type: 'boolean', + value: true, + description: 'When clicking the "give gold" button on the user hover info on a comment, give gold to the comment.' + }, + highlightButton: { + type: 'boolean', + value: true, + description: 'Show "highlight" button in user hover info, for distinguishing posts/comments from particular users.' + }, + highlightColor: { + type: 'text', + value: '#5544CC', + description: 'Color used to highlight a selected user, when "highlighted" from hover info.' + }, + highlightColorHover: { + type: 'text', + value: '#6677AA', + description: 'Color used to highlight a selected user on hover.' + }, + USDateFormat: { + type: 'boolean', + value: false, + description: 'Show date (redditor since...) in US format (i.e. 08-31-2010)' + }, + vwNumber: { + type: 'boolean', + value: true, + description: 'Show the number (i.e. [+6]) rather than [vw]' + }, + vwTooltip: { + type: 'boolean', + value: true, + description: 'Show the vote weight tooltip on hover (i.e. "your votes for...")' + } + }, + description: 'Adds a great deal of customization around users - tagging them, ignoring them, and more.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + include: [ + /^https?:\/\/([-\w\.]+\.)?reddit\.com\/[-\w\.]*/i + ], + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + var css = '.comment .tagline { display: inline; }'; + css += '#userTaggerToolTip { display: none; position: absolute; width: 334px; height: 248px; }'; + css += '#userTaggerToolTip label { margin-top: 5px; clear: both; float: left; width: 110px; }'; + css += '#userTaggerToolTip input[type=text], #userTaggerToolTip select { margin-top: 5px; float: left; width: 195px; border: 1px solid #c7c7c7; border-radius: 3px; margin-bottom: 6px; }'; + css += '#userTaggerToolTip input[type=checkbox] { margin-top: 5px; float: left; }'; + css += '#userTaggerToolTip input[type=submit] { cursor: pointer; position: absolute; right: 16px; bottom: 16px; padding: 3px 5px; font-size: 12px; color: #fff; border: 1px solid #636363; border-radius: 3px; background-color: #5cc410; } '; + css += '#userTaggerToolTip .toggleButton { margin-top: 5px; margin-bottom: 5px; }'; + css += '#userTaggerClose { position: absolute; right: 7px; top: 7px; z-index: 11; }'; + + css += '.ignoredUserComment { color: #CACACA; padding: 3px; font-size: 10px; }'; + css += '.ignoredUserPost { color: #CACACA; padding: 3px; font-size: 10px; }'; + css += 'a.voteWeight { text-decoration: none; color: #369; }'; + css += 'a.voteWeight:hover { text-decoration: none; }'; + css += '#authorInfoToolTip { display: none; position: absolute; min-width: 450px; z-index: 10001; }'; + css += '#authorInfoToolTip:before { content: ""; position: absolute; top: 10px; left: -26px; border-style: solid; border-width: 10px 29px 10px 0; border-color: transparent #c7c7c7; display: block; width: 0; z-index: 1; }' + css += '#authorInfoToolTip:after { content: ""; position: absolute; top: 10px; left: -24px; border-style: solid; border-width: 10px 29px 10px 0; border-color: transparent #f0f3fc; display: block; width: 0; z-index: 1; }' + css += '#authorInfoToolTip.right:before { content: ""; position: absolute; top: 10px; right: -26px; left: auto; border-style: solid; border-width: 10px 0 10px 29px; border-color: transparent #c7c7c7; display: block; width: 0; z-index: 1; }' + css += '#authorInfoToolTip.right:after { content: ""; position: absolute; top: 10px; right: -24px; left: auto; border-style: solid; border-width: 10px 0 10px 29px; border-color: transparent #f0f3fc; display: block; width: 0; z-index: 1; }' + css += '#authorInfoToolTip .authorFieldPair { clear: both; overflow: auto; margin-bottom: 12px; }'; + css += '#authorInfoToolTip .authorLabel { float: left; width: 140px; }'; + css += '#authorInfoToolTip .authorDetail { float: left; min-width: 240px; }'; + css += '#authorInfoToolTip .blueButton { float: right; margin-left: 8px; margin-top: 12px; }'; + css += '#authorInfoToolTip .redButton { float: right; margin-left: 8px; }'; + + css += '#benefits { width: 200px; margin-left: 0; }'; + css += '#userTaggerToolTip #userTaggerVoteWeight { width: 30px; }'; + css += '.RESUserTagImage { display: inline-block; width: 16px; height: 8px; background-image: url(\'http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png\'); background-repeat: no-repeat; background-position: -16px -137px; }'; + css += '.userTagLink { display: inline-block; }'; + css += '.hoverHelp { margin-left: 3px; cursor: pointer; color: #369; text-decoration: underline; }'; + css += '.userTagLink.hasTag, #userTaggerPreview { display: inline-block; padding: 0 4px; border: 1px solid #c7c7c7; border-radius: 3px; }'; + css += '#userTaggerPreview { float: left; height: 16px; margin-bottom: 10px; }'; + css += '#userTaggerToolTip .toggleButton .toggleOn { background-color: #107ac4; color: #fff; }'; + css += '#userTaggerToolTip .toggleButton.enabled .toggleOn { background-color: #ddd ; color: #636363; }'; + css += '#userTaggerToolTip .toggleButton.enabled .toggleOff { background-color: #d02020; color: #fff; }'; + css += '#userTaggerToolTip .toggleButton .toggleOff { background-color: #ddd; color: #636363; } '; + css += '#userTaggerTable th { -moz-user-select: none; -webkit-user-select: none; -o-user-select: none; user-select: none; }' + css += '#userTaggerTable tbody .deleteButton { cursor: pointer; width: 16px; height: 16px; background-image: url(data:image/gif;base64,R0lGODlhEAAQAOZOAP///3F6hcopAJMAAP/M//Hz9OTr8ZqksMTL1P8pAP9MDP9sFP+DIP8zAO7x8/D1/LnEz+vx+Flha+Ln7OLm61hhayk0QCo1QMfR2eDo8b/K1M/U2pqiqcfP15WcpcLK05ymsig0P2lyftnf5naBi8XJzZ6lrJGdqmBqdKissYyZpf/+/puotNzk66ayvtbc4rC7x9Xd5n+KlbG7xpiirnJ+ivDz9KKrtrvH1Ojv9ePq8HF8h2x2gvj9/yYyPmRueFxlb4eRm+71+kFLVdrb3c/X4KOnrYGMl3uGke/0+5Sgq1ZfaY6Xn/X4+f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAE4ALAAAAAAQABAAAAexgE6CggGFAYOIiAEPEREPh4lOhpOUgwEAmJmaABuQAUktMUUYGhAwLiwnKp41REYmHB5MQUcyN0iQTjsAHU05ICM4SjMQJIg8AAgFBgcvE5gUJYgiycsHDisCApjagj/VzAACBATa5AJOKOAHAAMMDOTvA05A6w7tC/kL804V9uIKAipA52QJgA82dNAQRyBBgwYJyjmRgKmHkAztHA4YAJHfEB8hLFxI0W4AACcbnQQCADs=)}'; + + RESUtils.addCSS(css); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + + this.usernameRE = /(?:u|user)\/([\w\-]+)/; + // Get user tag data... + var tags = RESStorage.getItem('RESmodules.userTagger.tags'); + this.tags = null; + if (typeof tags !== 'undefined') this.tags = safeJSON.parse(tags, 'RESmodules.userTagger.tags', true); + // check if we're using the old method of storing user tags... yuck! + if (this.tags === null) { + this.updateTagStorage(); + } + // If we're on the dashboard, add a tab to it... + if (RESUtils.currentSubreddit('dashboard')) { + // add tab to dashboard + modules['dashboard'].addTab('userTaggerContents','My User Tags'); + // populate the contents of the tab + var showDiv = $('
    Show:
    ') + var tagFilter = $('') + $(showDiv).append(tagFilter); + $('#userTaggerContents').append(showDiv); + $('#tagFilter').change(function(){ + modules['userTagger'].drawUserTagTable(); + }); + + var tagsPerPage = parseInt(modules['dashboard'].options['tagsPerPage'].value, 10); + if (tagsPerPage) { + var controlWrapper = document.createElement('div'); + controlWrapper.id = 'tagPageControls'; + controlWrapper.className = 'RESGalleryControls'; + controlWrapper.page = 1; + controlWrapper.pageCount = 1; + + var leftButton = document.createElement("a"); + leftButton.className = 'previous noKeyNav'; + leftButton.addEventListener('click', function(e){ + if (controlWrapper.page === 1) { + controlWrapper.page = controlWrapper.pageCount; + } else { + controlWrapper.page -= 1; + } + modules['userTagger'].drawUserTagTable(); + }); + controlWrapper.appendChild(leftButton); + + var posLabel = document.createElement('span'); + posLabel.className = 'RESGalleryLabel'; + posLabel.textContent = "1 of 2"; + controlWrapper.appendChild(posLabel); + + var rightButton = document.createElement("a"); + rightButton.className = 'next noKeyNav'; + rightButton.addEventListener('click', function(e){ + if (controlWrapper.page === controlWrapper.pageCount) { + controlWrapper.page = 1; + } else { + controlWrapper.page += 1; + } + modules['userTagger'].drawUserTagTable(); + }); + controlWrapper.appendChild(rightButton); + + $('#userTaggerContents').append(controlWrapper); + + } + var thisTable = $(''); + $(thisTable).append(''); + $('#userTaggerContents').append(thisTable); + $('#userTaggerTable thead th').click(function(e) { + e.preventDefault(); + if ($(this).hasClass('delete')) { + return false; + } + if ($(this).hasClass('active')) { + $(this).toggleClass('descending'); + } + $(this).addClass('active'); + $(this).siblings().removeClass('active').find('SPAN').remove(); + $(this).find('.sortAsc, .sortDesc').remove(); + ($(e.target).hasClass('descending')) ? $(this).append('') : $(this).append(''); + modules['userTagger'].drawUserTagTable($(e.target).attr('sort'), $(e.target).hasClass('descending')); + }); + this.drawUserTagTable(); + + } + + + // set up an array to cache user data + this.authorInfoCache = []; + if (this.options.colorUser.value) { + this.attachVoteHandlers(document.body); + } + // add tooltip to document body... + this.userTaggerToolTip = createElementWithID('div','userTaggerToolTip', 'RESDialogSmall'); + var thisHTML = '

    Tag User

    '; + thisHTML += '
    '; + thisHTML += ' '; + thisHTML += '
    ×
    '; + thisHTML += ' '; + thisHTML += ' '; + thisHTML += '';// '; + thisHTML += ' '; + thisHTML += ' '; + thisHTML += '
    '; + $(this.userTaggerToolTip).html(thisHTML); + var ignoreLabel = this.userTaggerToolTip.querySelector('label[for=userTaggerIgnore]'); + insertAfter(ignoreLabel, RESUtils.toggleButton('userTaggerIgnore', false, 'no', 'yes')); + this.userTaggerTag = this.userTaggerToolTip.querySelector('#userTaggerTag'); + this.userTaggerTag.addEventListener('keyup', modules['userTagger'].updateTagPreview, false); + this.userTaggerColor = this.userTaggerToolTip.querySelector('#userTaggerColor'); + this.userTaggerColor.addEventListener('change', modules['userTagger'].updateTagPreview, false); + this.userTaggerPreview = this.userTaggerToolTip.querySelector('#userTaggerPreview'); + var userTaggerSave = this.userTaggerToolTip.querySelector('#userTaggerSave'); + userTaggerSave.setAttribute('type','submit'); + userTaggerSave.setAttribute('value','✓ save tag'); + userTaggerSave.addEventListener('click', function(e) { + e.preventDefault(); + modules['userTagger'].saveTagForm(); + }, false); + var userTaggerClose = this.userTaggerToolTip.querySelector('#userTaggerClose'); + userTaggerClose.addEventListener('click', function(e) { + modules['userTagger'].closeUserTagPrompt(); + }, false); + //this.userTaggerToolTip.appendChild(userTaggerSave); + this.userTaggerForm = this.userTaggerToolTip.querySelector('FORM'); + this.userTaggerForm.addEventListener('submit', function(e) { + e.preventDefault(); + modules['userTagger'].saveTagForm(); + }, true); + document.body.appendChild(this.userTaggerToolTip); + if (this.options.hoverInfo.value) { + this.authorInfoToolTip = createElementWithID('div', 'authorInfoToolTip', 'RESDialogSmall'); + this.authorInfoToolTipHeader = document.createElement('h3'); + this.authorInfoToolTip.appendChild(this.authorInfoToolTipHeader); + this.authorInfoToolTipCloseButton = createElementWithID('div', 'authorInfoToolTipClose', 'RESCloseButton'); + $(this.authorInfoToolTipCloseButton).text('×'); + this.authorInfoToolTip.appendChild(this.authorInfoToolTipCloseButton); + this.authorInfoToolTipCloseButton.addEventListener('click', function(e) { + if (typeof modules['userTagger'].hideTimer !== 'undefined') { + clearTimeout(modules['userTagger'].hideTimer); + } + modules['userTagger'].hideAuthorInfo(); + }, false); + this.authorInfoToolTipContents = createElementWithID('div','authorInfoToolTipContents', 'RESDialogContents'); + this.authorInfoToolTip.appendChild(this.authorInfoToolTipContents); + this.authorInfoToolTip.addEventListener('mouseover', function(e) { + if (typeof modules['userTagger'].hideTimer !== 'undefined') { + clearTimeout(modules['userTagger'].hideTimer); + } + }, false); + this.authorInfoToolTip.addEventListener('mouseout', function(e) { + if (e.target.getAttribute('class') !== 'hoverAuthor') { + modules['userTagger'].hideTimer = setTimeout(function() { + modules['userTagger'].hideAuthorInfo(); + }, modules['userTagger'].options.fadeDelay.value); + } + }, false); + document.body.appendChild(this.authorInfoToolTip); + } + document.getElementById('userTaggerTag').addEventListener('keydown', function(e) { + if (e.keyCode === 27) { + // close prompt. + modules['userTagger'].closeUserTagPrompt(); + } + }, true); + //console.log('before applytags: ' + Date()); + this.applyTags(); + //console.log('after applytags: ' + Date()); + if (RESUtils.pageType() === 'comments') { + RESUtils.watchForElement('newComments', modules['userTagger'].attachVoteHandlers); + RESUtils.watchForElement('newComments', modules['userTagger'].applyTags); + } else { + RESUtils.watchForElement('siteTable', modules['userTagger'].attachVoteHandlers); + RESUtils.watchForElement('siteTable', modules['userTagger'].applyTags); + } + + var userpagere = /^https?:\/\/([a-z]+).reddit.com\/user\/[-\w\.]+\/?/i; + if (userpagere.test(location.href)) { + var friendButton = document.querySelector('.titlebox .fancy-toggle-button'); + if ((typeof friendButton !== 'undefined') && (friendButton !== null)) { + var firstAuthor = document.querySelector('a.author'); + if ((typeof firstAuthor !== 'undefined') && (firstAuthor !== null)) { + var thisFriendComment = firstAuthor.getAttribute('title'); + thisFriendComment = (thisFriendComment !== null) ? thisFriendComment.substring(8,thisFriendComment.length-1) : ''; + } else { + var thisFriendComment = ''; + } + // this stopped working. commenting it out for now. if i add this back I need to check if you're reddit gold anyway. + /* + var benefitsForm = document.createElement('div'); + var thisUser = document.querySelector('.titlebox > h1').innerHTML; + $(benefitsForm).html('
    '); + insertAfter( friendButton, benefitsForm ); + */ + } + } + } + }, + attachVoteHandlers: function(obj) { + var voteButtons = obj.querySelectorAll('.arrow'); + this.voteStates = []; + for (var i=0, len=voteButtons.length;i tagB) ? 1 : (tagB > tagA) ? -1 : 0; + }); + if (this.descending) taggedUsers.reverse(); + break; + case 'ignore': + taggedUsers.sort(function(a,b) { + var tagA = (typeof modules['userTagger'].tags[a].ignore === 'undefined') ? 'z' : 'a'; + var tagB = (typeof modules['userTagger'].tags[b].ignore === 'undefined') ? 'z' : 'a'; + return (tagA > tagB) ? 1 : (tagB > tagA) ? -1 : 0; + }); + if (this.descending) taggedUsers.reverse(); + break; + case 'color': + taggedUsers.sort(function(a,b) { + var colorA = (typeof modules['userTagger'].tags[a].color === 'undefined') ? 'zzzzz' : modules['userTagger'].tags[a].color.toLowerCase(); + var colorB = (typeof modules['userTagger'].tags[b].color === 'undefined') ? 'zzzzz' : modules['userTagger'].tags[b].color.toLowerCase(); + return (colorA > colorB) ? 1 : (colorB > colorA) ? -1 : 0; + }); + if (this.descending) taggedUsers.reverse(); + break; + case 'votes': + taggedUsers.sort(function(a,b) { + var tagA = (typeof modules['userTagger'].tags[a].votes === 'undefined') ? 0 : modules['userTagger'].tags[a].votes; + var tagB = (typeof modules['userTagger'].tags[b].votes === 'undefined') ? 0 : modules['userTagger'].tags[b].votes; + return (tagA > tagB) ? 1 : (tagB > tagA) ? -1 : (a.toLowerCase() > b.toLowerCase()); + }); + if (this.descending) taggedUsers.reverse(); + break; + default: + // sort users, ignoring case + taggedUsers.sort(function(a,b) { + return (a.toLowerCase() > b.toLowerCase()) ? 1 : (b.toLowerCase() > a.toLowerCase()) ? -1 : 0; + }); + if (this.descending) taggedUsers.reverse(); + break; + } + $('#userTaggerTable tbody').html(''); + var tagsPerPage = parseInt(modules['dashboard'].options['tagsPerPage'].value, 10); + var count = taggedUsers.length; + var start = 0; + var end = count; + + if (tagsPerPage) { + var tagControls = $('#tagPageControls'); + var page = tagControls.prop('page'); + var pages = Math.ceil(count / tagsPerPage); + page = Math.min(page, pages); + page = Math.max(page, 1); + tagControls.prop('page', page).prop('pageCount', pages); + tagControls.find('.RESGalleryLabel').text(page + ' of ' + pages); + start = tagsPerPage*(page-1); + end = Math.min(count, tagsPerPage*page); + } + + for (var i = start; i < end; i++) { + var thisUser = taggedUsers[i]; + var thisTag = (typeof this.tags[thisUser].tag === 'undefined') ? '' : this.tags[thisUser].tag; + var thisVotes = (typeof this.tags[thisUser].votes === 'undefined') ? 0 : this.tags[thisUser].votes; + var thisColor = (typeof this.tags[thisUser].color === 'undefined') ? '' : this.tags[thisUser].color; + var thisIgnore = (typeof this.tags[thisUser].ignore === 'undefined') ? 'no' : 'yes'; + + var userTagLink = document.createElement('a'); + if (thisTag === '') { + // thisTag = '
    '; + userTagLink.setAttribute('class','userTagLink RESUserTagImage'); + } else { + userTagLink.setAttribute('class','userTagLink hasTag'); + } + $(userTagLink).html(escapeHTML(thisTag)); + if (thisColor) { + var bgColor = (thisColor === 'none') ? 'transparent' : thisColor; + userTagLink.setAttribute('style','background-color: '+bgColor+'; color: '+this.bgToTextColorMap[thisColor]+' !important;'); + } + userTagLink.setAttribute('username',thisUser); + userTagLink.setAttribute('title','set a tag'); + userTagLink.setAttribute('href','javascript:void(0)'); + userTagLink.addEventListener('click', function(e) { + modules['userTagger'].openUserTagPrompt(e.target, this.getAttribute('username')); + }, true); + + $('#userTaggerTable tbody').append('
    '); + $('#tag_'+i).append(userTagLink); + } + $('#userTaggerTable tbody .deleteButton').click(function(e) { + var thisUser = $(this).attr('user'); + var answer = confirm("Are you sure you want to delete the tag for user: "+thisUser+"?"); + if (answer) { + delete modules['userTagger'].tags[thisUser]; + RESStorage.setItem('RESmodules.userTagger.tags', JSON.stringify(modules['userTagger'].tags)); + $(this).closest('tr').remove(); + } + }); + }, + saveTagForm: function() { + var thisName = document.getElementById('userTaggerName').value; + var thisTag = document.getElementById('userTaggerTag').value; + var thisColor = document.getElementById('userTaggerColor').value; + var thisIgnore = document.getElementById('userTaggerIgnore').checked; + var thisLink = document.getElementById('userTaggerLink').value; + var thisVotes = parseInt(document.getElementById('userTaggerVoteWeight').value, 10); + if (isNaN(thisVotes)) thisVotes = 0; + modules['userTagger'].setUserTag(thisName, thisTag, thisColor, thisIgnore, thisLink, thisVotes); + }, + bgToTextColorMap: { + 'none': 'black', + 'aqua': 'black', + 'black': 'white', + 'blue': 'white', + 'fuchsia': 'white', + 'pink': 'black', + 'gray': 'white', + 'green': 'white', + 'lime': 'black', + 'maroon': 'white', + 'navy': 'white', + 'olive': 'black', + 'orange': 'black', + 'purple': 'white', + 'red':' black', + 'silver': 'black', + 'teal': 'white', + 'white': 'black', + 'yellow': 'black' + }, + openUserTagPrompt: function(obj, username) { + var thisXY=RESUtils.getXYpos(obj); + this.clickedTag = obj; + var thisH3 = document.querySelector('#userTaggerToolTip h3'); + thisH3.textContent = 'Tag '+username; + document.getElementById('userTaggerName').value = username; + var thisTag = null; + var thisIgnore = null; + if (typeof this.tags[username] !== 'undefined') { + if (typeof this.tags[username].link !== 'undefined') { + document.getElementById('userTaggerLink').value = this.tags[username].link; + } else { + document.getElementById('userTaggerLink').value = ''; + } + if (typeof this.tags[username].tag !== 'undefined') { + document.getElementById('userTaggerTag').value = this.tags[username].tag; + } else { + document.getElementById('userTaggerTag').value = ''; + if (typeof this.tags[username].link === 'undefined') { + // since we haven't yet set a tag or a link for this user, auto populate a link for the + // user based on where we are tagging from. + this.setLinkBasedOnTagLocation(obj); + } + } + if (typeof this.tags[username].ignore !== 'undefined') { + document.getElementById('userTaggerIgnore').checked = this.tags[username].ignore; + var thisToggle = document.getElementById('userTaggerIgnoreContainer'); + if (this.tags[username].ignore) thisToggle.classList.add('enabled'); + } else { + document.getElementById('userTaggerIgnore').checked = false; + } + if (typeof this.tags[username].votes !== 'undefined') { + document.getElementById('userTaggerVoteWeight').value = this.tags[username].votes; + } else { + document.getElementById('userTaggerVoteWeight').value = ''; + } + if (typeof this.tags[username].color !== 'undefined') { + RESUtils.setSelectValue(document.getElementById('userTaggerColor'), this.tags[username].color); + } else { + document.getElementById('userTaggerColor').selectedIndex = 0; + } + } else { + document.getElementById('userTaggerTag').value = ''; + document.getElementById('userTaggerIgnore').checked = false; + document.getElementById('userTaggerVoteWeight').value = ''; + document.getElementById('userTaggerLink').value = ''; + if (this.options.storeSourceLink.value) { + this.setLinkBasedOnTagLocation(obj); + } + document.getElementById('userTaggerColor').selectedIndex = 0; + } + this.userTaggerToolTip.setAttribute('style', 'display: block; top: ' + thisXY.y + 'px; left: ' + thisXY.x + 'px;'); + document.getElementById('userTaggerTag').focus(); + modules['userTagger'].updateTagPreview(); + return false; + }, + setLinkBasedOnTagLocation: function(obj) { + var closestEntry = $(obj).closest('.entry'); + var linkTitle = $(closestEntry).find('a.title'); + // if we didn't find anything, try a new search (works on inbox) + if (!linkTitle.length) { + linkTitle = $(closestEntry).find('a.bylink'); + } + if (linkTitle.length) { + document.getElementById('userTaggerLink').value = $(linkTitle).attr('href'); + } else { + var permaLink = $(closestEntry).find('.flat-list.buttons li.first a'); + if (permaLink.length) { + document.getElementById('userTaggerLink').value = $(permaLink).attr('href'); + } + } + }, + updateTagPreview: function() { + $(modules['userTagger'].userTaggerPreview).text(modules['userTagger'].userTaggerTag.value); + var bgcolor = modules['userTagger'].userTaggerColor[modules['userTagger'].userTaggerColor.selectedIndex].value; + modules['userTagger'].userTaggerPreview.style.backgroundColor = bgcolor; + modules['userTagger'].userTaggerPreview.style.color = modules['userTagger'].bgToTextColorMap[bgcolor]; + }, + closeUserTagPrompt: function() { + this.userTaggerToolTip.setAttribute('style','display: none'); + if (modules['keyboardNav'].isEnabled()) { + var inputs = this.userTaggerToolTip.querySelectorAll('INPUT, BUTTON'); + // remove focus from any input fields from the prompt so that keyboard navigation works again... + for (var i=0,len=inputs.length; i 0) { + red = Math.max(0, (255-(8*votes))); + green = 255; + blue = Math.max(0, (255-(8*votes))); + } else if (votes < 0) { + red = 255; + green = Math.max(0, (255-Math.abs(8*votes))); + blue = Math.max(0, (255-Math.abs(8*votes))); + voteString = ''; + } + voteString = voteString + votes; + var rgb='rgb('+red+','+green+','+blue+')'; + if (obj !== null) { + if (votes === 0) { + obj.style.display = 'none'; + } else { + obj.style.display = 'inline'; + (modules['styleTweaks'].options.lightOrDark.value === 'dark') ? obj.style.color = rgb : obj.style.backgroundColor = rgb; + if (this.options.vwNumber.value) obj.textContent = '[' + voteString + ']'; + if (this.options.vwTooltip.value) obj.setAttribute('title','your votes for '+escapeHTML(author)+': '+escapeHTML(voteString)); + } + } + } + }, + showAuthorInfo: function(obj) { + var isFriend = obj.classList.contains('friend'); + var thisXY=RESUtils.getXYpos(obj); + var thisWidth = $(obj).width(); + // var thisUserName = obj.textContent; + var test = obj.href.match(this.usernameRE); + if (test) var thisUserName = test[1]; + // if (thisUserName.substr(0,3) === '/u/') thisUserName = thisUserName.substr(3); + $(this.authorInfoToolTipHeader).html('' + escapeHTML(thisUserName) + ' (Links) (Comments)'); + RESUtils.getUserInfo(function(userInfo) { + var myID = 't2_'+userInfo.data.id; + if (isFriend) { + var friendButton = '- friends+ friends'; + } else { + var friendButton = '+ friends- friends'; + } + var friendButtonEle = $(friendButton); + $(modules['userTagger'].authorInfoToolTipHeader).append(friendButtonEle); + }); + $(this.authorInfoToolTipContents).html(''+escapeHTML(thisUserName)+':
    loading...'); + if((window.innerWidth-thisXY.x-thisWidth)<=450){ + // tooltip would go off right edge - reverse it. + this.authorInfoToolTip.classList.add('right'); + var tooltipWidth = $(this.authorInfoToolTip).width(); + this.authorInfoToolTip.setAttribute('style', 'top: ' + (thisXY.y - 14) + 'px; left: ' + (thisXY.x - tooltipWidth - 30) + 'px;'); + } else { + this.authorInfoToolTip.classList.remove('right'); + this.authorInfoToolTip.setAttribute('style', 'top: ' + (thisXY.y - 14) + 'px; left: ' + (thisXY.x + thisWidth + 25) + 'px;'); + } + if(this.options.fadeSpeed.value < 0 || this.options.fadeSpeed.value > 1 || isNaN(this.options.fadeSpeed.value)) { + this.options.fadeSpeed.value = 0.3; + } + RESUtils.fadeElementIn(this.authorInfoToolTip, this.options.fadeSpeed.value); + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'authorInfo'); + setTimeout(function() { + if (!RESUtils.elementUnderMouse(modules['userTagger'].authorInfoToolTip) && (!RESUtils.elementUnderMouse(obj))) { + modules['userTagger'].hideAuthorInfo(); + } + }, 1000); + obj.addEventListener('mouseout', modules['userTagger'].delayedHideAuthorInfo); + if (typeof this.authorInfoCache[thisUserName] !== 'undefined') { + this.writeAuthorInfo(this.authorInfoCache[thisUserName], obj); + } else { + GM_xmlhttpRequest({ + method: "GET", + url: location.protocol + "//"+location.hostname+"/user/" + thisUserName + "/about.json?app=res", + onload: function(response) { + var thisResponse = JSON.parse(response.responseText); + modules['userTagger'].authorInfoCache[thisUserName] = thisResponse; + modules['userTagger'].writeAuthorInfo(thisResponse, obj); + } + }); + } + }, + delayedHideAuthorInfo: function(e) { + modules['userTagger'].hideTimer = setTimeout(function() { + e.target.removeEventListener('mouseout', modules['userTagger'].delayedHideAuthorInfo); + modules['userTagger'].hideAuthorInfo(); + }, modules['userTagger'].options.fadeDelay.value); + }, + writeAuthorInfo: function(jsonData, authorLink) { + if (!jsonData.data) { + $(this.authorInfoToolTipContents).text("User not found"); + return false; + } + var utctime = jsonData.data.created_utc; + var d = new Date(utctime * 1000); + // var userHTML = ''+jsonData.data.name+':'; + var userHTML = '
    Redditor since:
    ' + RESUtils.niceDate(d, this.options.USDateFormat.value) + ' (' + RESUtils.niceDateDiff(d) + ')
    '; + userHTML += '
    Link Karma:
    ' + escapeHTML(jsonData.data.link_karma) + '
    '; + userHTML += '
    Comment Karma:
    ' + escapeHTML(jsonData.data.comment_karma) + '
    '; + if ((typeof modules['userTagger'].tags[jsonData.data.name] !== 'undefined') && (modules['userTagger'].tags[jsonData.data.name].link)) { + userHTML += ''; + } + userHTML += '
    '; + userHTML += ' send message'; + if (jsonData.data.is_gold) { + userHTML += 'User has Reddit Gold'; + } else { + userHTML += 'Gift Reddit Gold'; + } + + if (this.options.highlightButton.value) { + if (!this.highlightedUsers || !this.highlightedUsers[jsonData.data.name]) { + userHTML += '
    Highlight
    '; + } else { + userHTML += '
    Unhighlight
    '; + } + } + + if ((modules['userTagger'].tags[jsonData.data.name]) && (modules['userTagger'].tags[jsonData.data.name].ignore)) { + userHTML += '
    ∅ Unignore
    '; + } else { + userHTML += '
    ∅ Ignore
    '; + } + userHTML += '
    '; // closes bottomButtons div + $(this.authorInfoToolTipContents).html(userHTML); + this.authorInfoToolTipIgnore = this.authorInfoToolTipContents.querySelector('#ignoreUser'); + this.authorInfoToolTipIgnore.addEventListener('click', modules['userTagger'].ignoreUser, false); + if (modules['userTagger'].options.highlightButton.value) { + this.authorInfoToolTipHighlight = this.authorInfoToolTipContents.querySelector('#highlightUser'); + if (this.authorInfoToolTipHighlight) { + this.authorInfoToolTipHighlight.addEventListener('click', function(e) { + var username = e.target.getAttribute('user'); + modules['userTagger'].toggleUserHighlight(username); + }, false); + } + } + if (modules['userTagger'].options.gildComments.value && RESUtils.pageType() === 'comments') { + var giveGold = this.authorInfoToolTipContents.querySelector('#gildUser'); + giveGold && giveGold.addEventListener('click', function(e) { + if (e.ctrlKey || e.cmdKey || e.shiftKey) return; + + var comment = $(authorLink).closest('.comment'); + if (!comment) return; + + modules['userTagger'].hideAuthorInfo(); + var giveGold = comment.find('.give-gold')[0]; + RESUtils.click(giveGold); + e.preventDefault(); + }); + } + }, + toggleUserHighlight: function(username) { + if (!this.highlightedUsers) this.highlightedUsers = {}; + + if (this.highlightedUsers[username]) { + this.highlightedUsers[username].remove(); + delete this.highlightedUsers[username]; + this.toggleUserHighlightButton(true); + } else { + + this.highlightedUsers[username] = + modules['userHighlight'].highlightUser(username); + this.toggleUserHighlightButton(false); + } + }, + toggleUserHighlightButton: function(canHighlight) { + $(this.authorInfoToolTipHighlight) + .toggleClass('blueButton', canHighlight) + .toggleClass('redButton', !canHighlight) + .text(canHighlight ? 'Highlight' : 'Unhighlight'); + }, + ignoreUser: function(e) { + if (e.target.classList.contains('blueButton')) { + e.target.classList.remove('blueButton'); + e.target.classList.add('redButton'); + $(e.target).html('∅ Unignore'); + var thisIgnore = true; + } else { + e.target.classList.remove('redButton'); + e.target.classList.add('blueButton'); + $(e.target).html('∅ Ignore'); + var thisIgnore = false; + } + var thisName = e.target.getAttribute('user'); + var thisColor, thisLink, thisVotes, thisTag; + if (modules['userTagger'].tags[thisName]) { + thisColor = modules['userTagger'].tags[thisName].color || ''; + thisLink = modules['userTagger'].tags[thisName].link || ''; + thisVotes = modules['userTagger'].tags[thisName].votes || 0; + thisTag = modules['userTagger'].tags[thisName].tag || ''; + } + if ((thisIgnore) && (thisTag === '')) { + thisTag = 'ignored'; + } else if ((!thisIgnore) && (thisTag === 'ignored')) { + thisTag = ''; + } + modules['userTagger'].setUserTag(thisName, thisTag, thisColor, thisIgnore, thisLink, thisVotes, true); // last true is for noclick param + }, + hideAuthorInfo: function(obj) { + // this.authorInfoToolTip.setAttribute('style', 'display: none'); + if(this.options.fadeSpeed.value < 0 || this.options.fadeSpeed.value > 1 || isNaN(this.options.fadeSpeed.value)) { + this.options.fadeSpeed.value = 0.3; + } + RESUtils.fadeElementOut(this.authorInfoToolTip, this.options.fadeSpeed.value); + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'authorInfo'); + }, + updateTagStorage: function() { + // update tag storage format from the old individual bits to a big JSON blob + // It's OK that we're directly accessing localStorage here because if they have old school tag storage, it IS in localStorage. + ls = (typeof unsafeWindow !== 'undefined') ? unsafeWindow.localStorage : localStorage; + var tags = {}; + var toRemove = []; + for (var i = 0, len=ls.length; i < len; i++){ + var keySplit = null; + if (ls.key(i)) keySplit = ls.key(i).split('.'); + if (keySplit) { + var keyRoot = keySplit[0]; + switch (keyRoot) { + case 'reddituser': + var thisNode = keySplit[1]; + if (typeof tags[keySplit[2]] === 'undefined') { + tags[keySplit[2]] = {}; + } + if (thisNode === 'votes') { + tags[keySplit[2]].votes = ls.getItem(ls.key(i)); + } else if (thisNode === 'tag') { + tags[keySplit[2]].tag = ls.getItem(ls.key(i)); + } else if (thisNode === 'color') { + tags[keySplit[2]].color = ls.getItem(ls.key(i)); + } else if (thisNode === 'ignore') { + tags[keySplit[2]].ignore = ls.getItem(ls.key(i)); + } + // now delete the old stored garbage... + var keyString = 'reddituser.'+thisNode+'.'+keySplit[2]; + toRemove.push(keyString); + break; + default: + // console.log('Not currently handling keys with root: ' + keyRoot); + break; + } + } + } + this.tags = tags; + RESStorage.setItem('RESmodules.userTagger.tags', JSON.stringify(this.tags)); + // now remove the old garbage... + for (var i=0, len=toRemove.length; iand comments tabs.' + } + }, + description: 'Adds a number of interface enhancements to Reddit, such as "full comments" links, the ability to unhide accidentally hidden posts, and more', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/.*/i + ], + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + + if ((this.options.toolbarFix.value) && ((RESUtils.pageType() === 'linklist') || RESUtils.pageType() === 'comments')) { + this.toolbarFix(); + } + if ((RESUtils.pageType() === 'comments') && (this.options.commentsLinksNewTabs.value)) { + this.commentsLinksNewTabs(); + } + // if (((RESUtils.pageType() === 'inbox') || (RESUtils.pageType() === 'profile') || ((RESUtils.pageType() === 'comments') && (RESUtils.currentSubreddit('friends')))) && (this.options.fullCommentsLink.value)) { + // removed profile pages since Reddit does this natively now for those... + if (((RESUtils.pageType() === 'inbox') || ((RESUtils.pageType() === 'comments') && (RESUtils.currentSubreddit('friends') === false))) && (this.options.fullCommentsLink.value)) { + // RESUtils.addCSS('a.redditFullCommentsSub { font-size: 9px !important; color: #BBB !important; }'); + this.fullComments(); + } + if ((RESUtils.pageType() === 'profile') && (location.href.split('/').indexOf(RESUtils.loggedInUser()) !== -1)) { + this.editMyComments(); + } + if (((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments')) && (this.options.fixSaveLinks.value)) { + this.fixSaveLinks(); + } + if (((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments')) && (this.options.fixHideLinks.value)) { + this.fixHideLinks(); + } + if ((this.options.turboSelfText.value) && (RESUtils.pageType() === 'linklist')) { + this.setUpTurboSelfText(); + } + if (this.options.showUnreadCountInFavicon.value) { + var faviconDataurl = 'data:image/x-icon;base64,AAABAAIAEBAAAAAAAABoBQAAJgAAACAgAAAAAAAAqAgAAI4FAAAoAAAAEAAAACAAAAABAAgAAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wBBRP4Aq5qHAFNOSgCkpv4A4Mu0AA8Q/gCAc2cAzM7+ADYvKQCys7QA/urQAGVo/gCGh4kA1NbYAMWvmgBjZWYA5efoAJucngD//+MAwcPFACIk/gCbinkAycGtADo8PgDz2sIAal5RADAy/gBvcHEA9PbzALijkACMfm8AqqmoALCy/gCAgHsAc2piAMnMzwDBw/4AXVxcAJGUlwDOuKMAR0dHANfDrABLTv4Aubu+ADIzNQBbVlEA++HJAP/23ACho6UAa2RdAJOEdQD3+P4A2tzeAO7UvACEeW4AjY2KALCfjgBsa2oArq6uAM/R0wB2cGwA//DWAKOYiACCg4MAemxfAPDw8ABOSkYA4+PjAMm7pgBeWlYAgHt3AGZhWwBtamQA+t7EAGVbUgB7b2QAoaGgAP7kzADdyLEA9Pf6AP774ACwnIoAZ15VAJiHdwBbXWAAy8vLAIh8cAC0oY0AZ2VkALy+vwD+7dMA9t3FAHJwbgB/f38Aqq2vAP//5wB4bWMA7ta/AHVqXwCCfXsAmYp8AIJ1aQDS0tIApKSkAP7jyQDOuaYA/vjeAH1xZQCBgYEArp2MAJ2enwD5+v4A//LYAH5zaQD13MMAd2xhAJWFdgD+69IA/ujPAGxsbABycXAAtqOPAN/IsQDLu6YA/v7+AP/84QD/+t8A/vfbAP7v1QD64MgA1dfZAH5+fgD//uIA/v7jAP/43QD+990AZVxTAP/x1wD+8tcA/u7UAOTk4wCYiHgA///kAP/+4wD++t8A/vneAP743QD/79UA/u3SAP7s0gD+69EA/unQAPXcxACrra8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AD8/Pz8/Pz8/Pz8/Pz8/Pz8/XV1dKY8IYgiPKV1dXV0/P11QVDubPYQ9mztUUF1dP4krRC1+V26Fbld+LUQrXT+UiiV+fiFoQ2ghfn4lil0/P15+fn5+fn5+fn5+fl5dPx8qfn4iHAl+CRwifn4qHz9IHTx+BQcmfiYHBX48HUg/PjYZDh5+fn5+fh4OGTY+P0YzOCBKOTILcCNJIDgzRj8/XV1dfFkXClUQGl1qXV0/P11dXTB4XWdrXV1YRzpdPz9dXV1dXV1vZBhCVlEEXT8/XV1dXV2DY0xtQCRaZl0/P11dXV1dXV1dXV1dXV1dPz8/Pz8/Pz8/Pz8/Pz8/Pz8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAIAAAAAACABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AJSr/AJmJegCIjf4AREZIANzFsADGy/4AUFb/ACgjHwBpamsAAAD/AP/mzQCmp6cA2dnaAL29vgDl6P4AYllPAMWzoAAQEhQAMjQ2AHp7fQCxtv4AlZaWAP784AASFv4A7NfAALGgjwCCdGYAy8vLAPPz9ACJiosARz83AFZNRAB1aV0A1Nj+AFJTVQCzs7MAjX9yAFtdXwDi4uIAqJaGALyplgDRvagAMiwnABkbHQB9g/4ACgoKAOLPuQD+8dcA6+vrAGphVwCdnp4A9t3GAD43LwB0dHQAgYOEADw8PAAdIf8A0tLSAPf5/wC5vv4AxcXGAJ+QgAAbFxMA3eH/AGJjZACtra0A9+rSAE9HQADs7/4AIyQmAHl1aQAsLzEATE5QAEpP/wAhHRoAXFJIAI+PjwAeICIAAwUHAD9BQgBWWFoAwa6bAMm8qAATEA0A///nAOnSuwAVFxgA3d3dAHltYACJe20AlIV2APj4+AA4My4A/vfcAP7s0gDUwa0AuaSSAPviyQA4ODgAt7e3AKGiogAODg8AKyssAMvP/gCBh/4ALiklAIR3awDOzs8A38u2AH9wYwAIBgQAb2NYADYwKgD7+/sA8PDwAPPbxABJS00AwcLCABke/gDY2/8ADgwJAAUDAgAjIB0A+eXMAF9gYgCbm5sAkpOUABQSEQBEPDUAUVFRAIWFhQAZGRkAIyMjAPP1/gDp7P4AtaORAAYHBwAfGxgA39/gAC8xMwDv2cIASUE4AK+vsADmz7kAAQMFABAQEAD+6dAAMSokAEE4MQBVS0EAyLWhAGVlZQCai30AjIyMAP7+4wAUFBQA/vneAP7z2gD+7tUALy8vADs1LgComYgA/f39APb29gAbHR8A/ePLAPngyADb29sAQkRGAEZISgDWw68A07+qAFpQRgBkW1EAvquYAHZ2dgCWh3gADw0MACAiJAD8584ANTc5ADc5OwDZxrEAV1dXAAMCAgDiy7UAjI2PAAwLCwDb3/4Aw7CcAJ+foACDdmgAoZGCABIUFgAjHxoAJSUlAPbcxADIzf4A79fAAOzZwgC3vP4A59G7AOLNtwBdXV0Av62aAAQEBAAGBQQACQgHABYWFgD09PUAFxocADAzNQDDw8MAWE5FAMOynwBaXF0AbmJWAGBiYwC3opAAAgEBABISEgD+6M8A/OXNADAqJgAyLSkANi8oADQ1NgDTvakAtba3AMGwnQBkWk8Aq6ytAG1jWQBjZWYAnY+AAP//5QD+9dsA+N7GAODNtwDRv6oAw66bAAcICQD+/v4A+vr6ABQRDgD5+fkAFhgZAP/43QAaGhoA8fHxAP/w1gD/7dMA6urqAP///wAAVvz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8VgD8NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTX8AKA1NTU1NTU1NagM/fz8oKCg/KDfqDU1NTU1NTU1NaAAoDU1NTU1NQz8p63CWq4gIJciA5g1oJQ1NTU1NTU1oACgNTU1Naf8uCKLz8NJq9knStX4z/ZNEmDfNTU1NTWgAPw1NTX9Gq/dRzhtXQEBAQEBAaQOF7cAgmH8NTU1NfwAoDU1oNhVtJD09PT09PSlHqT09PT09B1RAOwx7zU1oACgNaDOAEL19PT09CWDhtKd+mSbXfT09PROACb8NTWgAKDgy3sK9PT09PTNALl/D9ZDsdLF+/T09PRmAM5gNaAA/PyuRwEBAQEBAU4dAQEBAQEBdIR0AQEBAQFSxLWo/ADuV93p9PT09PT09PT09PT09PT09PT09PT09IwA8pSgABgqijL09PT09PT09PT09PT09PT09PT09PT09EccoKAAnLBn0/T09PT09COH9PT09PT09GlG9PT09PT0jRxfoADtcQBZAQEBATwICwJBAQEBATxLCzp5AQEBAQFY48n5AIlQkzf09PT0BwsLC2r09PT0xwsLCy709PT0Zc9HLJ8AlhVlADv09PQQGQsLyvT09PSIeAsLPfT09PfSmR5Q3ACVvPSDZ6n09PQjLhb09PT09PS+BD309PTTOYX79NXBAG/UdAG53g0BAQEBAQEBAQEBAQEBAQEBPmihdAH+kmIARGsT23YAALaQXfT09PT09PT09PT0PiTdAKrmNC1y7gD5dVuC186sIdDDJIAPWf4yjB3Afk/doufxIHpV6I5fAKBjoKCglN+gzLKPQNGK82cv0FU2wa1glN+UyHWgY6AA/DU1NTU1NTWnoKBjkSspAD8wdf2gDDU1DJQMqDU1/ACgNTU1NTU1NTU1NainlPx8bPmoNTU1Y5Tlv3XfxjWgAKA1NTU1NTU1NTU1NTU1MeprlDU1NajgEbqKTGHfNaAAoDU1NTU1NTU1NTU1NTWUsLq7pzVjnlySdygFCX2ooAD8NTU1NTU1NTU1NTU1NWN1vcLt/Y5UxOsBAQ4ABgz8AKA1NTU1NTU1NTU1NTU1NfxF4kheky+z5HP0H3DIY6AAoDU1NTU1NTU1NTU1NTU1YBuB4TOj8G6LphQAmmA1oACgNTU1NTU1NTU1NTU1NTU1qMiU/GCnlMvB2lNgNTWgAPw1NTU1NTU1NTU1NTU1NTXvY+81NTU1p6D8lDU1NfwAVvz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz8VgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAP////8K'; + // remove current favicons and replace accordingly, or tinycon has a cross domain issue since the real favicon is on redditstatic.com. + $('head link[rel="shortcut icon"], head link[rel="icon"]').attr('href',faviconDataurl); + } + if ((modules['betteReddit'].options.showLastEditedTimestamp.value) && ((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments'))) { + RESUtils.addCSS('.edited-timestamp[title]:after{content:" (" attr(title) ")";font-size: 90%;}'); + } + if ((modules['betteReddit'].options.restoreSavedTab.value) && (RESUtils.loggedInUser() !== null) && document.querySelector('.with-listing-chooser:not(.profile-page)')) { + this.restoreSavedTab(); + } + if ((modules['betteReddit'].options.toolbarFix.value) && (RESUtils.pageType() === 'linklist')) { + RESUtils.watchForElement('siteTable', modules['betteReddit'].toolbarFix); + } + if ((RESUtils.pageType() === 'inbox') && (modules['betteReddit'].options.fullCommentsLink.value)) { + RESUtils.watchForElement('siteTable', modules['betteReddit'].fullComments); + } + if (((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments')) && (modules['betteReddit'].options.fixSaveLinks.value)) { + RESUtils.watchForElement('siteTable', modules['betteReddit'].fixSaveLinks); + } + if (((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments')) && (modules['betteReddit'].options.fixHideLinks.value)) { + RESUtils.watchForElement('siteTable', modules['betteReddit'].fixHideLinks); + } + if ((RESUtils.pageType() === 'comments') && (modules['betteReddit'].options.commentsLinksNewTabs.value)) { + RESUtils.watchForElement('newComments', modules['betteReddit'].commentsLinksNewTabs); + } + + if (this.options.searchSubredditByDefault.value) { + // make sure we're not on a search results page... + if (!location.href.match('/[r|m]/[\\w+\\-]+/search')) { + this.searchSubredditByDefault(); + } + } + if ((this.options.videoTimes.value) && ((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments'))) { + this.getVideoTimes(); + // listen for new DOM nodes so that modules like autopager, river of reddit, etc still get l+c links... + + RESUtils.watchForElement('siteTable', modules['betteReddit'].getVideoTimes); + } + if ((RESUtils.loggedInUser() !== null) && ((this.options.showUnreadCount.value) || (this.options.showUnreadCountInTitle.value) || (this.options.showUnreadCountInFavicon.value))) { + // Reddit CSS change broke this when they went to sprite sheets.. new CSS will fix the issue. + // RESUtils.addCSS('#mail { min-width: 16px !important; width: auto !important; text-indent: 18px !important; background-repeat: no-repeat !important; line-height: 8px !important; }'); + // removing text indent - on 11/14/11 reddit changed the mail sprites, so I have to change how this is handled.. + RESUtils.addCSS('#mail { top: 2px; min-width: 16px !important; width: auto !important; background-repeat: no-repeat !important; line-height: 8px !important; }'); + // RESUtils.addCSS('#mail.havemail { top: 2px !important; margin-right: 1px; }'); + RESUtils.addCSS('#mail.havemail { top: 2px !important; }'); + if ((BrowserDetect.isChrome()) || (BrowserDetect.isSafari())) { + // I hate that I have this conditional CSS in here but I can't figure out why it's needed for webkit and screws up firefox. + RESUtils.addCSS('#mail.havemail { top: 0; }'); + } + this.showUnreadCount(); + } + switch(this.options.pinHeader.value) { + case 'header': + this.pinHeader(); + $('body').addClass('pinHeader-header'); + break; + case 'sub': + this.pinSubredditBar(); + $('body').addClass('pinHeader-sub'); + break; + case 'subanduser': + this.pinSubredditBar(); + this.pinUserBar(); + $('body').addClass('pinHeader-subanduser'); + break; + case 'userbar': + this.pinUserBar(); + $('body').addClass('pinHeader-userbar'); + break; + default: + break; + } + } + }, + commentsLinksNewTabs: function(ele) { + ele = ele || document.body; + var links = ele.querySelectorAll('div.md a'); + for (var i=0, len=links.length; i
    ' + + $('
    ').html(modules['betteReddit'].selfTextHash[thisID]).text() + + '
    ' + ).show(); + } else { + $(event.target).removeClass('expanded'); + $(event.target).addClass('collapsedExpando'); + $(event.target).addClass('collapsed'); + $(event.target).parent().find('.expando').hide(); + } + + } + }, + getSelfTextData: function(href) { + if (!modules['betteReddit'].gettingSelfTextData) { + modules['betteReddit'].gettingSelfTextData = true; + $.getJSON(href, modules['betteReddit'].applyTurboSelfText); + } + }, + applyTurboSelfText: function(data) { + var linkList = data.data.children; + + delete modules['betteReddit'].gettingSelfTextData; + for (var i=0, len=linkList.length; i 300000) { + GM_xmlhttpRequest({ + method: "GET", + url: location.protocol + '//' + location.hostname + "/message/unread/.json?mark=false&app=res", + onload: function(response) { + // save that we've checked in the last 5 minutes + var now = new Date(); + RESStorage.setItem('RESmodules.betteReddit.msgCount.lastCheck.'+RESUtils.loggedInUser(), now.getTime()); + var data = JSON.parse(response.responseText); + var count = data.data.children.length; + RESStorage.setItem('RESmodules.betteReddit.msgCount.'+RESUtils.loggedInUser(), count); + modules['betteReddit'].setUnreadCount(count); + } + }); + } else { + var count = RESStorage.getItem('RESmodules.betteReddit.msgCount.'+RESUtils.loggedInUser()); + modules['betteReddit'].setUnreadCount(count); + } + } else { + // console.log('no need to get count - no new mail. resetting lastCheck'); + modules['betteReddit'].setUnreadCount(0); + RESStorage.setItem('RESmodules.betteReddit.msgCount.lastCheck.'+RESUtils.loggedInUser(), 0); + } + } + }, + setUnreadCount: function(count) { + if (this.options.showUnreadCountInFavicon.value) { + window.Tinycon.setOptions({ fallback: false }); + } + if (count>0) { + if (this.options.showUnreadCountInTitle.value) { + var newTitle = '[' + count + '] ' + document.title.replace(/^\[[\d]+\]\s/,''); + document.title = newTitle; + } + if (this.options.showUnreadCountInFavicon.value) { + window.Tinycon.setBubble(count); + } + if (this.options.showUnreadCount.value) { + modules['betteReddit'].mailCount.display = 'inline-block' + modules['betteReddit'].mailCount.textContent = '['+count+']'; + if (modules['neverEndingReddit'].NREMailCount) { + modules['neverEndingReddit'].NREMailCount.display = 'inline-block' + modules['neverEndingReddit'].NREMailCount.textContent = '['+count+']'; + } + } + } else { + var newTitle = document.title.replace(/^\[[\d]+\]\s/,''); + document.title = newTitle; + if (modules['betteReddit'].mailCount) { + modules['betteReddit'].mailCount.display = 'none'; + $(modules['betteReddit'].mailCount).html(''); + if (modules['neverEndingReddit'].NREMailCount) { + modules['neverEndingReddit'].NREMailCount.display = 'none' + $(modules['neverEndingReddit'].NREMailCount).html(''); + } + } + if (this.options.showUnreadCountInFavicon.value) { + window.Tinycon.setBubble(0); + } + } + }, + toolbarFixLinks: [ + 'etsy.com', + 'youtube.com', + 'youtu.be', + 'twitter.com', + 'teamliquid.net', + 'flickr.com', + 'github.com', + 'battle.net', + 'play.google.com', + 'plus.google.com', + 'soundcloud.com' + ], + checkToolbarLink: function(url) { + for (var i=0, len=this.toolbarFixLinks.length; i'+ escapeHTML(this.options.fullCommentsText.value) +''); + linkList.appendChild(fullCommentsLink); + } + } + }, + editMyComments: function(ele) { + var root = ele || document; + var entries = root.querySelectorAll('#siteTable .entry'); + for (var i=0, len=entries.length; iedit'); + insertAfter(permalink, editLink); + } + } + }, + fixSaveLinks: function(ele) { + var root = ele || document; + var saveLinks = root.querySelectorAll('li:not(.comment-save-button) > FORM.save-button > SPAN > A'); + for (var i=0, len=saveLinks.length; i SPAN > A'); + for (var i=0, len=saveLinks.length; i SPAN > A'); + for (var i=0, len=hideLinks.length; i SPAN > A'); + for (var i=0, len=hideLinks.length; i inside the header to replace the subreddit bar (for spacing) + var spacer = document.createElement('div'); + // null parameter is necessary for FF3.6 compatibility. + spacer.style.paddingTop = window.getComputedStyle(sb, null).paddingTop; + spacer.style.paddingBottom = window.getComputedStyle(sb, null).paddingBottom; + + // HACK: for some reason, if the SM is enabled, the SB gets squeezed horizontally, + // and takes up three rows of vertical space (even at low horizontal resolution). + if (sm) spacer.style.height = (parseInt(window.getComputedStyle(sb, null).height, 10) / 3 - 3)+'px'; + else spacer.style.height = window.getComputedStyle(sb, null).height; + + //window.setTimeout(function(){ + // add the spacer; take the subreddit bar out of the header and put it above + header.insertBefore(spacer, sb); + document.body.insertBefore(sb,header); + + // make it fixed + // RESUtils.addCSS('div#sr-header-area {position: fixed; z-index: 10000 !important; left: 0; right: 0; box-shadow: 0 2px 2px #AAA;}'); + // something changed on Reddit on 1/31/2012 that made this header-bottom-left margin break subreddit stylesheets... commenting out seems to fix it? + // and now later on 1/31 they've changed it back and I need to add this line back in... + RESUtils.addCSS('#header-bottom-left { margin-top: 19px; }'); + RESUtils.addCSS('div#sr-header-area {position: fixed; z-index: 10000 !important; left: 0; right: 0; }'); + this.pinCommonElements(sm); + }, + pinUserBar: function() { + // Make the user bar at the top of the page a fixed element + this.userBarElement = document.getElementById('header-bottom-right'); + var thisHeight = $('#header-bottom-right').height(); + RESUtils.addCSS('#header-bottom-right:hover { opacity: 1 !important; }'); + RESUtils.addCSS('#header-bottom-right { height: '+parseInt(thisHeight+1, 10)+'px; }'); + // make the account switcher menu fixed + window.addEventListener('scroll', modules['betteReddit'].handleScroll, false); + this.pinCommonElements(); + }, + handleScroll: function(e) { + if (modules['betteReddit'].scrollTimer) clearTimeout(modules['betteReddit'].scrollTimer); + modules['betteReddit'].scrollTimer = setTimeout(modules['betteReddit'].handleScrollAfterTimer, 300); + }, + handleScrollAfterTimer: function(e) { + if (RESUtils.elementInViewport(modules['betteReddit'].userBarElement)) { + modules['betteReddit'].userBarElement.setAttribute('style',''); + if (typeof modules['accountSwitcher'].accountMenu !== 'undefined') { + $(modules['accountSwitcher'].accountMenu).attr('style','position: absolute;'); + } + } else if (modules['betteReddit'].options.pinHeader.value === 'subanduser') { + if (typeof modules['accountSwitcher'].accountMenu !== 'undefined') { + $(modules['accountSwitcher'].accountMenu).attr('style','position: fixed;'); + } + modules['betteReddit'].userBarElement.setAttribute('style','position: fixed; z-index: 10000 !important; top: 19px; right: 0; opacity: 0.6; -webkit-transition:opacity 0.3s ease-in; -moz-transition:opacity 0.3s ease-in; -o-transition:opacity 0.3s ease-in; -ms-transition:opacity 0.3s ease-in; -transition:opacity 0.3s ease-in;'); + } else { + if (typeof modules['accountSwitcher'].accountMenu !== 'undefined') { + $(modules['accountSwitcher'].accountMenu).attr('style','position: fixed;'); + } + modules['betteReddit'].userBarElement.setAttribute('style','position: fixed; z-index: 10000 !important; top: 0; right: 0; opacity: 0.6; -webkit-transition:opacity 0.3s ease-in; -moz-transition:opacity 0.3s ease-in; -o-transition:opacity 0.3s ease-in; -ms-transition:opacity 0.3s ease-in; -transition:opacity 0.3s ease-in;'); + } + }, + pinHeader: function() { + // Makes the Full header a fixed element + + // the subreddit manager code changes the document's structure + var sm = modules['subredditManager'].isEnabled(); + + var header = document.getElementById('header'); + if (header == null) return; // reddit is under heavy load + + // add a dummy
    to the document for spacing + var spacer = document.createElement('div'); + spacer.id = 'RESPinnedHeaderSpacer'; + + // without the next line, the subreddit manager would make the subreddit bar three lines tall and very narrow + RESUtils.addCSS('#sr-header-area {left: 0; right: 0;}'); + spacer.style.height = $('#header').outerHeight() + "px"; + + // insert the spacer + document.body.insertBefore(spacer, header.nextSibling); + + // make the header fixed + RESUtils.addCSS('#header, #RESAccountSwitcherDropdown {position:fixed;}'); + // RESUtils.addCSS('#header {left: 0; right: 0; box-shadow: 0 2px 2px #AAA;}'); + RESUtils.addCSS('#header {left: 0; right: 0; }'); + var headerHeight = $('#header').height() + 15; + RESUtils.addCSS('#RESNotifications { top: '+headerHeight+'px } '); + this.pinCommonElements(sm); + + // TODO Needs testing + // Sometimes this gets executed before the subreddit logo has finished loading. When that + // happens, the spacer gets created too short, so when the SR logo finally loads, the header + // grows and overlaps the top of the page, potentially obscuring the first link. This checks + // to see if the image is finished loading. If it is, then the spacer's height is set. Otherwise, + // it pauses, then loops. + // added a check that this element exists, so it doesn't error out RES. + if (document.getElementById('header-img') && (!document.getElementById('header-img').complete)) setTimeout(function(){ + if (document.getElementById('header-img').complete) + // null parameter is necessary for FF3.6 compatibility. + document.getElementById('RESPinnedHeaderSpacer').style.height = window.getComputedStyle(document.getElementById('header'), null).height; + else setTimeout(arguments.callee, 10); + }, 10); + }, + pinCommonElements: function(sm) { + // pin the elements common to both pinHeader() and pinSubredditBar() + if (sm) { + // RES's subreddit menu + RESUtils.addCSS('#RESSubredditGroupDropdown, #srList, #RESShortcutsAddFormContainer, #editShortcutDialog {position: fixed !important;}'); + } else { + RESUtils.addCSS('#sr-more-link: {position: fixed;}'); + } + }, + restoreSavedTab: function() { + var tabmenu = document.querySelector('#header .tabmenu'), + li = document.createElement('li'), + a = document.createElement('a'), + user = RESUtils.loggedInUser(); + a.textContent = 'saved'; + a.href = '/user/' + user + '/saved/'; + li.appendChild(a); + tabmenu.appendChild(li); + } +}; + +modules['singleClick'] = { + moduleID: 'singleClick', + moduleName: 'Single Click Opener', + category: 'UI', + options: { + openOrder: { + type: 'enum', + values: [ + { name: 'open comments then link', value: 'commentsfirst' }, + { name: 'open link then comments', value: 'linkfirst' } + ], + value: 'commentsfirst', + description: 'What order to open the link/comments in.' + }, + hideLEC: { + type: 'boolean', + value: false, + description: 'Hide the [l=c] when the link is the same as the comments page' + }, + openBackground: { + type: 'boolean', + value: false, + description: 'Open the [l+c] link in background tabs' + } + }, + description: 'Adds an [l+c] link that opens a link and the comments page in new tabs for you in one click.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[\?]*/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[-\w\._]*\//i + ], + exclude: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[-\w\._\/\?]*\/comments[-\w\._\/\?=]*/i + ], + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + RESUtils.addCSS('.redditSingleClick { color: #888; font-weight: bold; cursor: pointer; padding: 0 1px; }'); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // do stuff here! + this.applyLinks(); + // listen for new DOM nodes so that modules like autopager, river of reddit, etc still get l+c links... + RESUtils.watchForElement('siteTable', modules['singleClick'].applyLinks); + } + }, + applyLinks: function(ele) { + var ele = ele || document; + var entries = ele.querySelectorAll('#siteTable .entry, #siteTable_organic .entry'); + for (var i=0, len=entries.length; iSnudown, and therefore SnuOwnd, is a bit funny about how it generates its table of contents entries. + To when it encounters a header it tries to perform some of the usual inline formatting such as emphasis, strikethoughs, or superscript in headers. The text containing generated HTML then gets passed into cb_toc_header which escapes all of the passed HTML. When reddit gets it escaped tags are stripped. + + It would be nicer if they just used different functions for rendering the emphasis when making headers. + + It seems that my understanding was wrong, for some reason reddit doesn't even use snudown's TOC renderer. + */ + var doc = $('').html(body); + var header_ids = {}; + var headers = doc.find('h1, h2, h3, h4, h5, h6'); + var tocDiv = $('
    ').addClass('toc'); + var parent = $('
      '); + parent.data('level', 0); + tocDiv.append(parent); + var level = 0, previous = 0; + var prefix = 'wiki' + headers.each(function(i, e) { + var contents = $(this).text(); + var aid = $('
      ').html(contents).text(); + aid = prefix + '_' + aid.replace(/ /g, '_').toLowerCase(); + aid = aid.replace(/[^\w.-]/g, function(s) { + return '.' + s.charCodeAt(0).toString(16).toUpperCase(); + }); + if (!(aid in header_ids)) header_ids[aid] = 0; + var id_num = header_ids[aid] + 1; + header_ids[aid] += 1; + + if (id_num > 1) aid = aid + id_num; + + $(this).attr('id', aid); + + var li = $('
    • ').addClass(aid); + var a = $('').attr('href', '#'+aid).text(contents); + li.append(a); + + var thisLevel = +this.tagName.slice(-1); + if (previous && thisLevel > previous) { + var newUL = $('
        '); + newUL.data('level', thisLevel); + parent.append(newUL); + parent = newUL; + level++; + } else if (level && thisLevel < previous) { + while (level && parent.data('level') > thisLevel) { + parent = parent.closest('ul'); + level -= 1; + } + } + previous = thisLevel; + parent.append(li); + }); + doc.prepend(tocDiv); + return doc.html() + } else { + return body; + } + }, + makeBigEditorButton: function() { + return $(''); + }, + attachPreview: function(usertext) { + if (usertext == null) usertext = document.body; + if (modules['commentPreview'].options.enableBigEditor.value) { + modules['commentPreview'].makeBigEditorButton().prependTo($('.bottom-area:not(:has(.RESBigEditorPop))', usertext)); + } + $(usertext).find(".usertext-edit").each(function() { + var preview = $(this).find(".livePreview"); + if (preview.length === 0) { + preview = modules["commentPreview"].makePreviewBox(); + $(this).append(preview); + } + var contents = preview.find(".RESDialogContents"); + $(this).find("textarea[name=text], textarea[name=description], textarea[name=public_description]").bind("input", function() { + var textarea = $(this); + RESUtils.debounce('refreshPreview', 250, function() { + var markdownText = textarea.val(); + if (markdownText.length > 0) { + var html = modules["commentPreview"].markdownToHTML(markdownText); + preview.show(); + contents.html(html); + } else { + preview.hide(); + contents.html(""); + } + }); + }); + }); + }, + attachWikiPreview: function() { + if (modules['commentPreview'].options.enableBigEditor.value) { + modules['commentPreview'].makeBigEditorButton().insertAfter($('.pageactions')); + } + var preview = modules["commentPreview"].makePreviewBox(); + preview.find(".md").addClass("wiki"); + preview.insertAfter($("#editform > br").first()); + + var contents = preview.find(".RESDialogContents"); + $("#wiki_page_content").bind("input", function() { + + var textarea = $(this); + RESUtils.debounce('refreshPreview', 250, function() { + var markdownText = textarea.val(); + if (markdownText.length > 0) { + var html = modules["commentPreview"].markdownToHTML(markdownText) + preview.show(); + contents.html(html); + } else { + preview.hide(); + contents.html(""); + } + }); + }); + }, + makePreviewBox: function() { + return $("

        Live Preview

        "); + }, + addBigEditor: function() { + var editor = $('
        ').hide(); + var left = $('

        Editor

        '); + var contents = $('
        '); + var foot = $('
        '); + foot.append($('').bind('click', function() { + var len = $('#BigText').val().length; + var max = $('#BigText').attr("data-max-length"); + if (len > max) { + $('#BigEditor .errorList .error').hide().filter('.TOO_LONG').text('this is too long (max: '+max+')').show(); + } else if (len === 0) { + $('#BigEditor .errorList .error').hide().filter('.NO_TEXT').show(); + } else if (modules['commentPreview'].bigTextTarget) { + modules['commentPreview'].bigTextTarget.submit(); + modules['commentPreview'].bigTextTarget.parents('.usertext-edit:first').find('.livePreview .md').html(''); + modules.commentPreview.hideBigEditor(false, true); + } else { + $('#BigEditor .errorList .error').hide().filter('.NO_TARGET').show(); + } + + })); + foot.append($('').bind('click', modules.commentPreview.hideBigEditor)); + + foot.append($('\ + \ + \ + \ + ')); + + contents.append(foot); + left.append(contents); + + var right = $('

        Preview

        X
        \ +
        '); + editor.append(left).append(right); + + $(document.body).append(editor); + + $('.BERight .RESCloseButton.close').bind("click", modules.commentPreview.hideBigEditor); + $('.BERight .RESFadeButton').bind({ + click: function(e) { + $("#BigEditor").fadeTo(300, 0.3); + $(document.body).removeClass("RESScrollLock"); + this.isFaded = true; + }, + mouseout: function(e) { + if (this.isFaded) $("#BigEditor").fadeTo(300, 1.0); + $(document.body).addClass("RESScrollLock"); + this.isFaded = false; + } + }); + $('body').delegate('.RESBigEditorPop', 'click', modules.commentPreview.showBigEditor); + + $('#BigText').bind('input', function() { + RESUtils.debounce('refreshBigPreview', 250, function() { + var text = $('#BigText').val(); + var html = modules['commentPreview'].markdownToHTML(text); + $('#BigPreview').html(html); + if (modules['commentPreview'].bigTextTarget) { + modules['commentPreview'].bigTextTarget.val(text); + } + }); + }).bind("keydown", function(e) { + //Close big editor on escape + if (e.keyCode === modules["commentTools"].KEYS.ESCAPE) { + modules["commentPreview"].hideBigEditor(); + e.preventDefault(); + return false; + } + }); + if (modules['commentTools'].isEnabled()) { + contents.prepend(modules['commentTools'].makeEditBar()); + } + }, + showBigEditor: function(e) { + e.preventDefault(); + // modules.commentPreview.bigTextTarget = null; + modules.commentPreview.hideBigEditor(true); + $('.side').addClass('BESideHide'); + $('body').addClass('RESScrollLock'); + RESUtils.fadeElementIn(document.getElementById('BigEditor'), 0.3); + var baseText; + if (!modules['commentPreview'].isWiki) { + baseText = $(this).parents('.usertext-edit:first').find('textarea'); + $("#BigPreview").removeClass("wiki"); + $(".BERight .RESDialogContents").removeClass("wiki-page-content"); + } else { + baseText = $('#wiki_page_content'); + $("#BigPreview").addClass("wiki"); + $(".BERight .RESDialogContents").addClass("wiki-page-content"); + } + + var markdown = baseText.val(); + var maxLength = baseText.attr("data-max-length"); + $("#BigText").attr("data-max-length", maxLength).val(markdown).focus(); + modules['commentTools'].updateCounter($("#BigText")[0]); + $('#BigPreview').html(modules['commentPreview'].markdownToHTML(markdown)); + modules.commentPreview.bigTextTarget = baseText; + }, + hideBigEditor: function(quick, submitted) { + if (quick === true) { + $('#BigEditor').hide(); + } else { + RESUtils.fadeElementOut(document.getElementById('BigEditor'), 0.3); + } + $('.side').removeClass('BESideHide'); + $('body').removeClass('RESScrollLock'); + var target = modules['commentPreview'].bigTextTarget; + + if (target != null) { + target.val($('#BigText').val()) + target.focus(); + if (submitted !== true) { + var inputEvent = document.createEvent("HTMLEvents"); + inputEvent.initEvent("input", true, true); + target[0].dispatchEvent(inputEvent); + } + modules['commentPreview'].bigTextTarget = null; + } + }, +}; + + + +modules['commentTools'] = { + moduleID: 'commentTools', + moduleName: 'Comment Tools', + category: 'Comments', + options: { + commentingAs: { + type: 'boolean', + value: true, + description: 'Shows your currently logged in username to avoid posting from the wrong account.' + }, + userAutocomplete: { + type: 'boolean', + value: true, + description: 'Show user autocomplete tool when typing in posts, comments and replies' + }, + subredditAutocomplete: { + type: 'boolean', + value: true, + description: 'Show subreddit autocomplete tool when typing in posts, comments and replies' + }, + showInputLength: { + type: 'boolean', + value: true, + description: 'When submitting, display the number of characters entered in the title and text fields and indicate when you go over the 300 character limit for titles.' + }, + keyboardShortcuts: { + type: 'boolean', + value: true, + description: 'Use keyboard shortcuts to apply styles to selected text' + }, + macros: { + type: 'table', + addRowText: '+add shortcut', + fields: [ + { name: 'label', type: 'text' }, + { name: 'text', type: 'textarea' }, + { name: 'category', type: 'text' }, + { name: 'key', type: 'keycode' } + ], + value: [ + ], + description: "Add buttons to insert frequently used snippets of text." + }, + keepMacroListOpen: { + type: 'boolean', + value: false, + description: 'After selecting a macro from the dropdown list, do not hide the list.' + } + }, + description: 'Provides shortcuts for easier markdown.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]+\/comments\/?[-\w\.]*/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/comments\/[-\w\.]+/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/message\/[-\w\.]*\/?[-\w\.]*/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[-\w\.]*\/submit\/?/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/user\/[-\w\.\/]*\/?/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[\-\w\.]+\/about\/edit/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[\-\w\.]+\/wiki\/create(\/\w+)?/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/r\/[\-\w\.]+\/wiki\/edit(\/\w+)?/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/submit\/?/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + RESUtils.addCSS('.markdownEditor { white-space: nowrap; }'); + RESUtils.addCSS('.RESMacroWrappingSpan { white-space: normal; }'); + RESUtils.addCSS('.markdownEditor a { margin-right: 8px; text-decoration: none; font-size: 11px; }'); + RESUtils.addCSS('.markdownEditor .RESMacroDropdown {font-size: 10px; }'); + RESUtils.addCSS('.selectedItem { color: #fff; background-color: #5f99cf; }'); + // RESUtils.addCSS('.RESDialogSmall.livePreview { position: relative; width: auto; margin-bottom: 15px; }'); + // RESUtils.addCSS('.RESDialogSmall.livePreview .RESDialogContents h3 { font-weight: bold; }'); + RESUtils.addCSS('.RESMacroDropdownTitle, .RESMacroDropdownTitleOverlay { cursor: pointer; display: inline-block; font-size: 11px; text-decoration: underline; color: gray; padding-left: 2px; padding-right: 21px; background-image: url(http://www.redditstatic.com/droparrowgray.gif); background-position: 100% 50%; background-repeat: no-repeat; }'); + RESUtils.addCSS('.RESMacroDropdownTitleOverlay { cursor: pointer; }'); + RESUtils.addCSS('.RESMacroDropdownContainer { display: none; position: absolute; }'); + RESUtils.addCSS('.RESMacroDropdown { display: none; position: absolute; z-index: 2001; }'); + RESUtils.addCSS('.RESMacroDropdownList { margin-top: 0; width: auto; max-width: 300px; }'); + RESUtils.addCSS('.RESMacroDropdownList a, .RESMacroDropdownList li { font-size: 10px; }'); + RESUtils.addCSS('.RESMacroDropdown li { padding-right: 10px; height: 25px; line-height: 24px; }'); + } + }, + SUBMIT_LIMITS: { + STYLESHEET: 128*1024, + SIDEBAR: 5120, + DESCRIPTION: 500, + WIKI: 256*1024, + POST: 10000, + POST_TITLE: 300 + }, + //Moved this out of go() because the large commentPreview may need it. + macroCallbackTable: [], + macroKeyTable: [], + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + this.isWiki = $(document.body).is(".wiki-page"); + this.migrateData(); + $("body").delegate("li.viewSource a", { + click: function(e) { + e.preventDefault(); + modules["commentTools"].viewSource(this); + } + }).delegate(".usertext-edit.viewSource .cancel", { + click: function() { + $(this).parents(".usertext-edit.viewSource").hide(); + + } + }).delegate("div.markdownEditor a", { + click: function(e) { + e.preventDefault(); + + var index = $(this).attr("data-macro-index"); + var box = modules["commentTools"].findTextareaForElement(this)[0]; + // var box = $(this).closest(".usertext-edit, .RESDialogContents, .wiki-page-content").find("textarea[name=text], textarea[name=description], textarea[name=public_description]")[0]; + if (box == null) { + console.error("Failed to locate textarea."); + return; + } + var handler = modules["commentTools"].macroCallbackTable[index]; + if (box == null) { + console.error("Failed to locate find callback."); + return; + } + handler.call(modules["commentTools"], this, box); + + box.focus(); + //Fire an input event to refresh the preview + var inputEvent = document.createEvent("HTMLEvents"); + inputEvent.initEvent("input", true, true); + box.dispatchEvent(inputEvent); + } + }).delegate(".RESMacroDropdownTitle", { + click: function(e) { + var pos = $(this).position(); + $(this).next().css({ + top: (pos).top+"px", + left: (pos).left+"px" + }).show(); + } + }).delegate(".RESMacroDropdown", { + mouseleave: function(e) { + $(this).hide(); + } + }); + + if (this.options.showInputLength.value) { + + $("body").delegate(".usertext-edit textarea, #title-field textarea, #BigEditor textarea, #wiki_page_content", { + input: function(e){ + modules['commentTools'].updateCounter(this); + } + }); + } + + if (this.options.keyboardShortcuts.value) { + $("body").delegate(".usertext-edit textarea, #BigEditor textarea, #wiki_page_content", { + keydown: function(e) { + if (e.keyCode === modules["commentTools"].KEYS.ESCAPE) { + if (!modules["commentTools"].autoCompletePop.is(':visible')) { + // Blur from the editor on escape, so we can return to using the keyboard nav. + // NOTE: The big editor closes on ESC so this won't be reached in that case. + $(this).blur(); + e.preventDefault(); + return false; + } else { + return true; + } + } + + for (var i = 0; i < modules["commentTools"].macroKeyTable.length; i++) { + var row = modules["commentTools"].macroKeyTable[i]; + var testedKeyArray = row[0], macroIndex = row[1]; + if (checkKeysForEvent(e, testedKeyArray)) { + var handler = modules["commentTools"].macroCallbackTable[macroIndex]; + handler.call(modules["commentTools"], null, this); + //Fire an input event to refresh the preview + var inputEvent = document.createEvent("HTMLEvents"); + inputEvent.initEvent("input", true, true); + this.dispatchEvent(inputEvent); + e.preventDefault(); + return false; + } + }; + } + }); + } + if (this.options.subredditAutocomplete.value || this.options.userAutocomplete.value) { + this.addAutoCompletePop(); + } + + //Perform initial setup of tools over the whole page + this.attachCommentTools(); + this.attatchViewSourceButtons() + /* + //These are no longer necessary but I am saving them in case Reddit changes how they make their reply forms. + // Wireup reply editors + RESUtils.watchForElement("newCommentsForms", modules["commentTools"].attachCommentTools); + // Wireup edit editors (usertext-edit already exists in the page) + RESUtils.watchForElement("newComments", modules["commentTools"].attachCommentTools); + */ + RESUtils.watchForElement("newComments", modules["commentTools"].attatchViewSourceButtons); + } + }, + migrateData: function() { + var LATEST_MACRO_DATA_VERSION = "2"; + var macroVersion = RESStorage.getItem("RESmodules.commentTools.macroDataVersion"); + if (macroVersion == null || macroVersion === "0") { + //In this case it is unmigrated or uncreated + var previewOptionString = RESStorage.getItem("RESoptions.commentPreview"); + var previewOptions = safeJSON.parse(previewOptionString, "commentPreview"); + if (previewOptions != null) { + if (typeof previewOptions.commentingAs !== "undefined") { + this.options.commentingAs.value = previewOptions.commentingAs.value + delete previewOptions.commentingAs; + } + if (typeof previewOptions.keyboardShortcuts !== "undefined") { + this.options.keyboardShortcuts.value = previewOptions.keyboardShortcuts.value + delete previewOptions.keyboardShortcuts; + } + if (typeof previewOptions.subredditAutocomplete !== "undefined") { + this.options.subredditAutocomplete.value = previewOptions.subredditAutocomplete.value + delete previewOptions.subredditAutocomplete; + } + if (typeof previewOptions.macros !== "undefined") { + var macros; + macros = this.options.macros.value = previewOptions.macros.value + for (var i = 0; i < macros.length; i++) { + while (macros[i].length < 4) macros[i].push(""); + } + delete previewOptions.macros; + } + RESStorage.setItem("RESoptions.commentTools", JSON.stringify(this.options)); + RESStorage.setItem("RESoptions.commentPreview", JSON.stringify(previewOptions)); + RESStorage.setItem("RESmodules.commentTools.macroDataVersion", LATEST_MACRO_DATA_VERSION); + } else { + //No migration will be performed + RESStorage.setItem("RESmodules.commentTools.macroDataVersion", LATEST_MACRO_DATA_VERSION); + } + } if (macroVersion === "1") { + var macros = this.options.macros.value; + for (var i = 0; i < macros.length; i++) { + while (macros[i].length < 4) macros[i].push(""); + } + RESStorage.setItem("RESmodules.commentTools.macroDataVersion", LATEST_MACRO_DATA_VERSION); + } + }, + attatchViewSourceButtons: function(entry) { + var entries = entry == null ? $(".entry", document.body) : $(entry); + if (RESUtils.pageType() === "comments" || RESUtils.pageType() === "inbox") { + //Disabled syncronous version + // $(".flat-list.buttons", entries).find("li:nth-child(2), li:only-child").after('
      • source
      • '); + var menus = $(".flat-list.buttons li:first-child", entries); + RESUtils.forEachChunked(menus, 30, 500, function(item, i, array) { + $(item).after('
      • source
      • '); + }); + } + }, + viewSource: function(button) { + var buttonList = $(button).parent().parent(); + var sourceDiv = $(button).closest('.thing').find(".usertext-edit.viewSource:first"); + if (sourceDiv.length !== 0) { + sourceDiv.toggle(); + } else { + var permaLink = buttonList.find(".first a"); + var jsonURL = permaLink.attr("href"); + var urlSplit = jsonURL.split('/'); + var postID = urlSplit[urlSplit.length - 1]; + + var isSelfText = permaLink.is(".comments"); + if (jsonURL.indexOf('?context') !== -1) { + jsonURL = jsonURL.replace('?context=3','.json?'); + } else { + jsonURL += '/.json'; + } + this.gettingSource = this.gettingSource || {}; + if (this.gettingSource[postID]) return; + this.gettingSource[postID] = true; + + GM_xmlhttpRequest({ + method: "GET", + url: jsonURL, + onload: function(response) { + var thisResponse = JSON.parse(response.responseText); + var userTextForm = $('
        '); + if (!isSelfText) { + var sourceText = null; + if (typeof thisResponse[1] !== 'undefined') { + sourceText = thisResponse[1].data.children[0].data.body; + } else { + var thisData = thisResponse.data.children[0].data; + if (thisData.id == postID) { + sourceText = thisData.body; + } else { + // The message we want is a reply to a PM/modmail, but reddit returns the whole thread. + // So, we have to dig into the replies to find the message we want. + for (var i=0, len=thisData.replies.data.children.length; i limit) { + counter.addClass('tooLong'); + } else { + counter.removeClass('tooLong'); + } + }, + makeEditBar: function() { + if (this.cachedEditBar != null) { + return $(this.cachedEditBar).clone(); + } + + var editBar = $('
        '); + + editBar.append(this.makeEditButton("Bold", "ctrl-b", [66, false, true, false, false], function(button, box) { + this.wrapSelection(box, "**", "**"); + })); + editBar.append(this.makeEditButton("Italic", "ctrl-i", [73, false, true, false, false], function(button, box) { + this.wrapSelection(box, "*", "*"); + })); + editBar.append(this.makeEditButton("strike", "ctrl-s", 83, function(button, box) { + this.wrapSelection(box, "~~", "~~"); + })); + editBar.append(this.makeEditButton("sup", "", null, function(button, box) { + this.wrapSelectedWords(box, "^"); + })); + editBar.append(this.makeEditButton("Link", "", null, function(button, box) { + this.linkSelection(box); + })); + editBar.append(this.makeEditButton(">Quote", "", null, function(button, box) { + this.wrapSelectedLines(box, "> ", ""); + })); + editBar.append(this.makeEditButton("Code", "", null, function(button, box) { + this.wrapSelectedLines(box, " ", ""); + })); + editBar.append(this.makeEditButton("•Bullets", "", null, function(button, box) { + this.wrapSelectedLines(box, "* ", ""); + })); + editBar.append(this.makeEditButton("1.Numbers", "", null, function(button, box) { + this.wrapSelectedLines(box, "1. ", ""); + })); + + if (modules["commentTools"].options.showInputLength.value) { + var counter = $('0/?????'); + editBar.append(counter); + } + + this.addButtonToMacroGroup("", this.makeEditButton("reddiquette", "", null, function(button, box){ + var clickCount = $(button).data("clickCount") || 0; + clickCount++; + $(button).data("clickCount", clickCount); + if (clickCount > 2) { + $(button).hide(); + } + this.macroSelection(box, "[reddiquette](http://www.reddit.com/help/reddiquette) ", ""); + })); + + this.addButtonToMacroGroup("", this.makeEditButton("[Promote]", "", null, function(button, box){ + var clickCount = $(button).data("clickCount") || 0; + clickCount++; + $(button).data("clickCount", clickCount); + if (clickCount > 2) { + $(button).hide(); + modules["commentTools"].lod(); + } + this.macroSelection(box, "[Reddit Enhancement Suite](http://redditenhancementsuite.com) "); + })); + + this.addButtonToMacroGroup("", this.makeEditButton("ಠ\_ಠ", "Look of disaproval", null, function(button, box) { + this.macroSelection(box, "ಠ\_ಠ"); + })); + this.buildMacroDropdowns(editBar); + var addMacroButton = modules['commentTools'].makeEditButton(modules['commentTools'].options.macros.addRowText, null, null, function() { + modules['settingsNavigation'].loadSettingsPage(this.moduleID, 'macros'); + $('.RESMacroDropdown').fadeOut(100); + }); + modules['commentTools'].addButtonToMacroGroup('', addMacroButton); + + + + + //Wrap the edit bar in a
        of its own + var wrappedEditBar = $("
        ").append(editBar); + if (this.options.commentingAs.value && (!modules['usernameHider'].isEnabled())) { + // show who we're commenting as... + var commentingAs = $('
        ').text('Commenting as: ' + RESUtils.loggedInUser()); + wrappedEditBar.append(commentingAs); + + } + this.cachedEditBar = wrappedEditBar; + return this.cachedEditBar; + }, + macroDropDownTable: {}, + getMacroGroup: function(groupName) { + //Normalize and supply a default group name{} + groupName = (groupName||"").toString().trim() || "macros"; + var macroGroup; + if (groupName in this.macroDropDownTable) { + macroGroup = this.macroDropDownTable[groupName]; + } else { + macroGroup = this.macroDropDownTable[groupName] = {}; + macroGroup.titleButton = $(''+groupName+''); + macroGroup.container = $(''+groupName+'').hide(); + macroGroup.dropdown = $('
          '); + macroGroup.container.append(macroGroup.dropdown); + } + return macroGroup; + }, + addButtonToMacroGroup: function(groupName, button) { + var group = this.getMacroGroup(groupName); + group.dropdown.append($("
        • ").append(button)); + }, + buildMacroDropdowns: function(editBar) { + var macros = this.options.macros.value; + + for (var i = 0; i < macros.length; i++) { + var macro = macros[i]; + + //Confound these scoping rules + (function(title, text, category, key) { + var button = this.makeEditButton(title, null, key, function(button, box) { + this.macroSelection(box, text, ""); + }); + this.addButtonToMacroGroup(category, button); + }).apply(this, macro); + } + + + var macroWrapper = $(''); + if ("macros" in this.macroDropDownTable) { + macroWrapper.append(this.macroDropDownTable["macros"].titleButton); + macroWrapper.append(this.macroDropDownTable["macros"].container); + } + for (var category in this.macroDropDownTable) { + if (category === "macros") continue; + macroWrapper.append(this.macroDropDownTable[category].titleButton); + macroWrapper.append(this.macroDropDownTable[category].container); + } + editBar.append(macroWrapper); + }, + makeEditButton: function(label, title, key, handler) { + if (label == null) label = "unlabeled"; + if (title == null) title = ""; + var macroButtonIndex = this.macroCallbackTable.length; + var button = $("").html(label).attr({ + title: title, + href: "javascript:void(0)", + tabindex: 1, + "data-macro-index": macroButtonIndex + }); + + if (key != null && key[0] != null) { + this.macroKeyTable.push([key, macroButtonIndex]); + } + this.macroCallbackTable[macroButtonIndex] = handler; + return button; + }, + linkSelection: function(box) { + var url = prompt("Enter the URL:", ""); + if (url != null) { + //escape parens in url + url = url.replace(/\(/, "\\("); + url = url.replace( /\)/, "\\)"); + this.wrapSelection(box, "[", "](" + url + ")", function(text) { + //escape brackets and parens in text + text = text.replace(/\[/, "\\["); + text = text.replace(/\]/, "\\]"); + text = text.replace(/\(/, "\\("); + text = text.replace(/\)/, "\\)"); + return text; + }); + } + }, + macroSelection: function(box, macroText) { + if (!this.options.keepMacroListOpen.value) $('.RESMacroDropdown').fadeOut(100); + this.wrapSelection(box, macroText, ""); + }, + wrapSelection: function(box, prefix, suffix, escapeFunction) { + if (box == null) return; + //record scroll top to restore it later. + var scrollTop = box.scrollTop; + + //We will restore the selection later, so record the current selection. + var selectionStart = box.selectionStart; + var selectionEnd = box.selectionEnd; + + var text = box.value; + var beforeSelection = text.substring(0, selectionStart); + var selectedText = text.substring(selectionStart, selectionEnd); + var afterSelection = text.substring(selectionEnd); + + //Markdown doesn't like it when you tag a word like **this **. The space messes it up. So we'll account for that because Firefox selects the word, and the followign space when you double click a word. + var trailingSpace = ""; + var cursor = selectedText.length - 1; + while (cursor > 0 && selectedText[cursor] === " ") { + trailingSpace += " "; + cursor--; + } + selectedText = selectedText.substring(0, cursor+1); + + if (escapeFunction != null) { + selectedText = escapeFunction(selectedText); + } + + box.value = beforeSelection + prefix + selectedText + suffix + trailingSpace + afterSelection; + + box.selectionStart = selectionStart + prefix.length; + box.selectionEnd = selectionEnd + prefix.length; + + box.scrollTop = scrollTop; + }, + wrapSelectedLines: function(box, prefix, suffix) { + var scrollTop = box.scrollTop; + var selectionStart = box.selectionStart; + var selectionEnd = box.selectionEnd; + + var text = box.value; + var startPosition = 0; + var lines = text.split("\n"); + for (var i = 0; i < lines.length; i++) { + var lineStart = startPosition; + var lineEnd = lineStart + lines[i].length; + //Check if either end of the line is within the selection + if (selectionStart <= lineStart && lineStart <= selectionEnd + || selectionStart <= lineEnd && lineEnd <= selectionEnd + //Check if either end of the selection is within the line + || lineStart <= selectionStart && selectionStart <= lineEnd + || lineStart <= selectionEnd && selectionEnd <= lineEnd) { + lines[i] = prefix + lines[i] + suffix; + //Move the offsets separately so we don't throw off detection for the other end + var startMovement = 0, endMovement = 0; + if (lineStart < selectionStart) startMovement += prefix.length; + if (lineEnd < selectionStart) startMovement += suffix.length; + if (lineStart < selectionEnd) endMovement += prefix.length; + if (lineEnd < selectionEnd) endMovement += suffix.length; + + selectionStart += startMovement; + selectionEnd += endMovement; + lineStart += prefix.length; + lineEnd += prefix.length + suffix.length; + } + //Remember the newline + startPosition = lineEnd + 1; + } + + box.value = lines.join("\n"); + box.selectionStart = selectionStart; + box.selectionEnd = selectionEnd; + box.scrollTop = scrollTop; + }, + wrapSelectedWords: function(box, prefix) { + var scrollTop = box.scrollTop; + var selectionStart = box.selectionStart; + var selectionEnd = box.selectionEnd; + + var text = box.value; + var beforeSelection = text.substring(0, selectionStart); + var selectedWords = text.substring(selectionStart, selectionEnd).split(" "); + var afterSelection = text.substring(selectionEnd); + + var selectionModify = 0; + + for (i = 0; i < selectedWords.length; i++) { + if (selectedWords[i] !== "") { + if (selectedWords[i].indexOf("\n") !== -1) + { + newLinePosition = selectedWords[i].lastIndexOf("\n") + 1; + selectedWords[i] = selectedWords[i].substring(0, newLinePosition) + prefix + selectedWords[i].substring(newLinePosition); + selectionModify += prefix.length; + } + if (selectedWords[i].charAt(0) !== "\n") { + selectedWords[i] = prefix + selectedWords[i]; + } + selectionModify += prefix.length; + } + // If nothing is selected, stick the prefix in there and move the cursor to the right side. + else if (selectedWords[i] === "" && selectedWords.length === 1) { + selectedWords[i] = prefix + selectedWords[i]; + selectionModify += prefix.length; + selectionStart += prefix.length; + } + } + + box.value = beforeSelection + selectedWords.join(" ") + afterSelection; + box.selectionStart = selectionStart; + box.selectionEnd = selectionEnd + selectionModify; + box.scrollTop = scrollTop; + }, + lod: function() { + if (typeof this.firstlod === 'undefined') { + this.firstlod = true; + $('body').append(''); + } + $('#RESlod').fadeIn('slow', function() { + setTimeout(function() { + $('#RESlod').fadeOut('slow'); + }, 5000); + }); + }, + KEYS: { + BACKSPACE: 8, TAB: 9, ENTER: 13, + ESCAPE: 27, SPACE: 32, + PAGE_UP: 33, PAGE_DOWN: 34, + END: 35, HOME: 36, + LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, + NUMPAD_ENTER: 108, COMMA: 188 + }, + addAutoCompletePop: function() { + + this.autoCompleteCache = {}; + this.autoCompletePop = $('
        • You have not yet saved any comments.
        • '); + } + insertAfter(this.savedLinksContent, this.savedCommentsContent); + }, + unsaveComment: function(id, unsaveLink) { + /* + var newStoredComments = []; + for (var i=0, len=this.storedComments.length;i\ + submitter\ + friend\ + moderator\ + admin\ +
          '); + $(document.body).append(dummy); + this.colorTable = { + 'submitter': { + default: RESUtils.getComputedStyle('#dummy .author.submitter', 'color'), + color: this.options.OPColor.value, + hoverColor: this.options.OPColorHover.value + }, + 'friend': { + default: RESUtils.getComputedStyle('#dummy .author.friend', 'color'), + color: this.options.friendColor.value, + hoverColor: this.options.friendColorHover.value + }, + 'moderator': { + default: RESUtils.getComputedStyle('#dummy .author.moderator', 'color'), + color: this.options.modColor.value, + hoverColor: this.options.modColorHover.value + }, + 'admin': { + default: RESUtils.getComputedStyle('#dummy .author.admin', 'color'), + color: this.options.adminColor.value, + hoverColor: this.options.adminColorHover.value + }, + 'user': { + default: '#5544CC', + color: modules['userTagger'].options['highlightColor'].value, + hoverColor: modules['userTagger'].options['highlightColorHover'].value, + }, + 'firstComment': { + default: '#46B6CC', + color: this.options.firstCommentColor.value, + hoverColor: this.options.firstCommentColorHover.value + } + }; + $('#dummy').detach(); + }, + scanPageForFirstComments: function (ele) { + var comments = ele + ? $(ele).closest('.commentarea > .sitetable > .thing') + : document.body.querySelectorAll('.commentarea > .sitetable > .thing'); + + + RESUtils.forEachChunked(comments, 15, 1000, function(element, i, array) { + // Get identifiers + var idClass; + for (var i = 0, length = element.classList.length; i < length; i++) { + idClass = element.classList[i]; + if (idClass.substring(0, 6) === 'id-t1_') break; + } + + if (modules['userHighlight'].firstComments[idClass]) return; + + var author = element.querySelector('.author'); + if (!author) return; + var authorClass; + for (var i = 0, length = author.classList.length; i < length; i++) { + authorClass = author.classList[i]; + if (authorClass.substring(0, 6) === 'id-t2_') break; + } + + var authorDidReply = element.querySelector('.child .' + authorClass); + if (!authorDidReply) return; + + modules['userHighlight'].firstComments[idClass] = true; + modules['userHighlight'].doHighlight('firstComment', authorClass, '.' + idClass); + }); + }, + firstComments: {}, + scanPageForNewUsernames: function (ele) { + ele = ele || document.body; + var authors = ele.querySelectorAll('.author'); + RESUtils.forEachChunked(authors, 15, 1000, function(element, i, array) { + // Get identifiers + var idClass; + for (var i = 0, length = element.classList.length; i < length; i++) { + idClass = element.classList[i]; + if (idClass.substring(0, 6) === 'id-t2_') break; + } + + if (modules['userHighlight'].coloredUsernames[idClass]) return; + + // Choose color + var hash = 5381, str = idClass; + for (var i = 0; i < str.length; i++) { + hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */ + } + + var r = (hash & 0xFF0000) >> 16; + var g = (hash & 0x00FF00) >> 8; + var b = hash & 0x0000FF; + var color = "rgb(" + [ r, g, b ].join(',') + ")"; + + // Apply color + modules['userHighlight'].doTextColor('.' + idClass, color); + }); + }, + coloredUsernames: {}, + highlightUser: function (username) { + var name = 'author[href$="/' + username + '"]'; // yucky, but it'll do + return this.doHighlight('user', name); + }, + doHighlight: function(name, selector, container) { + if (selector == undefined) { + selector = name; + } + if (container == undefined) { + container = '' + } + var color, hoverColor; + var color = this.colorTable[name].color; + if (color === 'default') this.colorTable[name].default; + + var hoverColor = this.colorTable[name].hoverColor; + if (hoverColor === 'default') hoverColor = '#aaa'; + var css = '\ + ' + container + ' .author.' + selector + ' { \ + color: ' + this.options.fontColor.value + ' !important; \ + font-weight: bold; \ + padding: 0 2px 0 2px; \ + border-radius: 3px; \ + background-color:' + color + ' !important; \ + } \ + ' + container + ' .collapsed .author.' + selector + ' { \ + color: white !important; \ + background-color: #AAA !important; \ + } \ + ' + container + ' .author.' + selector + ':hover {\ + background-color: ' + hoverColor + ' !important; \ + text-decoration: none !important; \ + }'; + return RESUtils.addCSS(css); + }, + doTextColor: function (selector, color) { + var css = ' \ + .tagline .author' + selector + ' { \ + color: ' + color + ' !important; \ + } \ + '; + return RESUtils.addCSS(css); + } +}; + +modules['styleTweaks'] = { + moduleID: 'styleTweaks', + moduleName: 'Style Tweaks', + category: 'UI', + description: 'Provides a number of style tweaks to the Reddit interface', + options: { + navTop: { + type: 'boolean', + value: true, + description: 'Moves the username navbar to the top (great on netbooks!)' + }, + commentBoxes: { + type: 'boolean', + value: true, + description: 'Highlights comment boxes for easier reading / placefinding in large threads.' + }, + /* REMOVED for performance reasons... + commentBoxShadows: { + type: 'boolean', + value: false, + description: 'Drop shadows on comment boxes (turn off for faster performance)' + }, + */ + commentRounded: { + type: 'boolean', + value: true, + description: 'Round corners of comment boxes' + }, + commentHoverBorder: { + type: 'boolean', + value: false, + description: 'Highlight comment box hierarchy on hover (turn off for faster performance)' + }, + commentIndent: { + type: 'text', + value: 10, + description: 'Indent comments by [x] pixels (only enter the number, no \'px\')' + }, + continuity: { + type: 'boolean', + value: false, + description: 'Show comment continuity lines' + }, + lightSwitch: { + type: 'boolean', + value: true, + description: 'Enable lightswitch (toggle between light / dark reddit)' + }, + lightOrDark: { + type: 'enum', + values: [ + { name: 'Light', value: 'light' }, + { name: 'Dark', value: 'dark' } + ], + value: 'light', + description: 'Light, or dark?' + }, + visitedStyle: { + type: 'boolean', + value: false, + description: 'Reddit makes it so no links on comment pages appear as "visited" - including user profiles. This option undoes that.' + }, + showExpandos: { + type: 'boolean', + value: true, + description: 'Bring back video and text expando buttons for users with compressed link display' + }, + hideUnvotable: { + type: 'boolean', + value: false, + description: 'Hide vote arrows on threads where you cannot vote (e.g. archived due to age)' + }, + colorBlindFriendly: { + type: 'boolean', + value: false, + description: 'Use colorblind friendly styles when possible' + }, + scrollSubredditDropdown: { + type: 'boolean', + value: true, + description: 'Scroll the standard subreddit dropdown (useful for pinned header and disabled Subreddit Manager)' + } + }, + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + if (RESUtils.currentSubreddit()) { + this.curSubReddit = RESUtils.currentSubreddit().toLowerCase(); + } + + this.styleCBName = RESUtils.randomHash(); + RESUtils.addCSS('body.res .side .spacer .titlebox div #'+this.styleCBName+':before { display: none !important; }'); + RESUtils.addCSS('body.res .side .spacer .titlebox div #label-'+this.styleCBName+':before { display: none !important; }'); + RESUtils.addCSS('body.res .side .spacer .titlebox div #'+this.styleCBName+':after { display: none !important; }'); + RESUtils.addCSS('body.res .side .spacer .titlebox div #label-'+this.styleCBName+':after { display: none !important; }'); + + // In firefox, we need to style tweet expandos because they can't take advantage of twitter.com's widget.js + if (BrowserDetect.isFirefox()) { + RESUtils.addCSS('.res blockquote.twitter-tweet { padding: 15px; border-left: 5px solid #ccc; font-size: 14px; line-height: 20px; }'); + RESUtils.addCSS('.res blockquote.twitter-tweet p { margin-bottom: 15px; }'); + } + + if (this.options.colorBlindFriendly.value) { + document.html.classList.add('res-colorblind'); + } + // if night mode is enabled, set a localstorage token so that in the future, + // we can add the res-nightmode class to the page prior to page load. + if (this.options.lightOrDark.value === 'dark') { + localStorage.setItem('RES_nightMode', true); + } + + // wow, Reddit doesn't define a visited class for any links on comments pages... + // let's put that back if users want it back. + // If not, we still need a visited class for links in comments, like imgur photos for example, or inline image viewer can't make them look different when expanded! + if (this.options.visitedStyle.value) { + RESUtils.addCSS(".comment a:visited { color:#551a8b }"); + } else { + RESUtils.addCSS(".comment .md p > a:visited { color:#551a8b }"); + } + if (this.options.showExpandos.value) { + RESUtils.addCSS('.compressed .expando-button { display: block !important; }'); + } + if ((this.options.commentBoxes.value) && (RESUtils.pageType() === 'comments')) { + this.commentBoxes(); + } + if (this.options.hideUnvotable.value) { + RESUtils.addCSS('.unvoted .arrow[onclick*=unvotable] { visibility: hidden }'); + RESUtils.addCSS('.voted .arrow[onclick*=unvotable] { cursor: normal; }'); + } + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + + // get the head ASAP! + this.head = document.getElementsByTagName("head")[0]; + + // handle night mode scenarios (check if subreddit is compatible, etc) + this.handleNightModeAtStart(); + + // get rid of antequated option we've removed (err, renamed) due to performance issues. + if (typeof this.options.commentBoxHover !== 'undefined') { + delete this.options.commentBoxHover; + RESStorage.setItem('RESoptions.styleTweaks', JSON.stringify(modules['styleTweaks'].options)); + } + if (this.options.lightOrDark.value === 'dark') { + // still add .res-nightmode to body just in case subreddit stylesheets specified body.res-nightmode instead of just .res-nightmode + document.body.classList.add('res-nightmode'); + } + if (this.options.navTop.value) { + this.navTop(); + } + if (this.options.lightSwitch.value) { + this.lightSwitch(); + } + if (this.options.colorBlindFriendly.value) { + var orangered = document.body.querySelector('#mail'); + if ((orangered) && (orangered.classList.contains('havemail'))) { + orangered.setAttribute('style','background-image: url(http://thumbs.reddit.com/t5_2s10b_5.png); background-position: 0 0;'); + } + } + if (this.options.scrollSubredditDropdown.value) { + var calcedHeight = Math.floor(window.innerHeight * 0.95); + if( $('.drop-choices.srdrop').height() > calcedHeight ) { + RESUtils.addCSS('.drop-choices.srdrop { \ + overflow-y:scroll; \ + max-height:' + calcedHeight + 'px; \ + }'); + } + } + if (this.options.showExpandos.value) { + RESUtils.addCSS('.compressed .expando-button { display: block !important; }'); + var twitterLinks = document.body.querySelectorAll('.entry > p.title > a.title'); + var isTwitterLink = /twitter.com\/(?:#!\/)?([\w]+)\/(status|statuses)\/([\d]+)/i; + for (var i=0, len = twitterLinks.length; i
          ' + escapeHTML(tweet.user.screen_name) + ': ' + escapeHTML(tweet.text) + '
          '); + thisExpando.style.display = 'block'; + } + }); + } + } + } else { + $(e.target).removeClass('expanded').addClass('collapsedExpando').addClass('collapsed'); + thisExpando.style.display = 'none'; + } + + }, + navTop: function() { + RESUtils.addCSS('#header-bottom-right { top: 19px; border-radius: 0 0 0 3px; bottom: auto; }'); + RESUtils.addCSS('.beta-notice { top: 48px; }'); + $('body, #header-bottom-right').addClass('res-navTop'); + }, + userbarHider: function() { + RESUtils.addCSS("#userbarToggle { min-height: 22px; position: absolute; top: auto; bottom: 0; left: -5px; width: 16px; padding-right: 3px; height: 100%; font-size: 15px; border-radius: 4px 0; color: #a1bcd6; display: inline-block; background-color: #dfecf9; border-right: 1px solid #cee3f8; cursor: pointer; text-align: right; line-height: 24px; }"); + RESUtils.addCSS("#userbarToggle.userbarShow { min-height: 26px; }"); + RESUtils.addCSS("#header-bottom-right .user { margin-left: 16px; }"); + // RESUtils.addCSS(".userbarHide { background-position: 0 -137px; }"); + RESUtils.addCSS("#userbarToggle.userbarShow { left: -12px; }"); + RESUtils.addCSS(".res-navTop #userbarToggle.userbarShow { top: 0; bottom: auto; }"); + this.userbar = document.getElementById('header-bottom-right'); + if (this.userbar) { + this.userbarToggle = createElementWithID('div','userbarToggle'); + $(this.userbarToggle).html('»'); + this.userbarToggle.setAttribute('title','Toggle Userbar'); + this.userbarToggle.classList.add('userbarHide'); + this.userbarToggle.addEventListener('click', function(e) { + modules['styleTweaks'].toggleUserBar(); + }, false); + this.userbar.insertBefore(this.userbarToggle, this.userbar.firstChild); + // var currHeight = $(this.userbar).height(); + // $(this.userbarToggle).css('height',currHeight+'px'); + if (RESStorage.getItem('RESmodules.styleTweaks.userbarState') === 'hidden') { + this.toggleUserBar(); + } + } + }, + toggleUserBar: function() { + var nextEle = this.userbarToggle.nextSibling; + // hide userbar. + if (this.userbarToggle.classList.contains('userbarHide')) { + this.userbarToggle.classList.remove('userbarHide'); + this.userbarToggle.classList.add('userbarShow'); + $(this.userbarToggle).html('«'); + RESStorage.setItem('RESmodules.styleTweaks.userbarState', 'hidden'); + modules['accountSwitcher'].closeAccountMenu(); + while ((typeof nextEle !== 'undefined') && (nextEle !== null)) { + nextEle.style.display = 'none'; + nextEle = nextEle.nextSibling; + } + // show userbar. + } else { + this.userbarToggle.classList.remove('userbarShow'); + this.userbarToggle.classList.add('userbarHide'); + $(this.userbarToggle).html('»'); + RESStorage.setItem('RESmodules.styleTweaks.userbarState', 'visible'); + while ((typeof nextEle !== 'undefined') && (nextEle !== null)) { + if ((/mail/.test(nextEle.className)) || (nextEle.id === 'openRESPrefs')) { + nextEle.style.display = 'inline-block'; + } else { + nextEle.style.display = 'inline'; + } + nextEle = nextEle.nextSibling; + } + } + }, + commentBoxes: function() { + document.html.classList.add('res-commentBoxes'); + if (this.options.commentRounded.value) { + document.html.classList.add('res-commentBoxes-rounded'); + } + if (this.options.continuity.value) { + document.html.classList.add('res-continuity'); + } + if (this.options.commentHoverBorder.value) { + document.html.classList.add('res-commentHoverBorder'); + } + if (this.options.commentIndent.value) { + // this should override the default of 10px in commentboxes.css because it's added later. + RESUtils.addCSS('.res-commentBoxes .comment { margin-left:'+this.options.commentIndent.value+'px !important; }'); + } + }, + lightSwitch: function() { + RESUtils.addCSS(".lightOn { background-position: 0 -96px; } "); + RESUtils.addCSS(".lightOff { background-position: 0 -108px; } "); + var thisFrag = document.createDocumentFragment(); + this.lightSwitch = document.createElement('li'); + this.lightSwitch.setAttribute('title',"Toggle night and day"); + this.lightSwitch.addEventListener('click', function(e) { + e.preventDefault(); + if (modules['styleTweaks'].isDark == true) { + RESUtils.setOption('styleTweaks','lightOrDark','light'); + modules['styleTweaks'].lightSwitchToggle.classList.remove('enabled'); + modules['styleTweaks'].redditDark(true); + } else { + RESUtils.setOption('styleTweaks','lightOrDark','dark'); + modules['styleTweaks'].lightSwitchToggle.classList.add('enabled'); + modules['styleTweaks'].redditDark(); + } + }, true); + // this.lightSwitch.setAttribute('id','lightSwitch'); + this.lightSwitch.textContent = 'night mode'; + this.lightSwitchToggle = createElementWithID('div','lightSwitchToggle','toggleButton'); + $(this.lightSwitchToggle).html('onoff'); + this.lightSwitch.appendChild(this.lightSwitchToggle); + (this.options.lightOrDark.value === 'dark') ? this.lightSwitchToggle.classList.add('enabled') : this.lightSwitchToggle.classList.remove('enabled'); + // thisFrag.appendChild(separator); + thisFrag.appendChild(this.lightSwitch); + // if (RESConsole.RESPrefsLink) insertAfter(RESConsole.RESPrefsLink, thisFrag); + $('#RESDropdownOptions').append(this.lightSwitch); + }, + subredditStyles: function() { + if (! RESUtils.currentSubreddit()) return; + this.ignoredSubReddits = []; + var getIgnored = RESStorage.getItem('RESmodules.styleTweaks.ignoredSubredditStyles'); + if (getIgnored) { + this.ignoredSubReddits = safeJSON.parse(getIgnored, 'RESmodules.styleTweaks.ignoredSubredditStyles'); + } + var subredditTitle = document.querySelector('.titlebox h1'); + this.styleToggleContainer = document.createElement('div'); + this.styleToggleLabel = document.createElement('label'); + this.styleToggleCheckbox = document.createElement('input'); + this.styleToggleCheckbox.setAttribute('type','checkbox'); + this.styleToggleCheckbox.setAttribute('id',this.styleCBName); + this.styleToggleCheckbox.setAttribute('name',this.styleCBName); + + // are we blacklisting, or whitelisting subreddits? If we're in night mode on a sub that's + // incompatible with it, we want to check the whitelist. Otherwise, check the blacklist. + + if ((this.curSubReddit !== null) && (subredditTitle !== null)) { + + if (this.isDark && !this.isNightmodeCompatible) { + var idx = this.nightModeWhitelist.indexOf(this.curSubReddit); + if (idx !== -1) { + this.styleToggleCheckbox.checked = true; + } + } else { + var idx = this.ignoredSubReddits.indexOf(this.curSubReddit); + if (idx === -1) { + this.styleToggleCheckbox.checked = true; + } else { + this.toggleSubredditStyle(false); + } + } + this.styleToggleCheckbox.addEventListener('change', function(e) { + modules['styleTweaks'].toggleSubredditStyle(this.checked); + }, false); + this.styleToggleContainer.appendChild(this.styleToggleCheckbox); + insertAfter(subredditTitle, this.styleToggleContainer); + } + this.styleToggleLabel.setAttribute('for',this.styleCBName); + this.styleToggleLabel.setAttribute('id','label-'+this.styleCBName); + this.styleToggleLabel.textContent = 'Use subreddit style '; + this.styleToggleContainer.appendChild(this.styleToggleLabel); + this.setSRStyleToggleVisibility(true); // no source + }, + srstyleHideLock: RESUtils.createMultiLock(), + setSRStyleToggleVisibility: function (visible, source) { + /// When showing/hiding popups which could overlay the "Use subreddit style" checkbox, + /// set the checkbox's styling to "less visible" or "more visible" + /// @param visible bool make checkbox "more visible" (true) or less (false) + /// @param source string popup ID, so checkbox stays less visible until that popup's lock is released + var self = modules['styleTweaks']; + if (!self.styleToggleContainer) return; + + if (typeof source !== "undefined") { + if (visible) { + self.srstyleHideLock.unlock(source); + } else { + self.srstyleHideLock.lock(source); + } + } + + if (visible && self.srstyleHideLock.locked()) { + visible = false; + } + + // great, now people are still finding ways to hide this.. these extra declarations are to try and fight that. + // Sorry, subreddit moderators, but users can disable all subreddit stylesheets if they want - this is a convenience + // for them and I think taking this functionality away from them is unacceptable. + + var zIndex = 'z-index: ' + (visible ? ' 2147483647' : 'auto') + ' !important;'; + + self.styleToggleContainer.setAttribute( 'style', 'margin: 0 !important; background-color: inherit !important; color: inherit !important; display: block !important; position: relative !important; left: 0 !important; top: 0 !important; max-height: none!important; max-width: none!important; height: auto !important; width: auto !important; visibility: visible !important; overflow: auto !important; text-indent: 0 !important; font-size: 12px !important; float: none !important; opacity: 1 !important;' + zIndex ); + self.styleToggleCheckbox.setAttribute( 'style', 'margin: 0 !important; background-color: inherit !important; color: inherit !important; display: inline-block !important; position: relative !important; left: 0 !important; top: 0 !important; max-height: none!important; max-width: none!important; height: auto !important; width: auto !important; visibility: visible !important; overflow: auto !important; text-indent: 0 !important; font-size: 12px !important; float: none !important; opacity: 1 !important;' + zIndex ); + self.styleToggleLabel.setAttribute( 'style', 'margin: 0 !important; background-color: inherit !important; color: inherit !important; display: inline-block !important; position: relative !important; left: 0 !important; top: 0 !important; max-height: none!important; max-width: none!important; height: auto !important; width: auto !important; visibility: visible !important; overflow: auto !important; text-indent: 0 !important; font-size: 12px !important; margin-left: 4px !important; float: none !important; opacity: 1 !important;' + zIndex ); + }, + toggleSubredditStyle: function(toggle, subreddit) { + var togglesr = (subreddit) ? subreddit.toLowerCase() : this.curSubReddit; + if (toggle) { + this.enableSubredditStyle(subreddit); + } else { + this.disableSubredditStyle(subreddit); + } + }, + enableSubredditStyle: function(subreddit) { + var togglesr = (subreddit) ? subreddit.toLowerCase() : this.curSubReddit; + + if (this.isDark && !this.isNightmodeCompatible) { + var idx = this.nightModeWhitelist.indexOf(togglesr); + if (idx === -1) this.nightModeWhitelist.push(togglesr); // add if not found + RESStorage.setItem('RESmodules.styleTweaks.nightModeWhitelist',JSON.stringify(this.nightModeWhitelist)); + } else if (this.ignoredSubReddits) { + var idx = this.ignoredSubReddits.indexOf(togglesr); + if (idx !== -1) this.ignoredSubReddits.splice(idx, 1); // Remove it if found... + RESStorage.setItem('RESmodules.styleTweaks.ignoredSubredditStyles',JSON.stringify(this.ignoredSubReddits)); + } + + var subredditStyleSheet = document.createElement('link'); + subredditStyleSheet.setAttribute('title','applied_subreddit_stylesheet'); + subredditStyleSheet.setAttribute('rel','stylesheet'); + subredditStyleSheet.setAttribute('href','http://www.reddit.com/r/'+togglesr+'/stylesheet.css'); + if (!subreddit || (subreddit == this.curSubReddit)) this.head.appendChild(subredditStyleSheet); + }, + disableSubredditStyle: function(subreddit) { + var togglesr = (subreddit) ? subreddit.toLowerCase() : this.curSubReddit; + + if (this.isDark && !this.isNightmodeCompatible) { + var idx = this.nightModeWhitelist.indexOf(togglesr); + if (idx !== -1) this.nightModeWhitelist.splice(idx, 1); // Remove it if found... + RESStorage.setItem('RESmodules.styleTweaks.nightModeWhitelist',JSON.stringify(this.nightModeWhitelist)); + } else if (this.ignoredSubReddits) { + var idx = this.ignoredSubReddits.indexOf(togglesr); // Find the index + if (idx === -1) this.ignoredSubReddits.push(togglesr); + RESStorage.setItem('RESmodules.styleTweaks.ignoredSubredditStyles',JSON.stringify(this.ignoredSubReddits)); + } + + var subredditStyleSheet = this.head.querySelector('link[title=applied_subreddit_stylesheet]'); + if (!subredditStyleSheet) subredditStyleSheet = this.head.querySelector('style[title=applied_subreddit_stylesheet]'); + if ((subredditStyleSheet) && (!subreddit || (subreddit == this.curSubReddit))) { + subredditStyleSheet.parentNode.removeChild(subredditStyleSheet); + } + }, + redditDark: function(off) { + if (off) { + this.isDark = false; + localStorage.removeItem('RES_nightMode'); + document.html.classList.remove('res-nightmode'); + if (document.body) { + document.body.classList.remove('res-nightmode'); + } + } else { + this.isDark = true; + localStorage.setItem('RES_nightMode', true); + document.html.classList.add('res-nightmode'); + if (document.body) { + document.body.classList.add('res-nightmode'); + } + } + } +}; + +modules['accountSwitcher'] = { + moduleID: 'accountSwitcher', + moduleName: 'Account Switcher', + category: 'Accounts', + options: { + accounts: { + type: 'table', + addRowText: '+add account', + fields: [ + { name: 'username', type: 'text' }, + { name: 'password', type: 'password' } + ], + value: [ + /* + ['somebodymakethis','SMT','[SMT]'], + ['pics','pic','[pic]'] + */ + ], + description: 'Set your usernames and passwords below. They are only stored in RES preferences.' + }, + keepLoggedIn: { + type: 'boolean', + value: false, + description: 'Keep me logged in when I restart my browser.' + }, + showCurrentUserName: { + type: 'boolean', + value: false, + description: 'Show my current user name in the Account Switcher.' + }, + dropDownStyle: { + type: 'enum', + values: [ + { name: 'snoo (alien)', value: 'alien' }, + { name: 'simple arrow', value: 'arrow' } + ], + value: 'alien', + description: 'Use the "snoo" icon, or older style dropdown?' + } + }, + description: 'Store username/password pairs and switch accounts instantly while browsing Reddit!', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + RESUtils.addCSS('#header-bottom-right { height: auto; padding: 4px 4px 7px }') + RESUtils.addCSS('#RESAccountSwitcherDropdown { min-width: 110px; width: auto; display: none; position: absolute; z-index: 1000; }'); + RESUtils.addCSS('#RESAccountSwitcherDropdown li { height: auto; line-height: 20px; padding: 2px 10px; }'); + if (this.options.dropDownStyle.value === 'alien') { + RESUtils.addCSS('#RESAccountSwitcherIcon { cursor: pointer; margin-left: 3px; display: inline-block; width: 12px; vertical-align: middle; height: 16px; background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAPCAYAAAAyPTUwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkQ3NTExRkExOEYzNTExRTFBNjgzQzhEOUY2QzU2MUNFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkQ3NTExRkEyOEYzNTExRTFBNjgzQzhEOUY2QzU2MUNFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RDc1MTFGOUY4RjM1MTFFMUE2ODNDOEQ5RjZDNTYxQ0UiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RDc1MTFGQTA4RjM1MTFFMUE2ODNDOEQ5RjZDNTYxQ0UiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6W3fJJAAAB4ElEQVR42mJgwA4YgdgSiJUUFRXDW1tbL7Kzswsw4VDMBcRXgfgeMzPzJx4eHn4gG0MtSICPjY3NF0jLoCtglJWV1eDm5rZmZWX9k5ZbWGFmYqwhwM3B8Pn7T4bzl6/enzNlQsfrV68+srKxPWHMz89/ZmJiIunn58fA9+YKAwMHHwODlA4Dw4fHDAzPbzD8VLRhWLNuPcOzp0//MEhJSaU/f/HyPxhkyf//3xsEYa+s/f8/nOn//19f/n/98fO/jo5ONwMfH5/S27dvwfL/nt/5//8rhP3/z7f//55cgzD//PkPdK4F2N3x8fFLv3///v/d56//l69a83///v3/V65e8//+k+f///79+7+4uPgAUB0zIywUgNZEZmVlzRMTE2P78OEDA9DTDN++ffs3c+bMglOnTk0HqvkDC5p/L168+P7582cmaWlpBhUVFQZ5eXkGoPUMDx8+BMn/QQ5C1vb29r+HDx/+jwwuXLjwv7e39z8wWHkYkAOdk5OT4cePHygx9OXLF7BzgPpQo05NTS2mp6fnO7LJc+bM+a2np1eKNUFISEg0gEIFHIz//v3X1dWdDU1UYMAMYzg7O8eUlpYmXLly5dtfFm6h40cO3DU2NhYBphOea9euHQOpAQgwAKMW+Z5mJFvIAAAAAElFTkSuQmCC); }'); + RESUtils.addCSS('#RESAccountSwitcherIconOverlay { cursor: pointer; position: absolute; display: none; width: 11px; height: 22px; background-position: 2px 3px; padding-left: 2px; padding-right: 2px; padding-top: 3px; border: 1px solid #369; border-bottom: 1px solid #5f99cf; background-color: #5f99cf; border-radius: 3px 3px 0 0; z-index: 100; background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAPCAYAAAAyPTUwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkQ3NTExRkExOEYzNTExRTFBNjgzQzhEOUY2QzU2MUNFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkQ3NTExRkEyOEYzNTExRTFBNjgzQzhEOUY2QzU2MUNFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RDc1MTFGOUY4RjM1MTFFMUE2ODNDOEQ5RjZDNTYxQ0UiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RDc1MTFGQTA4RjM1MTFFMUE2ODNDOEQ5RjZDNTYxQ0UiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6W3fJJAAAB4ElEQVR42mJgwA4YgdgSiJUUFRXDW1tbL7Kzswsw4VDMBcRXgfgeMzPzJx4eHn4gG0MtSICPjY3NF0jLoCtglJWV1eDm5rZmZWX9k5ZbWGFmYqwhwM3B8Pn7T4bzl6/enzNlQsfrV68+srKxPWHMz89/ZmJiIunn58fA9+YKAwMHHwODlA4Dw4fHDAzPbzD8VLRhWLNuPcOzp0//MEhJSaU/f/HyPxhkyf//3xsEYa+s/f8/nOn//19f/n/98fO/jo5ONwMfH5/S27dvwfL/nt/5//8rhP3/z7f//55cgzD//PkPdK4F2N3x8fFLv3///v/d56//l69a83///v3/V65e8//+k+f///79+7+4uPgAUB0zIywUgNZEZmVlzRMTE2P78OEDA9DTDN++ffs3c+bMglOnTk0HqvkDC5p/L168+P7582cmaWlpBhUVFQZ5eXkGoPUMDx8+BMn/QQ5C1vb29r+HDx/+jwwuXLjwv7e39z8wWHkYkAOdk5OT4cePHygx9OXLF7BzgPpQo05NTS2mp6fnO7LJc+bM+a2np1eKNUFISEg0gEIFHIz//v3X1dWdDU1UYMAMYzg7O8eUlpYmXLly5dtfFm6h40cO3DU2NhYBphOea9euHQOpAQgwAKMW+Z5mJFvIAAAAAElFTkSuQmCC); }'); + } else { + RESUtils.addCSS('#RESAccountSwitcherIcon { display: inline-block; vertical-align: middle; margin-left: 3px; }'); + RESUtils.addCSS('#RESAccountSwitcherIcon .downArrow { cursor: pointer; margin-top: 2px; display: block; width: 16px; height: 10px; background-image: url("http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png"); background-position: 0 -106px; }'); + RESUtils.addCSS('#RESAccountSwitcherIconOverlay { cursor: pointer; position: absolute; display: none; width: 20px; height: 22px; z-index: 100; border: 1px solid #369; border-bottom: 1px solid #5f99cf; background-color: #5f99cf; border-radius: 3px 3px 0 0; }'); + RESUtils.addCSS('#RESAccountSwitcherIconOverlay .downArrow { margin-top: 6px; margin-left: 3px; display: inline-block; width: 18px; height: 10px; background-image: url("http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png"); background-position: 0 -96px; }'); + // this.alienIMG = ''; + } + // RESUtils.addCSS('#RESAccountSwitcherIconOverlay { display: none; position: absolute; }'); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + this.userLink = document.querySelector('#header-bottom-right > span.user > a'); + if (this.userLink) { + this.userLink.style.marginRight = '2px'; + this.loggedInUser = RESUtils.loggedInUser(); + // var downArrowIMG = 'data:image/gif;base64,R0lGODlhBwAEALMAAAcHBwgICAoKChERETs7Ozo6OkJCQg0NDRoaGhAQEAwMDDIyMv///wAAAAAAAAAAACH5BAEAAAwALAAAAAAHAAQAAAQQ0BSykADsDAUwY4kQfOT4RQA7'; + if (this.options.dropDownStyle.value === 'alien') { + this.downArrowOverlay = $(''); + this.downArrow = $(''); + } else { + this.downArrowOverlay = $(''); + this.downArrow = $(''); + } + this.downArrowOverlay.on('click', function() { + modules['accountSwitcher'].toggleAccountMenu(false); + modules['accountSwitcher'].manageAccounts(); + }).appendTo(document.body); + + this.downArrow.on('click', function() { + modules['accountSwitcher'].updateUserDetails(); + modules['accountSwitcher'].toggleAccountMenu(true); + }); + this.downArrowOverlay.on('mouseleave', function() { + modules['accountSwitcher'].dropdownTimer = setTimeout(function() { + modules['accountSwitcher'].toggleAccountMenu(false); + }, 1000); + }); + + // insertAfter(this.userLink, downArrow); + $(this.userLink).after(this.downArrow); + + this.accountMenu = $('
            ') + this.accountMenu.on('mouseenter', function() { + clearTimeout(modules['accountSwitcher'].dropdownTimer); + }); + this.accountMenu.on('mouseleave', function() { + modules['accountSwitcher'].toggleAccountMenu(false); + }); + // GM_addStyle(css); + var accounts = this.options.accounts.value; + if (accounts !== null) { + var accountCount = 0; + for (var i=0, len=accounts.length; i', { 'class': 'accountName' }); + // Check if user is logged in before comparing + if (this.loggedInUser && username.toUpperCase() === this.loggedInUser.toUpperCase()) { + $accountLink.addClass('active'); + } + + $accountLink + .data('username', username) + .html(username) + .css('cursor', 'pointer') + .on('click', function(e) { + e.preventDefault(); + modules['accountSwitcher'].switchTo($(this).data('username')); + }) + .appendTo(this.accountMenu); + + RESUtils.getUserInfo(function (userInfo) { + var userDetails = username; + + // Display the karma of the user, if it is already pre-fetched + if (userInfo && !userInfo.error && userInfo.data) { + userDetails = username + ' (' + userInfo.data.link_karma + ' · ' + userInfo.data.comment_karma + ')'; + } + + $accountLink.html(userDetails); + }, username, false); + } + } + $('
          • ', { 'class': 'addAccount' }) + .text('+ add account') + .css('cursor', 'pointer') + .on('click', function(e) { + e.preventDefault(); + modules['accountSwitcher'].toggleAccountMenu(false); + modules['accountSwitcher'].manageAccounts(); + }) + .appendTo(this.accountMenu); + } + $(document.body).append(this.accountMenu); + } + } + }, + updateUserDetails: function() { + this.accountMenu.find('.accountName').each(function (index) { + var username = $(this).data('username'), + that = this; + + // Ignore "+ add account" + if (typeof username === 'undefined') { + return; + } + + // Leave a 500 ms delay between requests + setTimeout(function () { + RESUtils.getUserInfo(function (userInfo) { + // Fail if retrieving the user's info results in an error (such as a 404) + if (!userInfo || userInfo.error || !userInfo.data) { + return; + } + + // Display the karma of the user + var userDetails = username + ' (' + userInfo.data.link_karma + ' · ' + userInfo.data.comment_karma + ')'; + $(that).html(userDetails); + }, username); + }, 500 * index); + }); + }, + toggleAccountMenu: function(open) { + if ((open) || (! $(modules['accountSwitcher'].accountMenu).is(':visible'))) { + var thisHeight = 18; + if ($('#RESAccountSwitcherDropdown').css('position') !== 'fixed') { + var thisX = $(modules['accountSwitcher'].userLink).offset().left; + var thisY = $(modules['accountSwitcher'].userLink).offset().top; + } else { + var thisX = $('#header-bottom-right').position().left + $(modules['accountSwitcher'].userLink).position().left; + var thisY = $(modules['accountSwitcher'].userLink).position().top; + if (modules['betteReddit'].options.pinHeader.value === 'subanduser') { + thisHeight += $('#sr-header-area').height(); + } else if (modules['betteReddit'].options.pinHeader.value === 'header') { + thisHeight += $('#sr-header-area').height(); + } + } + $(modules['accountSwitcher'].accountMenu).css({ + top: (thisY + thisHeight) + 'px', + left: (thisX) + 'px' + }); + $(modules['accountSwitcher'].accountMenu).show(); + var thisX = $(modules['accountSwitcher'].downArrow).offset().left; + var thisY = $(modules['accountSwitcher'].downArrow).offset().top; + $(modules['accountSwitcher'].downArrowOverlay).css({ + top: (thisY-4) + 'px', + left: (thisX-3) + 'px' + }); + + $(modules['accountSwitcher'].downArrowOverlay).show(); + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'accountSwitcher'); + + } else { + $(modules['accountSwitcher'].accountMenu).hide(); + $(modules['accountSwitcher'].downArrowOverlay).hide(); + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'accountSwitcher'); + } + }, + closeAccountMenu: function() { + // this function basically just exists for other modules to call. + this.accountMenu.hide(); + }, + switchTo: function(username) { + var accounts = this.options.accounts.value; + var password = ''; + var rem = ''; + if (this.options.keepLoggedIn.value) { + rem = '&rem=on'; + } + for (var i=0, len=accounts.length; ionoff'); + this.nsfwSwitch.appendChild(this.nsfwSwitchToggle); + (this.options.NSFWfilter.value) ? this.nsfwSwitchToggle.classList.add('enabled') : this.nsfwSwitchToggle.classList.remove('enabled'); + thisFrag.appendChild(this.nsfwSwitch); + $('#RESDropdownOptions').append(this.nsfwSwitch); + } + + if ((this.isEnabled()) && (this.isMatchURL())) { + this.scanEntries(); + RESUtils.watchForElement('siteTable', modules['filteReddit'].scanEntries); + } + }, + toggleNsfwFilter: function(toggle, notify) { + if (toggle !== true && toggle === false || modules['filteReddit'].options.NSFWfilter.value == true) { + modules['filteReddit'].filterNSFW(false); + RESUtils.setOption('filteReddit','NSFWfilter',false); + $(modules['filteReddit'].nsfwSwitchToggle).removeClass('enabled'); + } else { + modules['filteReddit'].filterNSFW(true); + RESUtils.setOption('filteReddit','NSFWfilter',true); + $(modules['filteReddit'].nsfwSwitchToggle).addClass('enabled'); + } + + if (notify) { + var onOff = modules['filteReddit'].options.NSFWfilter.value ? 'on' : ' off'; + + RESUtils.notification({ + header: 'NSFW Filter', + moduleID: 'filteReddit', + optionKey: 'NSFWfilter', + message: 'NSFW Filter has been turned ' + onOff + '.' + }, 4000); + } + }, + scanEntries: function(ele) { + var numFiltered = 0; + var numNsfwHidden = 0; + + var entries; + if (ele == null) { + entries = document.querySelectorAll('#siteTable div.thing.link'); + } else { + entries = ele.querySelectorAll('div.thing.link'); + } + // var RALLre = /\/r\/all\/?(([\w]+)\/)?/i; + // var onRALL = RALLre.exec(location.href); + var filterSubs = (RESUtils.currentSubreddit('all')) || (RESUtils.currentDomain()); + for (var i=0, len=entries.length; i a').innerHTML.toLowerCase(); + var thisSubreddit = entries[i].querySelector('.entry a.subreddit'); + var postFlair = entries[i].querySelector('.entry span.linkflairlabel'); + if (thisSubreddit !== null) { + var postSubreddit = thisSubreddit.innerHTML; + } else { + var postSubreddit = false; + } + var filtered = false; + var currSub = (RESUtils.currentSubreddit()) ? RESUtils.currentSubreddit().toLowerCase() : null; + filtered = modules['filteReddit'].filterTitle(postTitle, postSubreddit || RESUtils.currentSubreddit()); + if (!filtered) filtered = modules['filteReddit'].filterDomain(postDomain, postSubreddit || currSub); + if ((!filtered) && (filterSubs) && (postSubreddit)) { + filtered = modules['filteReddit'].filterSubreddit(postSubreddit); + } + if ((!filtered) && (postFlair)) { + filtered = modules['filteReddit'].filterFlair(postFlair.textContent, postSubreddit || RESUtils.currentSubreddit()); + } + if (filtered) { + entries[i].classList.add('RESFiltered') + numFiltered++; + } + + if (entries[i].classList.contains('over18')) { + if (modules['filteReddit'].allowNSFW(postSubreddit, currSub)) { + entries[i].classList.add('allowOver18'); + } else if (modules['filteReddit'].options.NSFWfilter.value) { + numNsfwHidden++; + } + } + } + + if ((numFiltered || numNsfwHidden) && modules['filteReddit'].options.notification.value) { + var notification = []; + if (numFiltered) notification.push( numFiltered + ' post(s) hidden by ' + modules['settingsNavigation'].makeUrlHashLink('filteReddit', 'keywords', 'custom filters') + '.'); + if (numNsfwHidden) notification.push ( numNsfwHidden + ' post(s) hidden by the ' + modules['settingsNavigation'].makeUrlHashLink('filteReddit', 'NSFWfilter', 'NSFW filter') + '.'); + if (numNsfwHidden && modules['filteReddit'].options.NSFWQuickToggle.value) notification.push('You can toggle the nsfw filter in the menu.'); + + notification.push("To hide this message, disable the " + modules['settingsNavigation'].makeUrlHashLink('filteReddit', 'notification', 'filteReddit notification') + '.'); + var notification = notification.join('

            '); + RESUtils.notification({ + header: 'Posts Filtered', + moduleID: 'filteReddit', + message: notification + }); + } + }, + addedNSFWFilterStyle: false, + addNSFWFilterStyle: function() { + if (this.addedNSFWFilterStyle) return; + this.addedNSFWFilterStyle = true; + + RESUtils.addCSS('body:not(.allowOver18) .over18 { display: none !important; }'); + RESUtils.addCSS('.thing.over18.allowOver18 { display: block !important; }'); + }, + filterNSFW: function(filterOn) { + this.addNSFWFilterStyle(); + $(document.body).toggleClass('allowOver18'); + }, + filterTitle: function(title, reddit) { + var reddit = (reddit) ? reddit.toLowerCase() : null; + return this.arrayContainsSubstring(this.options.keywords.value, title.toLowerCase(), reddit); + }, + filterDomain: function(domain, reddit) { + var reddit = (reddit) ? reddit.toLowerCase() : null; + var domain = (domain) ? domain.toLowerCase() : null; + return this.arrayContainsSubstring(this.options.domains.value, domain, reddit); + }, + filterSubreddit: function(subreddit) { + return this.arrayContainsSubstring(this.options.subreddits.value, subreddit.toLowerCase(), null, true); + }, + filterFlair: function(flair, reddit) { + var reddit = (reddit) ? reddit.toLowerCase() : null; + return this.arrayContainsSubstring(this.options.flair.value, flair.toLowerCase(), reddit); + }, + allowAllNSFW: null, // lazy loaded with boolean-y value + subredditAllowNsfwOption: null, // lazy loaded with function to get a given subreddit's row in this.options.allowNSFW + allowNSFW: function (postSubreddit, currSubreddit) { + if (!this.options.allowNSFW.value || !this.options.allowNSFW.value.length) return false; + + if (typeof currSubreddit === "undefined") { + currSubreddit = RESUtils.currentSubreddit(); + } + + if (!this.subredditAllowNsfwOption) { + this.subredditAllowNsfwOption = RESUtils.indexOptionTable('filteReddit', 'allowNSFW', 0); + } + + if (this.allowAllNsfw == null && currSubreddit) { + var optionValue = this.subredditAllowNsfwOption(currSubreddit); + this.allowAllNsfw = (optionValue && optionValue[1] === 'visit') || false; + } + if (this.allowAllNsfw) { + return true; + } + + if (!postSubreddit) postSubreddit = currSubreddit; + if (!postSubreddit) return false; + var optionValue = this.subredditAllowNsfwOption(postSubreddit); + if (optionValue) { + if (optionValue[1] === 'everywhere') { + return true; + } else { // optionValue[1] == visit (subreddit or multisubreddit) + if (RESUtils.inList(postSubreddit, currSubreddit, '+')) { + return true; + } + } + } + }, + unescapeHTML: function(theString) { + var temp = document.createElement("div"); + $(temp).html(theString); + var result = temp.childNodes[0].nodeValue; + temp.removeChild(temp.firstChild); + delete temp; + return result; + }, + arrayContainsSubstring: function(obj, stringToSearch, reddit, fullmatch) { + if (!obj) return false; + stringToSearch = this.unescapeHTML(stringToSearch); + var i = obj.length; + while (i--) { + if ((typeof obj[i] !== 'object') || (obj[i].length < 3)) { + if (obj[i].length === 1) obj[i] = obj[i][0]; + obj[i] = [obj[i], 'everywhere','']; + } + var searchString = obj[i][0]; + var applyTo = obj[i][1]; + var applyList = obj[i][2].toLowerCase().split(','); + var skipCheck = false; + // we also want to know if we should be matching /r/all, because when getting + // listings on /r/all, each post has a subreddit (that does not equal "all") + var checkRAll = ((RESUtils.currentSubreddit() === "all") && (applyList.indexOf("all") !== -1)); + switch (applyTo) { + case 'exclude': + if ((applyList.indexOf(reddit) !== -1) || (checkRAll)) { + skipCheck = true; + } + break; + case 'include': + if ((applyList.indexOf(reddit) === -1) && (!checkRAll)) { + skipCheck = true; + } + break; + } + // if fullmatch is defined, don't do a substring match... this is used for subreddit matching on /r/all for example + if ((!skipCheck) && (fullmatch) && (obj[i] !== null) && (stringToSearch.toLowerCase() === searchString.toLowerCase())) return true; + if ((!skipCheck) && (!fullmatch) && (obj[i] !== null) && (stringToSearch.indexOf(searchString.toString().toLowerCase()) !== -1)) { + return true; + } + } + return false; + }, + toggleFilter: function(e) { + var thisSubreddit = $(e.target).data('subreddit').toLowerCase(); + var filteredReddits = modules['filteReddit'].options.subreddits.value || []; + var exists=false; + for (var i=0, len=filteredReddits.length; iShow:
          • ') + var subscriptionFilter = $('') + $(showDiv).append(subscriptionFilter); + $('#newCommentsContents').append(showDiv); + $('#subscriptionFilter').change(function(){ + modules['newCommentCount'].drawSubscriptionsTable(); + }); + var thisTable = $('
        Username TagIgnoredColorVote Weight
        '+thisUser+' '+thisIgnore+''+thisColor+''+thisVotes+'
        '); + $(thisTable).append(''); + $('#newCommentsContents').append(thisTable); + $('#newCommentsTable thead th').click(function(e) { + e.preventDefault(); + if ($(this).hasClass('actions')) { + return false; + } + if ($(this).hasClass('active')) { + $(this).toggleClass('descending'); + } + $(this).addClass('active'); + $(this).siblings().removeClass('active').find('SPAN').remove(); + $(this).find('.sortAsc, .sortDesc').remove(); + ($(e.target).hasClass('descending')) ? $(this).append('') : $(this).append(''); + modules['newCommentCount'].drawSubscriptionsTable($(e.target).attr('sort'), $(e.target).hasClass('descending')); + }); + this.drawSubscriptionsTable(); + RESUtils.watchForElement('siteTable', modules['newCommentCount'].processCommentCounts); + /* + document.body.addEventListener('DOMNodeInserted', function(event) { + if ((event.target.tagName === 'DIV') && (event.target.getAttribute('id') && event.target.getAttribute('id').indexOf('siteTable') !== -1)) { + modules['newCommentCount'].processCommentCounts(event.target); + } + }, true); + */ + } else { + this.processCommentCounts(); + RESUtils.watchForElement('siteTable', modules['newCommentCount'].processCommentCounts); + /* + document.body.addEventListener('DOMNodeInserted', function(event) { + if ((event.target.tagName === 'DIV') && (event.target.getAttribute('id') && event.target.getAttribute('id').indexOf('siteTable') !== -1)) { + modules['newCommentCount'].processCommentCounts(event.target); + } + }, true); + */ + } + this.checkSubscriptions(); + } + }, + drawSubscriptionsTable: function(sortMethod, descending) { + var filterType = $('#subscriptionFilter').val(); + this.currentSortMethod = sortMethod || this.currentSortMethod; + this.descending = (descending == null) ? this.descending : descending == true; + var thisCounts = []; + for (var i in this.commentCounts) { + this.commentCounts[i].id = i; + // grab the subreddit out of the URL and store it in match[i] + var match = this.commentCounts[i].url.match(RESUtils.matchRE); + if (match) { + this.commentCounts[i].subreddit = match[1].toLowerCase(); + thisCounts.push(this.commentCounts[i]); + } + } + $('#newCommentsTable tbody').html(''); + switch (this.currentSortMethod) { + case 'subscriptionDate': + thisCounts.sort(function(a,b) { + return (a.subscriptionDate > b.subscriptionDate) ? 1 : (b.subscriptionDate > a.subscriptionDate) ? -1 : 0; + }); + if (this.descending) thisCounts.reverse(); + break; + case 'updateTime': + thisCounts.sort(function(a,b) { + return (a.updateTime > b.updateTime) ? 1 : (b.updateTime > a.updateTime) ? -1 : 0; + }); + if (this.descending) thisCounts.reverse(); + break; + case 'subreddit': + thisCounts.sort(function(a,b) { + return (a.subreddit > b.subreddit) ? 1 : (b.subreddit > a.subreddit) ? -1 : 0; + }); + if (this.descending) thisCounts.reverse(); + break; + default: + thisCounts.sort(function(a,b) { + return (a.title > b.title) ? 1 : (b.title > a.title) ? -1 : 0; + }); + if (this.descending) thisCounts.reverse(); + break; + } + var rows = 0; + for (var i in thisCounts) { + if ((filterType === 'all threads') || ((filterType === 'subscribed threads') && (typeof thisCounts[i].subscriptionDate !== 'undefined'))) { + var thisTitle = thisCounts[i].title; + var thisURL = thisCounts[i].url; + var thisUpdateTime = new Date(thisCounts[i].updateTime); + // expire time is this.options.subscriptionLength.value days, so: 1000ms * 60s * 60m * 24hr = 86400000 + // then multiply by this.options.subscriptionLength.value + var thisSubscriptionExpirationDate = (typeof thisCounts[i].subscriptionDate !== 'undefined') ? new Date(thisCounts[i].subscriptionDate + (86400000 * this.options.subscriptionLength.value)) : 0; + if (thisSubscriptionExpirationDate > 0) { + var thisExpiresContent = RESUtils.niceDateTime(thisSubscriptionExpirationDate); + var thisRenewButton = 'renew'; + var thisUnsubButton = 'unsubscribe'; + var thisActionContent = thisRenewButton+thisUnsubButton; + + } else { + var thisExpiresContent = 'n/a'; + var thisActionContent = ''; + } + var thisSubreddit = '/r/'+thisCounts[i].subreddit+''; + var thisROW = $(''); + $(thisROW).find('.renew').click(modules['newCommentCount'].renewSubscriptionButton); + $(thisROW).find('.unsubscribe').click(modules['newCommentCount'].unsubscribeButton); + $(thisROW).find('.subscribe').click(modules['newCommentCount'].subscribeButton); + $('#newCommentsTable tbody').append(thisROW); + rows++; + } + } + if (rows === 0) { + if (filterType === 'subscribed threads') { + $('#newCommentsTable tbody').append(''); + } else { + $('#newCommentsTable tbody').append(''); + } + } + }, + renewSubscriptionButton: function(e) { + var thisURL = $(e.target).attr('data-threadid'); + modules['newCommentCount'].renewSubscription(thisURL); + RESUtils.notification({ + header: 'Subscription Notification', + moduleID: 'newCommentCount', + optionKey: 'subscriptionLength', + message: 'Your subscription has been renewed - it will expire in '+modules['newCommentCount'].options.subscriptionLength.value+' days.' + }) + }, + renewSubscription: function(threadid) { + var now = new Date(); + modules['newCommentCount'].commentCounts[threadid].subscriptionDate = now.getTime(); + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + this.drawSubscriptionsTable(); + }, + unsubscribeButton: function(e) { + var confirmunsub = window.confirm('Are you sure you want to unsubscribe?'); + if (confirmunsub) { + var thisURL = $(e.target).attr('data-threadid'); + modules['newCommentCount'].unsubscribe(thisURL); + } + }, + unsubscribe: function(threadid) { + delete modules['newCommentCount'].commentCounts[threadid].subscriptionDate; + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + this.drawSubscriptionsTable(); + }, + subscribeButton: function(e) { + var thisURL = $(e.target).attr('data-threadid'); + modules['newCommentCount'].subscribe(thisURL); + }, + subscribe: function(threadid) { + var now = new Date(); + modules['newCommentCount'].commentCounts[threadid].subscriptionDate = now.getTime(); + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + this.drawSubscriptionsTable(); + }, + processCommentCounts: function(ele) { + var ele = ele || document.body; + var lastClean = RESStorage.getItem('RESmodules.newCommentCount.lastClean'); + var now = new Date(); + if (lastClean == null) { + lastClean = now.getTime(); + RESStorage.setItem('RESmodules.newCommentCount.lastClean', now.getTime()); + } + // Clean cache every six hours + if ((now.getTime() - lastClean) > 21600000) { + modules['newCommentCount'].cleanCache(); + } + var IDre = /\/r\/[\w]+\/comments\/([\w]+)\//i; + var commentsLinks = ele.querySelectorAll('.sitetable.linklisting div.thing.link a.comments'); + for (var i=0, len=commentsLinks.length; i 0) { + var newString = $(' ('+diff+' new)'); + $(commentsLinks[i]).append(newString); + } + } + } + } + }, + updateCommentCountFromMyComment: function() { + modules['newCommentCount'].updateCommentCount(true); + }, + updateCommentCount: function(mycomment) { + var thisModule = modules['newCommentCount']; + var IDre = /\/r\/[\w]+\/comments\/([\w]+)\//i; + var matches = IDre.exec(location.href); + if (matches) { + if (!thisModule.currentCommentCount) { + thisModule.currentCommentID = matches[1]; + var thisCount = document.querySelector('#siteTable a.comments'); + if (thisCount) { + var split = thisCount.innerHTML.split(' '); + thisModule.currentCommentCount = split[0]; + if ((typeof thisModule.commentCounts[thisModule.currentCommentID] !== 'undefined') && (thisModule.commentCounts[thisModule.currentCommentID] !== null)) { + var prevCommentCount = thisModule.commentCounts[thisModule.currentCommentID].count; + var diff = thisModule.currentCommentCount - prevCommentCount; + var newString = $(' ('+diff+' new)'); + if (diff>0) $(thisCount).append(newString); + } + if (isNaN(thisModule.currentCommentCount)) thisModule.currentCommentCount = 0; + if (mycomment) thisModule.currentCommentCount++; + } + } else { + thisModule.currentCommentCount++; + } + } + var now = new Date(); + if (typeof thisModule.commentCounts === 'undefined') { + thisModule.commentCounts = {}; + } + if (typeof thisModule.commentCounts[thisModule.currentCommentID] === 'undefined') { + thisModule.commentCounts[thisModule.currentCommentID] = {}; + } + thisModule.commentCounts[thisModule.currentCommentID].count = thisModule.currentCommentCount; + thisModule.commentCounts[thisModule.currentCommentID].url = location.href.replace(location.hash, ''); + thisModule.commentCounts[thisModule.currentCommentID].title = document.title; + thisModule.commentCounts[thisModule.currentCommentID].updateTime = now.getTime(); + // if (this.currentCommentCount) { + // dumb, but because of Greasemonkey security restrictions we need a window.setTimeout here... + window.setTimeout( function() { + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + }, 100); + // } + }, + cleanCache: function() { + var now = new Date(); + for (var i in this.commentCounts) { + if ((this.commentCounts[i] !== null) && ((now.getTime() - this.commentCounts[i].updateTime) > (86400000 * this.options.cleanComments.value))) { + // this.commentCounts[i] = null; + delete this.commentCounts[i]; + } else if (this.commentCounts[i] == null) { + delete this.commentCounts[i]; + } + } + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(this.commentCounts)); + RESStorage.setItem('RESmodules.newCommentCount.lastClean', now.getTime()); + }, + addSubscribeLink: function() { + var commentCount = document.body.querySelector('.commentarea .panestack-title'); + if (commentCount) { + this.commentSubToggle = createElementWithID('span','REScommentSubToggle','RESSubscriptionButton'); + this.commentSubToggle.addEventListener('click', modules['newCommentCount'].toggleSubscription, false); + commentCount.appendChild(this.commentSubToggle); + if (typeof this.commentCounts[this.currentCommentID].subscriptionDate !== 'undefined') { + this.commentSubToggle.textContent = 'unsubscribe'; + this.commentSubToggle.setAttribute('title','unsubscribe from thread'); + this.commentSubToggle.classList.add('unsubscribe'); + } else { + this.commentSubToggle.textContent = 'subscribe'; + this.commentSubToggle.setAttribute('title','subscribe to this thread to be notified when new comments are posted'); + this.commentSubToggle.classList.remove('unsubscribe'); + } + } + }, + toggleSubscription: function() { + var commentID = modules['newCommentCount'].currentCommentID; + if (typeof modules['newCommentCount'].commentCounts[commentID].subscriptionDate !== 'undefined') { + modules['newCommentCount'].unsubscribeFromThread(commentID); + } else { + modules['newCommentCount'].subscribeToThread(commentID); + } + }, + getLatestCommentCounts: function() { + var counts = RESStorage.getItem('RESmodules.newCommentCount.counts'); + if (counts == null) { + counts = '{}'; + } + modules['newCommentCount'].commentCounts = safeJSON.parse(counts, 'RESmodules.newCommentCount.counts'); + }, + subscribeToThread: function(commentID) { + modules['newCommentCount'].getLatestCommentCounts(); + modules['newCommentCount'].commentSubToggle.textContent = 'unsubscribe'; + modules['newCommentCount'].commentSubToggle.setAttribute('title','unsubscribe from thread'); + modules['newCommentCount'].commentSubToggle.classList.add('unsubscribe'); + commentID = commentID || modules['newCommentCount'].currentCommentID; + var now = new Date(); + modules['newCommentCount'].commentCounts[commentID].subscriptionDate = now.getTime(); + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + RESUtils.notification({ + header: 'Subscription Notification', + moduleID: 'newCommentCount', + optionKey: 'subscriptionLength', + message: 'You are now subscribed to this thread for '+modules['newCommentCount'].options.subscriptionLength.value+' days. You will be notified if new comments are posted since your last visit.' + + '

        Visit your Dashboard to see all your thread subscriptions.' + }, 3000); + }, + unsubscribeFromThread: function(commentID) { + modules['newCommentCount'].getLatestCommentCounts(); + modules['newCommentCount'].commentSubToggle.textContent = 'subscribe'; + modules['newCommentCount'].commentSubToggle.setAttribute('title','subscribe to this thread and be notified when new comments are posted'); + modules['newCommentCount'].commentSubToggle.classList.remove('unsubscribe'); + commentID = commentID || modules['newCommentCount'].currentCommentID; + var now = new Date(); + delete modules['newCommentCount'].commentCounts[commentID].subscriptionDate; + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + RESUtils.notification({ + header: 'Subscription Notification', + moduleID: 'newCommentCount', + message: 'You are now unsubscribed from this thread.' + }, 3000); + }, + checkSubscriptions: function() { + if (this.commentCounts) { + var threadsToCheck = []; + for (var i in this.commentCounts) { + var thisSubscription = this.commentCounts[i]; + if ((thisSubscription) && (typeof thisSubscription.subscriptionDate !== 'undefined')) { + var lastCheck = parseInt(thisSubscription.lastCheck, 10) || 0; + var subscriptionDate = parseInt(thisSubscription.subscriptionDate, 10); + // If it's been subscriptionLength days since we've subscribed, we're going to delete this subscription... + var now = new Date(); + if ((now.getTime() - subscriptionDate) > (this.options.subscriptionLength.value * 86400000)) { + delete this.commentCounts[i].subscriptionDate; + } + // if we haven't checked this subscription in 5 minutes, try it again... + if ((now.getTime() - lastCheck) > 300000) { + thisSubscription.lastCheck = now.getTime(); + this.commentCounts[i] = thisSubscription; + // this.checkThread(i); + threadsToCheck.push('t3_'+i); + } + RESStorage.setItem('RESmodules.newCommentCount.count', JSON.stringify(this.commentCounts)); + } + } + if (threadsToCheck.length > 0) { + this.checkThreads(threadsToCheck); + } + } + }, + checkThreads: function(commentIDs) { + GM_xmlhttpRequest({ + method: "GET", + url: location.protocol + '//' + location.hostname + '/by_id/' + commentIDs.join(',') + '.json?app=res', + onload: function(response) { + var now = new Date(); + var commentInfo = JSON.parse(response.responseText); + if (typeof commentInfo.data !== 'undefined') { + for (var i=0, len=commentInfo.data.children.length; iNew comments posted to thread:

        ' + subObj.title + '

        view the submission

        ' + }, 10000); + } + } + RESStorage.setItem('RESmodules.newCommentCount.counts', JSON.stringify(modules['newCommentCount'].commentCounts)); + } + } + }); + } +}; + +modules['spamButton'] = { + moduleID: 'spamButton', + moduleName: 'Spam Button', + category: 'Filters', + options: { + }, + description: 'Adds a Spam button to posts for easy reporting.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[\?]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // check if the spam button was on by default from an old install of RES. Per Reddit Admin request, this is being + // disabled by default due to excess misuse, but people who want to purposefully re-enable it may do so. + var reset = RESStorage.getItem('RESmodules.spamButton.reset'); + if (!reset) { + RESStorage.setItem('RESmodules.spamButton.reset','true'); + RESConsole.enableModule('spamButton', false); + } + + // credit to tico24 for the idea, here: http://userscripts.org/scripts/review/84454 + // code adapted for efficiency... + if (RESUtils.loggedInUser() !== RESUtils.currentUserProfile()) { + RESUtils.watchForElement('siteTable', modules['spamButton'].addSpamButtons); + this.addSpamButtons(); + } + } + }, + addSpamButtons: function(ele) { + if (ele == null) ele = document; + if ((RESUtils.pageType() === 'linklist') || (RESUtils.pageType() === 'comments') || (RESUtils.pageType() === 'profile')) { + var allLists = ele.querySelectorAll('#siteTable ul.flat-list.buttons'); + for(var i=0, len=allLists.length; i .spacer { margin-right: 0; }'); + RESUtils.addCSS('#commentNavButtons { margin: auto; }'); + RESUtils.addCSS('#commentNavUp { margin: auto; cursor: pointer; background-image: url("http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png"); width: 32px; height: 20px; background-position: 0 -224px; }'); + RESUtils.addCSS('#commentNavDown { margin: auto; cursor: pointer; background-image: url("http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png"); width: 32px; height: 20px; background-position: 0 -244px; }'); + RESUtils.addCSS('#commentNavUp.noNav { background-position: 0 -264px; }'); + RESUtils.addCSS('#commentNavDown.noNav { background-position: 0 -284px; }'); + RESUtils.addCSS('#commentNavButtons { display: none; margin-left: 12px; text-align: center; user-select: none; -webkit-user-select: none; -moz-user-select: none; }'); + RESUtils.addCSS('.commentNavSortType { cursor: pointer; font-weight: bold; float: left; margin-left: 6px; }'); + RESUtils.addCSS('#commentNavPostCount { color: #1278d3; }'); + RESUtils.addCSS('.noNav #commentNavPostCount { color: #ddd; }'); + RESUtils.addCSS('.commentNavSortTypeDisabled { color: #ddd; }'); + RESUtils.addCSS('.commentNavSortType:hover { text-decoration: underline; }'); + RESUtils.addCSS('#REScommentNavToggle span { float: left; margin-left: 6px; }'); + RESUtils.addCSS('.menuarea > .spacer { float: left; }'); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // draw the commentNav box + this.commentNavBox = createElementWithID('div','REScommentNavBox'); + this.commentNavBox.classList.add('RESDialogSmall'); + // var commentArea = document.body.querySelector('div.sitetable.nestedlisting'); + var commentArea = document.body.querySelector('.commentarea .menuarea'); + if (commentArea) { + this.commentNavToggle = createElementWithID('div','REScommentNavToggle'); + $(this.commentNavToggle).html('navigate by:'); + var sortTypes = ['submitter', 'moderator', 'friend', 'me', 'admin', 'IAmA', 'images', 'popular', 'new']; + for (var i=0, len=sortTypes.length; i 0) { + modules['commentNavigator'].nav[category]--; + } else { + modules['commentNavigator'].nav[category] = modules['commentNavigator'].posts[category].length - 1; + } + modules['commentNavigator'].scrollToNavElement(); + } + }, + moveDown: function() { + var category = modules['commentNavigator'].currentCategory; + if (modules['commentNavigator'].posts[category].length) { + if (modules['commentNavigator'].nav[category] < modules['commentNavigator'].posts[category].length - 1) { + modules['commentNavigator'].nav[category]++; + } else { + modules['commentNavigator'].nav[category] = 0; + } + modules['commentNavigator'].scrollToNavElement(); + } + }, + scrollToNavElement: function() { + var category = modules['commentNavigator'].currentCategory; + $(modules['commentNavigator'].commentNavPostCount).text(modules['commentNavigator'].nav[category]+1 + '/' + modules['commentNavigator'].posts[category].length); + var thisXY=RESUtils.getXYpos(modules['commentNavigator'].posts[category][modules['commentNavigator'].nav[category]]); + RESUtils.scrollTo(0,thisXY.y); + } +}; + + +/* +modules['redditProfiles'] = { + moduleID: 'redditProfiles', + moduleName: 'Reddit Profiles', + category: 'Users', + options: { + }, + description: 'Pulls in profiles from redditgifts.com when viewing a user profile.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^http:\/\/([a-z]+).reddit.com\/user\/[-\w\.]+/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + RESUtils.addCSS('.redditGiftsProfileField { margin-top: 3px; margin-bottom: 6px; }'); + RESUtils.addCSS('.redditGiftsTrophy { margin-right: 4px; }'); + var thisCache = RESStorage.getItem('RESmodules.redditProfiles.cache'); + if (thisCache == null) { + thisCache = '{}'; + } + this.profileCache = safeJSON.parse(thisCache); + if (this.profileCache == null) this.profileCache = {}; + var userRE = /\/user\/(\w+)/i; + var match = userRE.exec(location.href); + if (match) { + var username = match[1]; + this.getProfile(username); + } + } + }, + getProfile: function(username) { + var lastCheck = 0; + if ((typeof this.profileCache[username] !== 'undefined') && (this.profileCache[username] !== null)) { + lastCheck = this.profileCache[username].lastCheck; + } + var now = new Date(); + if ((now.getTime() - lastCheck) > 900000) { + var jsonURL = 'http://redditgifts.com/profiles/view-json/'+username+'/'; + GM_xmlhttpRequest({ + method: "GET", + url: jsonURL, + onload: function(response) { + try { + // if it is JSON parseable, it's a profile. + var profileData = JSON.parse(response.responseText); + } catch(error) { + // if it is NOT JSON parseable, it's a 404 - user doesn't have a profile. + var profileData = {}; + } + var now = new Date(); + profileData.lastCheck = now.getTime(); + // set the last check time... + modules['redditProfiles'].profileCache[username] = profileData; + RESStorage.setItem('RESmodules.redditProfiles.cache', JSON.stringify(modules['redditProfiles'].profileCache)); + modules['redditProfiles'].displayProfile(username, profileData); + } + }); + } else { + this.displayProfile(username, this.profileCache[username]); + } + }, + displayProfile: function(username, profileObject) { + if (typeof profileObject !== 'undefined') { + var firstSpacer = document.querySelector('div.side > div.spacer'); + var newSpacer = document.createElement('div'); + var profileHTML = '
        what\'s this?

        PROFILE

        '; + var profileBody = ''; + if (typeof profileObject.body !== 'undefined') { + profileBody += '

        RedditGifts Profile:

        '; + profileBody += '
        '+profileObject.body+'
        '; + } + if (typeof profileObject.description !== 'undefined') { + profileBody += '

        Description:

        '; + profileBody += '
        '+profileObject.description+'
        '; + } + if (typeof profileObject.photo !== 'undefined') { + profileBody += '

        Photo:

        '; + profileBody += '
        '; + } + if (typeof profileObject.twitter_username !== 'undefined') { + profileBody += '

        Twitter:

        '; + profileBody += ''; + } + if (typeof profileObject.website !== 'undefined') { + profileBody += '

        Website:

        '; + profileBody += ''; + } + if (typeof profileObject.trophies !== 'undefined') { + profileBody += '

        RedditGifts Trophies:

        '; + var count=1; + var len=profileObject.trophies.length; + for (var i in profileObject.trophies) { + var rowNum = parseInt(count/2); + if (count===1) { + profileBody += '
        Thread titleSubredditLast VisitedSubscription ExpiresActions
        '+thisTitle+''+thisSubreddit+''+RESUtils.niceDateTime(thisUpdateTime)+''+thisExpiresContent+''+thisActionContent+'
        You are currently not subscribed to any threads. To subscribe to a thread, click the "subscribe" button found near the top of the comments page.No threads found
        '; + } + // console.log('count: ' + count + ' -- mod: ' + (count%2) + ' len: ' + len); + // console.log('countmod: ' + ((count%2) === 0)); + if ((count%2) === 1) { + profileBody += ''; + } + if ((count===len) && ((count%2) === 1)) { + profileBody += ''; + if (((count%2) === 0) || (count===len)) { + profileBody += ''; + } + count++; + } + if (count) { + profileBody += '
        '; + } else { + profileBody += ''; + } + profileBody += '
        '+profileObject.trophies[i].title+'
        '+profileObject.trophies[i].title+'
        '; + profileBody += '
        '; + } + } + if (profileBody === '') { + profileBody = 'User has not filled out a profile on RedditGifts.'; + } + profileHTML += profileBody + ''; + $(newSpacer).html(profileHTML); + addClass(newSpacer,'spacer'); + insertAfter(firstSpacer,newSpacer); + } + } +}; +*/ + +modules['subredditManager'] = { + moduleID: 'subredditManager', + moduleName: 'Subreddit Manager', + category: 'UI', + options: { + subredditShortcut: { + type: 'boolean', + value: true, + description: 'Add +shortcut button in subreddit sidebar for easy addition of shortcuts.' + }, + linkDashboard: { + type: 'boolean', + value: true, + description: 'Show "DASHBOARD" link in subreddit manager' + }, + linkAll: { + type: 'boolean', + value: true, + description: 'Show "ALL" link in subreddit manager' + }, + linkFront: { + type: 'boolean', + value: true, + description: 'show "FRONT" link in subreddit manager' + }, + linkRandom: { + type: 'boolean', + value: true, + description: 'Show "RANDOM" link in subreddit manager' + }, + linkMyRandom: { + type: 'boolean', + value: true, + description: 'Show "MYRANDOM" link in subreddit manager (reddit gold only)' + }, + linkRandNSFW: { + type: 'boolean', + value: false, + description: 'Show "RANDNSFW" link in subreddit manager' + }, + linkFriends: { + type: 'boolean', + value: true, + description: 'Show "FRIENDS" link in subreddit manager' + }, + linkMod: { + type: 'boolean', + value: true, + description: 'Show "MOD" link in subreddit manager' + }, + linkModqueue: { + type: 'boolean', + value: true, + description: 'Show "MODQUEUE" link in subreddit manager' + } +/* sortingField: { + type: 'enum', + values: [ + { name: 'Subreddit Name', value: 'displayName' }, + { name: 'Added date', value: 'addedDate' } + ], + value : 'displayName', + description: 'Field to sort subreddit shortcuts by' + }, + sortingDirection: { + type: 'enum', + values: [ + { name: 'Ascending', value: 'asc' }, + { name: 'Descending', value: 'desc' } + ], + value : 'asc', + description: 'Field to sort subreddit shortcuts by' + } +*/ }, + description: 'Allows you to customize the top bar with your own subreddit shortcuts, including dropdown menus of multi-reddits and more.', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/.*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + RESUtils.addCSS('.srOver { outline: 1px dashed black; }'); + RESUtils.addCSS('body { overflow-x: hidden; }'); + RESUtils.addCSS('#sr-header-area a { font-size: 100% !important; }'); + RESUtils.addCSS('#srList { position: absolute; top: 18px; left: 0; z-index: 9999; display: none; border: 1px solid black; background-color: #FAFAFA; width: auto; overflow-y: auto; }'); + RESUtils.addCSS('#srList tr { border-bottom: 1px solid gray; }'); + RESUtils.addCSS('#srList thead td { cursor: pointer; }'); + RESUtils.addCSS('#srList td { padding: 3px 8px; }'); + RESUtils.addCSS('#srList td.RESvisited, #srList td.RESshortcut { text-transform: none; }'); + RESUtils.addCSS('#srList td.RESshortcut {cursor: pointer;}'); + RESUtils.addCSS('#srList td a { width: 100%; display: block; }'); + RESUtils.addCSS('#srList tr:hover { background-color: #eef; }'); + RESUtils.addCSS('#srLeftContainer, #RESStaticShortcuts, #RESShortcuts, #srDropdown { display: inline; float: left; position: relative; z-index: 5; }'); + RESUtils.addCSS('#editShortcutDialog { display: none; z-index: 999; position: absolute; top: 25px; left: 5px; width: 230px; padding: 10px; background-color: #f0f3fc; border: 1px solid #c7c7c7; border-radius: 3px; font-size: 12px; color: #000; }'); + RESUtils.addCSS('#editShortcutDialog h3 { display: inline-block; float: left; font-size: 13px; margin-top: 6px; }'); + RESUtils.addCSS('#editShortcutClose { float: right; margin-top: 2px; margin-right: 0; }'); + RESUtils.addCSS('#editShortcutDialog label { clear: both; float: left; width: 100px; margin-top: 12px; }'); + RESUtils.addCSS('#editShortcutDialog input { float: left; width: 126px; margin-top: 10px; }'); + RESUtils.addCSS('#editShortcutDialog input[type=button] { float: right; width: 45px; margin-left: 10px; cursor: pointer; padding: 3px 5px; font-size: 12px; color: #fff; border: 1px solid #636363; border-radius: 3px; background-color: #5cc410; }'); RESUtils.addCSS( '#srLeftContainer { z-index: 4; padding-left: 4px; margin-right: 6px; }' ); // RESUtils.addCSS('#RESShortcuts { position: absolute; left: '+ this.srLeftContainerWidth+'px; z-index: 6; white-space: nowrap; overflow-x: hidden; padding-left: 2px; margin-top: -2px; padding-top: 2px; }'); + RESUtils.addCSS( '#srLeftContainer { z-index: 4; padding-left: 4px; margin-right: 6px; }' ); + RESUtils.addCSS('#RESShortcutsViewport { width: auto; max-height: 20px; overflow: hidden; } '); + RESUtils.addCSS('#RESShortcuts { z-index: 6; white-space: nowrap; overflow-x: hidden; padding-left: 2px; }'); + RESUtils.addCSS('#RESSubredditGroupDropdown { display: none; position: absolute; z-index: 99999; padding: 3px; background-color: #F0F0F0; border-left: 1px solid black; border-right: 1px solid black; border-bottom: 1px solid black; }'); + RESUtils.addCSS('#RESSubredditGroupDropdown li { padding-left: 3px; padding-right: 3px; margin-bottom: 2px; }'); + RESUtils.addCSS('#RESSubredditGroupDropdown li:hover { background-color: #F0F0FC; }'); + + RESUtils.addCSS('#RESShortcutsEditContainer { width: 69px; position: absolute; right: 0; top: 0; z-index: 999; background-color: #f0f0f0; height: 16px; user-select: none; -webkit-user-select: none; -moz-user-select: none; }'); + RESUtils.addCSS('#RESShortcutsRight { right: 0; }'); + RESUtils.addCSS('#RESShortcutsAdd { right: 15px; }'); + RESUtils.addCSS('#RESShortcutsLeft { right: 31px; }'); + RESUtils.addCSS('#RESShortcutsSort { right: 47px; }'); + + RESUtils.addCSS('#RESShortcutsSort, #RESShortcutsRight, #RESShortcutsLeft, #RESShortcutsAdd, #RESShortcutsTrash { width: 16px; cursor: pointer; background: #F0F0F0; font-size: 20px; color: #369; height: 18px; line-height: 15px; position: absolute; top: 0; z-index: 999; background-color: #f0f0f0; user-select: none; -webkit-user-select: none; -moz-user-select: none; } '); + RESUtils.addCSS('#RESShortcutsSort { font-size: 14px; }') + RESUtils.addCSS('#RESShortcutsTrash { display: none; font-size: 17px; width: 16px; cursor: pointer; right: 15px; height: 16px; position: absolute; top: 0; z-index: 1000; user-select: none; -webkit-user-select: none; -moz-user-select: none; }'); + RESUtils.addCSS('.srSep { margin-left: 6px; }'); + RESUtils.addCSS('.RESshortcutside { margin-right: 5px; margin-top: 2px; color: white; background-image: url(http://www.redditstatic.com/bg-button-add.png); cursor: pointer; text-align: center; width: 68px; font-weight: bold; font-size: 10px; border: 1px solid #444; padding: 1px 6px; border-radius: 3px 3px 3px 3px; }'); + RESUtils.addCSS('.RESshortcutside.remove { background-image: url(http://www.redditstatic.com/bg-button-remove.png) }'); + RESUtils.addCSS('.RESshortcutside:hover { background-color: #f0f0ff; }'); + // RESUtils.addCSS('h1.redditname > a { float: left; }'); + RESUtils.addCSS('h1.redditname { overflow: auto; }'); + RESUtils.addCSS('.sortAsc, .sortDesc { float: right; background-image: url("http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png"); width: 12px; height: 12px; background-repeat: no-repeat; }'); + RESUtils.addCSS('.sortAsc { background-position: 0 -149px; }'); + RESUtils.addCSS('.sortDesc { background-position: -12px -149px; }'); + RESUtils.addCSS('#RESShortcutsAddFormContainer { display: none; position: absolute; width: 290px; padding: 2px; right: 0; top: 21px; z-index: 10000; background-color: #f0f3fc; border: 1px solid #c7c7c7; border-radius: 3px; font-size: 12px; color: #000; }'); + RESUtils.addCSS('#RESShortcutsAddFormContainer a { font-weight: bold; }'); + RESUtils.addCSS('#newShortcut { width: 130px; }'); + RESUtils.addCSS('#displayName { width: 130px; }'); + RESUtils.addCSS('#shortCutsAddForm { padding: 5px; }'); + RESUtils.addCSS('#shortCutsAddForm div { font-size: 10px; margin-bottom: 10px; }'); + RESUtils.addCSS('#shortCutsAddForm label { display: inline-block; width: 100px; }'); + RESUtils.addCSS('#shortCutsAddForm input[type=text] { width: 170px; margin-bottom: 6px; }'); + RESUtils.addCSS('#addSubreddit { float: right; cursor: pointer; padding: 3px 5px; font-size: 12px; color: #fff; border: 1px solid #636363; border-radius: 3px; background-color: #5cc410; }'); + RESUtils.addCSS('.RESShortcutsCurrentSub { color:orangered!important; font-weight:bold; }'); + RESUtils.addCSS('.RESShortcutsCurrentSub:visited { color:orangered!important; font-weight:bold; }'); + RESUtils.addCSS('#srLeftContainer, #RESShortcutsViewport, #RESShortcutsEditContainer{max-height:18px;}'); + + // this shows the sr-header-area that we hid while rendering it (to curb opera's glitchy "jumping")... + if (BrowserDetect.isOpera()) { + RESUtils.addCSS('#sr-header-area { display: block !important; }'); + } + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + + if (this.options.linkMyRandom.value) { + var originalMyRandom = document.querySelector('#sr-header-area a[href$="/r/myrandom/"]') + if (originalMyRandom) { + this.myRandomEnabled = true; + if (originalMyRandom.classList.contains('gold')) { + this.myRandomGold = true; + } + } + } + + this.manageSubreddits(); + if (RESUtils.currentSubreddit() !== null) { + this.setLastViewtime(); + } + } + }, + manageSubreddits: function() { + // This is the init function for Manage Subreddits - it'll get your preferences and redraw the top bar. + this.redrawSubredditBar(); + // Listen for subscriptions / unsubscriptions from reddits so we know to reload the JSON string... + // also, add a +/- shortcut button... + if ((RESUtils.currentSubreddit()) && (this.options.subredditShortcut.value == true)) { + var subButtons = document.querySelectorAll('.fancy-toggle-button'); + // for (var h=0, len=currentSubreddits.length; h'); + $(subButton).wrap(subButtonsWrapper); + // move this wrapper to the end (after any icons that may exist...) + if (isMulti) { + var theWrap = $(subButton).parent(); + $(theWrap).appendTo($(theWrap).parent()); + } + } + subButton.addEventListener('click', function() { + // reset the last checked time for the subreddit list so that we refresh it anew no matter what. + RESStorage.setItem('RESmodules.subredditManager.subreddits.lastCheck.'+RESUtils.loggedInUser(),0); + },false); + var theSC = document.createElement('span'); + theSC.setAttribute('style','display: inline-block !important;'); + theSC.setAttribute('class','RESshortcut RESshortcutside'); + theSC.setAttribute('data-subreddit',thisSubredditFragment); + var idx = -1; + for (var i=0, sublen=modules['subredditManager'].mySubredditShortcuts.length; i') + .css({ display: 'inline-block', 'margin-right': '0'}) + .addClass('RESshortcut RESshortcutside') + .data('subreddit', subreddit), + isShortcut = false; + + for (var j = 0, shortcutsLength = modules['subredditManager'].mySubredditShortcuts.length; j < shortcutsLength; j++) { + if (modules['subredditManager'].mySubredditShortcuts[j].subreddit === subreddit) { + isShortcut = true; + break; + } + } + + if (isShortcut) { + $theSC + .attr('title', 'Remove this subreddit from your shortcut bar') + .text('-shortcut') + .addClass('remove'); + } else { + $theSC + .attr('title', 'Add this subreddit to your shortcut bar') + .text('+shortcut') + .removeClass('remove'); + } + + $theSC + .on('click', modules['subredditManager'].toggleSubredditShortcut) + .appendTo($(this).find('.midcol')); + }); + }, + redrawShortcuts: function() { + this.shortCutsContainer.textContent = ''; + // Try Refresh subreddit shortcuts + if (this.mySubredditShortcuts.length === 0) { + this.getLatestShortcuts(); + } + if (this.mySubredditShortcuts.length > 0) { + // go through the list of shortcuts and print them out... + for (var i = 0, len = this.mySubredditShortcuts.length; i < len; i++) { + if (typeof this.mySubredditShortcuts[i] === 'string') { + this.mySubredditShortcuts[i] = { + subreddit: this.mySubredditShortcuts[i], + displayName: this.mySubredditShortcuts[i], + addedDate: new Date() + } + } + + var thisShortCut = document.createElement('a'); + thisShortCut.setAttribute('draggable', 'true'); + thisShortCut.setAttribute('orderIndex', i); + thisShortCut.setAttribute('data-subreddit', this.mySubredditShortcuts[i].subreddit); + thisShortCut.classList.add('subbarlink'); + + if ((RESUtils.currentSubreddit() !== null) && (RESUtils.currentSubreddit().toLowerCase() === this.mySubredditShortcuts[i].subreddit.toLowerCase())) { + thisShortCut.classList.add('RESShortcutsCurrentSub'); + } + + thisShortCut.setAttribute('href','/r/'+this.mySubredditShortcuts[i].subreddit); + thisShortCut.textContent = this.mySubredditShortcuts[i].displayName; + thisShortCut.addEventListener('click', function(e) { + if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey) { + // open in new tab, let the browser handle it + return true; + } else { + e.preventDefault(); + // use to open links in new tabs... work on this later... + modules['subredditManager'].clickedShortcut = e.target.getAttribute('href'); + if (typeof modules['subredditManager'].clickTimer === 'undefined') { + modules['subredditManager'].clickTimer = setTimeout(modules['subredditManager'].followSubredditShortcut, 300); + } + } + }, false); + + thisShortCut.addEventListener('dblclick', function(e) { + e.preventDefault(); + clearTimeout(modules['subredditManager'].clickTimer); + delete modules['subredditManager'].clickTimer; + modules['subredditManager'].editSubredditShortcut(e.target); + }, false); + + thisShortCut.addEventListener('mouseover', function(e) { + clearTimeout(modules['subredditManager'].hideSubredditGroupDropdownTimer); + if ((typeof e.target.getAttribute !== 'undefined') && (e.target.getAttribute('href').indexOf('+') !== -1)) { + var subreddits = e.target.getAttribute('href').replace('/r/','').split('+'); + modules['subredditManager'].showSubredditGroupDropdown(subreddits, e.target); + } + }, false); + + thisShortCut.addEventListener('mouseout', function(e) { + modules['subredditManager'].hideSubredditGroupDropdownTimer = setTimeout(function() { + modules['subredditManager'].hideSubredditGroupDropdown(); + }, 500); + }, false); + + thisShortCut.addEventListener('dragstart', modules['subredditManager'].subredditDragStart, false); + thisShortCut.addEventListener('dragenter', modules['subredditManager'].subredditDragEnter, false) + thisShortCut.addEventListener('dragover', modules['subredditManager'].subredditDragOver, false); + thisShortCut.addEventListener('dragleave', modules['subredditManager'].subredditDragLeave, false); + thisShortCut.addEventListener('drop', modules['subredditManager'].subredditDrop, false); + thisShortCut.addEventListener('dragend', modules['subredditManager'].subredditDragEnd, false); + this.shortCutsContainer.appendChild(thisShortCut); + + if (i < len - 1) { + var sep = document.createElement('span'); + sep.setAttribute('class', 'separator'); + sep.textContent = '-'; + this.shortCutsContainer.appendChild(sep); + } + } + if (this.mySubredditShortcuts.length === 0) { + this.shortCutsContainer.style.textTransform = 'none'; + this.shortCutsContainer.textContent = 'add shortcuts from the my subreddits menu at left or click the button by the subreddit name, drag and drop to sort'; + } else { + this.shortCutsContainer.style.textTransform = ''; + } + } else { + this.shortCutsContainer.style.textTransform = 'none'; + this.shortCutsContainer.textContent = 'add shortcuts from the my subreddits menu at left or click the button by the subreddit name, drag and drop to sort'; + this.mySubredditShortcuts = []; + } + }, + showSubredditGroupDropdown: function(subreddits, obj) { + if (typeof this.subredditGroupDropdown === 'undefined') { + this.subredditGroupDropdown = createElementWithID('div', 'RESSubredditGroupDropdown'); + this.subredditGroupDropdownUL = document.createElement('ul'); + this.subredditGroupDropdown.appendChild(this.subredditGroupDropdownUL); + document.body.appendChild(this.subredditGroupDropdown); + + this.subredditGroupDropdown.addEventListener('mouseout', function(e) { + modules['subredditManager'].hideSubredditGroupDropdownTimer = setTimeout(function() { + modules['subredditManager'].hideSubredditGroupDropdown(); + }, 500); + }, false); + + this.subredditGroupDropdown.addEventListener('mouseover', function(e) { + clearTimeout(modules['subredditManager'].hideSubredditGroupDropdownTimer); + }, false); + } + this.groupDropdownVisible = true; + + if (subreddits) { + $(this.subredditGroupDropdownUL).html(''); + + for (var i=0, len=subreddits.length; i'+subreddits[i]+''); + $(this.subredditGroupDropdownUL).append(thisLI); + } + + var thisXY = RESUtils.getXYpos(obj); + this.subredditGroupDropdown.style.top = (thisXY.y + 16) + 'px'; + // if fixed, override y to just be the height of the subreddit bar... + // this.subredditGroupDropdown.style.position = 'fixed'; + // this.subredditGroupDropdown.style.top = '20px'; + this.subredditGroupDropdown.style.left = thisXY.x + 'px'; + this.subredditGroupDropdown.style.display = 'block'; + + modules['styleTweaks'].setSRStyleToggleVisibility(false, "subredditGroupDropdown"); + } + }, + hideSubredditGroupDropdown: function() { + delete modules['subredditManager'].hideSubredditGroupDropdownTimer; + if (this.subredditGroupDropdown) { + this.subredditGroupDropdown.style.display = 'none'; + modules['styleTweaks'].setSRStyleToggleVisibility(true, "subredditGroupDropdown") + } + }, + editSubredditShortcut: function(ele) { + var subreddit = ele.getAttribute('href').slice(3); + + var idx; + for (var i=0, len=modules['subredditManager'].mySubredditShortcuts.length; i \ +
        \ + \ + \ + \ + \ + '; + $(this.editShortcutDialog).html(thisForm); + + this.subredditInput = this.editShortcutDialog.querySelector('input[name=subreddit]'); + this.displayNameInput = this.editShortcutDialog.querySelector('input[name=displayName]'); + + this.subredditForm = this.editShortcutDialog.querySelector('FORM'); + this.subredditForm.addEventListener('submit', function(e) { + e.preventDefault(); + }, false); + + this.saveButton = this.editShortcutDialog.querySelector('input[name=shortcut-save]'); + this.saveButton.addEventListener('click', function(e) { + var idx = modules['subredditManager'].editShortcutDialog.querySelector('input[name=idx]').value; + var subreddit = modules['subredditManager'].editShortcutDialog.querySelector('input[name=subreddit]').value; + var displayName = modules['subredditManager'].editShortcutDialog.querySelector('input[name=displayName]').value; + + if ((subreddit === '') || (displayName === '')) { + // modules['subredditManager'].mySubredditShortcuts.splice(idx,1); + subreddit = modules['subredditManager'].mySubredditShortcuts[idx].subreddit; + modules['subredditManager'].removeSubredditShortcut(subreddit); + } else { + if (RESUtils.proEnabled()) { + // store a delete for the old subreddit, and an add for the new. + var oldsubreddit = modules['subredditManager'].mySubredditShortcuts[idx].subreddit; + if (typeof modules['subredditManager'].RESPro === 'undefined') { + if (RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.'+RESUtils.loggedInUser()) !== null) { + var temp = safeJSON.parse(RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.'+RESUtils.loggedInUser()), 'RESmodules.subredditManager.subredditShortcuts.RESPro.'+RESUtils.loggedInUser()); + } else { + var temp = { add: {}, del: {} }; + } + modules['subredditManager'].RESPro = temp; + } + if (typeof modules['subredditManager'].RESPro.add === 'undefined') { + modules['subredditManager'].RESPro.add = {} + } + if (typeof modules['subredditManager'].RESPro.del === 'undefined') { + modules['subredditManager'].RESPro.del = {} + } + // add modules['subredditManager'] new subreddit next time we sync... + modules['subredditManager'].RESPro.add[subreddit] = true; + // delete the old one + modules['subredditManager'].RESPro.del[oldsubreddit] = true; + // make sure we don't run an add on the old subreddit next time we sync... + if (typeof modules['subredditManager'].RESPro.add[oldsubreddit] !== 'undefined') delete modules['subredditManager'].RESPro.add[oldsubreddit]; + // make sure we don't run a delete on the new subreddit next time we sync... + if (typeof modules['subredditManager'].RESPro.del[subreddit] !== 'undefined') delete modules['subredditManager'].RESPro.del[subreddit]; + RESStorage.setItem('RESmodules.subredditManager.subredditShortcuts.RESPro.'+RESUtils.loggedInUser(), JSON.stringify(modules['subredditManager'].RESPro)); + } + modules['subredditManager'].mySubredditShortcuts[idx] = { + subreddit: subreddit, + displayName: displayName, + addedDate: new Date() + } + + modules['subredditManager'].saveLatestShortcuts(); + + if (RESUtils.proEnabled()) { + modules['RESPro'].saveModuleData('subredditManager'); + } + } + + modules['subredditManager'].editShortcutDialog.style.display = 'none'; + modules['subredditManager'].redrawShortcuts(); + modules['subredditManager'].populateSubredditDropdown(); + }, false); + + // handle enter and escape keys in the dialog box... + this.subredditInput.addEventListener('keyup', function(e) { + if (e.keyCode === 27) { + modules['subredditManager'].editShortcutDialog.style.display = 'none'; + modules['subredditManager'].editShortcutDialog.blur(); + } else if (e.keyCode === 13) { + RESUtils.click(modules['subredditManager'].saveButton); + } + }, false); + this.displayNameInput.addEventListener('keyup', function(e) { + if (e.keyCode === 27) { + modules['subredditManager'].editShortcutDialog.style.display = 'none'; + modules['subredditManager'].editShortcutDialog.blur(); + } else if (e.keyCode === 13) { + RESUtils.click(modules['subredditManager'].saveButton); + } + }, false); + + var cancelButton = this.editShortcutDialog.querySelector('#editShortcutClose'); + cancelButton.addEventListener('click', function(e) { + modules['subredditManager'].editShortcutDialog.style.display = 'none'; + }, false); + + this.editShortcutDialog.style.display = 'block'; + var thisLeft = Math.min(RESUtils.mouseX, window.innerWidth - 300); + this.editShortcutDialog.style.left = thisLeft + 'px'; + + setTimeout(function() { + modules['subredditManager'].subredditInput.focus() + }, 200); + }, + followSubredditShortcut: function() { + if (BrowserDetect.isFirefox()) { + // stupid firefox... sigh... + location.href = location.protocol + '//' + location.hostname + modules['subredditManager'].clickedShortcut; + } else { + location.href = modules['subredditManager'].clickedShortcut; + } + }, + subredditDragStart: function(e) { + clearTimeout(modules['subredditManager'].clickTimer); + // Target (this) element is the source node. + this.style.opacity = '0.4'; + modules['subredditManager'].shortCutsTrash.style.display = 'block'; + modules['subredditManager'].dragSrcEl = this; + + e.dataTransfer.effectAllowed = 'move'; + // because Safari is stupid, we have to do this. + modules['subredditManager'].srDataTransfer = this.getAttribute('orderIndex') + ',' + $(this).data('subreddit'); + }, + subredditDragEnter: function(e) { + this.classList.add('srOver'); + return false; + }, + subredditDragOver: function(e) { + if (e.preventDefault) { + e.preventDefault(); // Necessary. Allows us to drop. + } + + // See the section on the DataTransfer object. + e.dataTransfer.dropEffect = 'move'; + return false; + }, + subredditDragLeave: function(e) { + this.classList.remove('srOver'); + return false; + }, + subredditDrop: function(e) { + // this/e.target is current target element. + if (e.stopPropagation) { + e.stopPropagation(); // Stops some browsers from redirecting. + } + + // Stops other browsers from redirecting. + e.preventDefault(); + + modules['subredditManager'].shortCutsTrash.style.display = 'none'; + // Don't do anything if dropping the same column we're dragging. + if (modules['subredditManager'].dragSrcEl !== this) { + if (e.target.getAttribute('id') !== 'RESShortcutsTrash') { + // get the order index of the src and destination to swap... + // var theData = e.dataTransfer.getData('text/html').split(','); + var theData = modules['subredditManager'].srDataTransfer.split(','); + var srcOrderIndex = parseInt(theData[0], 10); + var srcSubreddit = modules['subredditManager'].mySubredditShortcuts[srcOrderIndex]; + var destOrderIndex = parseInt(this.getAttribute('orderIndex'), 10); + var destSubreddit = modules['subredditManager'].mySubredditShortcuts[destOrderIndex]; + var rearranged = []; + var rearrangedI = 0; + + for (var i = 0, len = modules['subredditManager'].mySubredditShortcuts.length; i < len; i++) { + if ((i !== srcOrderIndex) && (i !== destOrderIndex)) { + rearranged[rearrangedI] = modules['subredditManager'].mySubredditShortcuts[i]; + rearrangedI++; + } else if (i === destOrderIndex) { + if (destOrderIndex > srcOrderIndex) { + // if dragging right, order dest first, src next. + rearranged[rearrangedI] = destSubreddit; + rearrangedI++; + rearranged[rearrangedI] = srcSubreddit; + rearrangedI++; + } else { + // if dragging left, order src first, dest next. + rearranged[rearrangedI] = srcSubreddit; + rearrangedI++; + rearranged[rearrangedI] = destSubreddit; + rearrangedI++; + } + } + } + + // save the updated order... + modules['subredditManager'].mySubredditShortcuts = rearranged; + modules['subredditManager'].saveLatestShortcuts(); + // redraw the shortcut bar... + modules['subredditManager'].redrawShortcuts(); + this.classList.remove('srOver'); + } else { + var theData = modules['subredditManager'].srDataTransfer.split(','); + var srcOrderIndex = parseInt(theData[0], 10); + var srcSubreddit = theData[1]; + modules['subredditManager'].removeSubredditShortcut(srcSubreddit); + } + } + return false; + }, + subredditDragEnd: function(e) { + modules['subredditManager'].shortCutsTrash.style.display = 'none'; + this.style.opacity = '1'; + return false; + }, + redrawSubredditBar: function() { + this.headerContents = document.querySelector('#sr-header-area'); + if (this.headerContents) { + // for opera, because it renders progressively and makes it look "glitchy", hide the header bar, then show it all at once with CSS. + // if (BrowserDetect.isOpera()) this.headerContents.style.display = 'none'; + // Clear out the existing stuff in the top bar first, we'll replace it with our own stuff. + $(this.headerContents).html(''); + + this.srLeftContainer = createElementWithID('div','srLeftContainer'); + this.srLeftContainer.setAttribute('class','sr-bar'); + + this.srDropdown = createElementWithID('div','srDropdown'); + this.srDropdownContainer = createElementWithID('div','srDropdownContainer'); + $(this.srDropdownContainer).html('My Subreddits'); + this.srDropdownContainer.addEventListener('click',modules['subredditManager'].toggleSubredditDropdown, false); + this.srDropdown.appendChild(this.srDropdownContainer); + + this.srList = createElementWithID('table','srList'); + var maxHeight = $(window).height() - 40; + $(this.srList).css('max-height', maxHeight + 'px'); + // this.srDropdownContainer.appendChild(this.srList); + document.body.appendChild(this.srList); + + this.srLeftContainer.appendChild(this.srDropdown); + var sep = document.createElement('span'); + sep.setAttribute('class','srSep'); + sep.textContent = '|'; + this.srLeftContainer.appendChild(sep); + + // now put in the shortcuts... + this.staticShortCutsContainer = document.createElement('div'); + this.staticShortCutsContainer.setAttribute('id','RESStaticShortcuts'); + /* this probably isn't the best way to give the option, since the mechanic is drag/drop for other stuff.. but it's much easier for now... */ + $(this.staticShortCutsContainer).html(''); + var specialButtonSelected = {}; + var subLower = (RESUtils.currentSubreddit()) ? RESUtils.currentSubreddit().toLowerCase() : 'home'; + specialButtonSelected[subLower] = 'RESShortcutsCurrentSub'; + + var shortCutsHTML = ''; + + if (this.options.linkDashboard.value) shortCutsHTML += '-DASHBOARD'; + if (this.options.linkFront.value) shortCutsHTML += '-FRONT'; + if (this.options.linkAll.value) shortCutsHTML += '-ALL'; + if (this.options.linkRandom.value) shortCutsHTML += '-RANDOM'; + if (this.options.linkMyRandom.value && this.myRandomEnabled) shortCutsHTML += '-MYRANDOM'; + if (this.options.linkRandNSFW.value) shortCutsHTML += '-RANDNSFW'; + + if (RESUtils.loggedInUser()) { + if (this.options.linkFriends.value) shortCutsHTML += '-FRIENDS'; + + var modmail = document.getElementById('modmail'); + if (modmail) { + if (this.options.linkMod.value) shortCutsHTML += '-MOD'; + if (this.options.linkModqueue.value) shortCutsHTML += '-MODQUEUE'; + } + } + $(this.staticShortCutsContainer).append(shortCutsHTML); + + this.srLeftContainer.appendChild(this.staticShortCutsContainer); + this.srLeftContainer.appendChild(sep); + this.headerContents.appendChild(this.srLeftContainer); + + this.shortCutsViewport = document.createElement('div'); + this.shortCutsViewport.setAttribute('id','RESShortcutsViewport'); + this.headerContents.appendChild(this.shortCutsViewport); + + this.shortCutsContainer = document.createElement('div'); + this.shortCutsContainer.setAttribute('id','RESShortcuts'); + this.shortCutsContainer.setAttribute('class','sr-bar'); + this.shortCutsViewport.appendChild(this.shortCutsContainer); + + this.shortCutsEditContainer = document.createElement('div'); + this.shortCutsEditContainer.setAttribute('id','RESShortcutsEditContainer'); + this.headerContents.appendChild(this.shortCutsEditContainer); + + // Add shortcut sorting arrow + this.sortShortcutsButton = document.createElement('div'); + this.sortShortcutsButton.setAttribute('id','RESShortcutsSort'); + this.sortShortcutsButton.setAttribute('title','sort subreddit shortcuts'); + this.sortShortcutsButton.innerHTML = '↑↓'; + this.sortShortcutsButton.addEventListener('click', modules['subredditManager'].showSortMenu, false); + this.shortCutsEditContainer.appendChild(this.sortShortcutsButton); + + // add right scroll arrow... + this.shortCutsRight = document.createElement('div'); + this.shortCutsRight.setAttribute('id','RESShortcutsRight'); + this.shortCutsRight.textContent = '>'; + this.shortCutsRight.addEventListener('click', function(e) { + modules['subredditManager'].containerWidth = modules['subredditManager'].shortCutsContainer.offsetWidth; + var marginLeft = modules['subredditManager'].shortCutsContainer.firstChild.style.marginLeft; + marginLeft = parseInt(marginLeft.replace('px', ''), 10); + + if (isNaN(marginLeft)) marginLeft = 0; + + var shiftWidth = $('#RESShortcutsViewport').width() - 80; + if (modules['subredditManager'].containerWidth > (shiftWidth)) { + marginLeft -= shiftWidth; + modules['subredditManager'].shortCutsContainer.firstChild.style.marginLeft = marginLeft + 'px'; + } + }, false); + this.shortCutsEditContainer.appendChild(this.shortCutsRight); + + // add an "add shortcut" button... + this.shortCutsAdd = document.createElement('div'); + this.shortCutsAdd.setAttribute('id','RESShortcutsAdd'); + this.shortCutsAdd.textContent = '+'; + this.shortCutsAdd.title = 'add shortcut'; + this.shortCutsAddFormContainer = document.createElement('div'); + this.shortCutsAddFormContainer.setAttribute('id','RESShortcutsAddFormContainer'); + this.shortCutsAddFormContainer.style.display = 'none'; + var thisForm = ' \ +
        \ +
        Add shortcut or multi-reddit (i.e. foo+bar+baz):
        \ +
        \ +
        \ + \ + \ +
        \ + '; + $(this.shortCutsAddFormContainer).html(thisForm); + this.shortCutsAddFormField = this.shortCutsAddFormContainer.querySelector('#newShortcut'); + this.shortCutsAddFormFieldDisplayName = this.shortCutsAddFormContainer.querySelector('#displayName'); + + modules['subredditManager'].shortCutsAddFormField.addEventListener('keyup', function(e) { + if (e.keyCode === 27) { + modules['subredditManager'].shortCutsAddFormContainer.style.display = 'none'; + modules['subredditManager'].shortCutsAddFormField.blur(); + } + }, false); + + modules['subredditManager'].shortCutsAddFormFieldDisplayName.addEventListener('keyup', function(e) { + if (e.keyCode === 27) { + modules['subredditManager'].shortCutsAddFormContainer.style.display = 'none'; + modules['subredditManager'].shortCutsAddFormFieldDisplayName.blur(); + } + }, false); + + // add the "add shortcut" form... + this.shortCutsAddForm = this.shortCutsAddFormContainer.querySelector('#shortCutsAddForm'); + this.shortCutsAddForm.addEventListener('submit', function(e) { + e.preventDefault(); + var subreddit = modules['subredditManager'].shortCutsAddFormField.value; + var displayname = modules['subredditManager'].shortCutsAddFormFieldDisplayName.value; + if (displayname === '') displayname = subreddit; + + var r_match_regex = /^(\/r\/|r\/)(.*)/i; + if(r_match_regex.test(subreddit)) { + subreddit = subreddit.match(r_match_regex)[2]; + } + + modules['subredditManager'].shortCutsAddFormField.value = ''; + modules['subredditManager'].shortCutsAddFormFieldDisplayName.value = ''; + modules['subredditManager'].shortCutsAddFormContainer.style.display = 'none'; + + if (subreddit) { + modules['subredditManager'].addSubredditShortcut(subreddit, displayname); + } + }, false); + this.shortCutsAdd.addEventListener('click', function(e) { + if (modules['subredditManager'].shortCutsAddFormContainer.style.display === 'none') { + modules['subredditManager'].shortCutsAddFormContainer.style.display = 'block'; + modules['subredditManager'].shortCutsAddFormField.focus(); + } else { + modules['subredditManager'].shortCutsAddFormContainer.style.display = 'none'; + modules['subredditManager'].shortCutsAddFormField.blur(); + } + }, false); + this.shortCutsEditContainer.appendChild(this.shortCutsAdd); + document.body.appendChild(this.shortCutsAddFormContainer); + + // add the "trash bin"... + this.shortCutsTrash = document.createElement('div'); + this.shortCutsTrash.setAttribute('id','RESShortcutsTrash'); + this.shortCutsTrash.textContent = '×'; + this.shortCutsTrash.addEventListener('dragenter', modules['subredditManager'].subredditDragEnter, false) + this.shortCutsTrash.addEventListener('dragleave', modules['subredditManager'].subredditDragLeave, false); + this.shortCutsTrash.addEventListener('dragover', modules['subredditManager'].subredditDragOver, false); + this.shortCutsTrash.addEventListener('drop', modules['subredditManager'].subredditDrop, false); + this.shortCutsEditContainer.appendChild(this.shortCutsTrash); + + // add left scroll arrow... + this.shortCutsLeft = document.createElement('div'); + this.shortCutsLeft.setAttribute('id','RESShortcutsLeft'); + this.shortCutsLeft.textContent = '<'; + this.shortCutsLeft.addEventListener('click', function(e) { + var marginLeft = modules['subredditManager'].shortCutsContainer.firstChild.style.marginLeft; + marginLeft = parseInt(marginLeft.replace('px', ''), 10); + + if (isNaN(marginLeft)) marginLeft = 0; + + var shiftWidth = $('#RESShortcutsViewport').width() - 80; + marginLeft += shiftWidth; + if (marginLeft <= 0) { + modules['subredditManager'].shortCutsContainer.firstChild.style.marginLeft = marginLeft + 'px'; + } + }, false); + this.shortCutsEditContainer.appendChild(this.shortCutsLeft); + + this.redrawShortcuts(); + } + }, + showSortMenu: function() { + // Add shortcut sorting menu if it doesn't exist in the DOM yet... + if (!modules['subredditManager'].sortMenu) { + modules['subredditManager'].sortMenu = + $('
        ' + + '

         sort by:

        ' + + 'display name' + + 'added date' + + '
        '); + + $(modules['subredditManager'].sortMenu).find('a').click(modules['subredditManager'].sortShortcuts); + + $(document.body).append(modules['subredditManager'].sortMenu); + } + var menu = modules['subredditManager'].sortMenu; + if ($(menu).is(':visible')) { + $(menu).hide(); + return; + } + var thisXY = $(modules['subredditManager'].sortShortcutsButton).offset(); + thisXY.left = thisXY.left - $(menu).width() + $(modules['subredditManager'].sortShortcutsButton).width(); + var thisHeight = $(modules['subredditManager'].sortShortcutsButton).height(); + + $(menu).css({ + top: thisXY.top + thisHeight, + left: thisXY.left + }).show(); + }, + hideSortMenu: function() { + var menu = modules['subredditManager'].sortMenu; + $(menu).hide(); + }, + sortShortcuts: function(e) { + modules['subredditManager'].hideSortMenu(); + + var sortingField = $(this).data('field'); + var asc = ! modules['subredditManager'].currentSort; + // toggle sort method... + modules['subredditManager'].currentSort = !modules['subredditManager'].currentSort; + // Make sure we have a valid list of shortucts + if (!modules['subredditManager'].mySubredditShortcuts) { + modules['subredditManager'].getLatestShortcuts(); + } + + modules['subredditManager'].mySubredditShortcuts = modules['subredditManager'].mySubredditShortcuts.sort(function(a, b) { + // var sortingField = field; // modules['subredditManager'].options.sortingField.value; + // var asc = order === 'asc'; // (modules['subredditManager'].options.sortingDirection.value === 'asc'); + var aField = a[sortingField]; + var bField = b[sortingField]; + if (typeof aField === 'string' && typeof bField === 'string') { + aField = aField.toLowerCase(); + bField = bField.toLowerCase(); + } + + if (aField === bField) { + return 0; + } else if (aField > bField) { + return (asc) ? 1 : -1; + } else { + return (asc) ? -1 : 1; + } + }); + + // Save shortcuts sort order + modules['subredditManager'].saveLatestShortcuts(); + + // Refresh shortcuts + modules['subredditManager'].redrawShortcuts(); + }, + toggleSubredditDropdown: function(e) { + e.stopPropagation(); + if (modules['subredditManager'].srList.style.display === 'block') { + modules['subredditManager'].srList.style.display = 'none'; + document.body.removeEventListener('click',modules['subredditManager'].toggleSubredditDropdown, false); + } else { + if (RESUtils.loggedInUser()) { + $(modules['subredditManager'].srList).html('Loading subreddits (may take a moment)...
        '); + if (!modules['subredditManager'].subredditPagesLoaded) { + modules['subredditManager'].subredditPagesLoaded = modules['subredditManager'].srList.querySelector('#subredditPagesLoaded'); + } + modules['subredditManager'].srList.style.display = 'block'; + modules['subredditManager'].getSubreddits(); + } else { + $(modules['subredditManager'].srList).html('You must be logged in to load your own list of subreddits. browse them all'); + modules['subredditManager'].srList.style.display = 'block'; + } + modules['subredditManager'].srList.addEventListener('click',modules['subredditManager'].stopDropDownPropagation, false); + document.body.addEventListener('click',modules['subredditManager'].toggleSubredditDropdown, false); + } + }, + stopDropDownPropagation: function(e) { + e.stopPropagation(); + }, + mySubreddits: [ + ], + mySubredditShortcuts: [ + ], + getSubredditJSON: function(after) { + var jsonURL = location.protocol + '//' + location.hostname + '/subreddits/mine/.json?app=res'; + if (after) jsonURL += '&after='+after; + GM_xmlhttpRequest({ + method: "GET", + url: jsonURL, + onload: function(response) { + var thisResponse = JSON.parse(response.responseText); + if ((typeof thisResponse.data !== 'undefined') && (typeof thisResponse.data.children !== 'undefined')) { + if (modules['subredditManager'].subredditPagesLoaded.innerHTML === '') { + modules['subredditManager'].subredditPagesLoaded.textContent = 'Pages loaded: 1'; + } else { + var pages = modules['subredditManager'].subredditPagesLoaded.innerHTML.match(/:\ ([\d]+)/); + modules['subredditManager'].subredditPagesLoaded.textContent = 'Pages loaded: ' + (parseInt(pages[1], 10)+1); + } + + var now = new Date(); + RESStorage.setItem('RESmodules.subredditManager.subreddits.lastCheck.'+RESUtils.loggedInUser(),now.getTime()); + + var subreddits = thisResponse.data.children; + for (var i = 0, len = subreddits.length; i < len; i++) { + var srObj = { + display_name: subreddits[i].data.display_name, + url: subreddits[i].data.url, + over18: subreddits[i].data.over18, + id: subreddits[i].data.id, + created: subreddits[i].data.created, + description: subreddits[i].data.description + } + modules['subredditManager'].mySubreddits.push(srObj); + } + + if (thisResponse.data.after) { + modules['subredditManager'].getSubredditJSON(thisResponse.data.after); + } else { + modules['subredditManager'].mySubreddits.sort(function(a, b) { + var adisp = a.display_name.toLowerCase(); + var bdisp = b.display_name.toLowerCase(); + if (adisp > bdisp) return 1; + if (adisp == bdisp) return 0; + return -1; + }); + + RESStorage.setItem('RESmodules.subredditManager.subreddits.' + RESUtils.loggedInUser(), JSON.stringify(modules['subredditManager'].mySubreddits)); + this.gettingSubreddits = false; + modules['subredditManager'].populateSubredditDropdown(); + } + } else { + // User is probably not logged in.. no subreddits found. + modules['subredditManager'].populateSubredditDropdown(null, true); + } + } + }); + + }, + getSubreddits: function() { + modules['subredditManager'].mySubreddits = []; + var lastCheck = parseInt(RESStorage.getItem('RESmodules.subredditManager.subreddits.lastCheck.'+RESUtils.loggedInUser()), 10) || 0; + var now = new Date(); + var check = RESStorage.getItem('RESmodules.subredditManager.subreddits.'+RESUtils.loggedInUser()); + + // 86400000 = 1 day + if (((now.getTime() - lastCheck) > 86400000) || !check || (check.length === 0)) { + if (!this.gettingSubreddits) { + this.gettingSubreddits = true; + this.getSubredditJSON(); + } + } else { + modules['subredditManager'].mySubreddits = safeJSON.parse(check, 'RESmodules.subredditManager.subreddits.'+RESUtils.loggedInUser()); + this.populateSubredditDropdown(); + } + }, + // if badJSON is true, then getSubredditJSON ran into an error... + populateSubredditDropdown: function(sortBy, badJSON) { + modules['subredditManager'].sortBy = sortBy || 'subreddit'; + $(modules['subredditManager'].srList).html(''); + // NOTE WE NEED TO CHECK LAST TIME THEY UPDATED THEIR SUBREDDIT LIST AND REPOPULATE... + + var theHead = document.createElement('thead'); + var theRow = document.createElement('tr'); + + modules['subredditManager'].srHeader = document.createElement('td'); + modules['subredditManager'].srHeader.addEventListener('click', function() { + if (modules['subredditManager'].sortBy === 'subreddit') { + modules['subredditManager'].populateSubredditDropdown('subredditDesc'); + } else { + modules['subredditManager'].populateSubredditDropdown('subreddit'); + } + }, false); + modules['subredditManager'].srHeader.textContent = 'subreddit'; + modules['subredditManager'].srHeader.setAttribute('width','200'); + + modules['subredditManager'].lvHeader = document.createElement('td'); + modules['subredditManager'].lvHeader.addEventListener('click', function() { + if (modules['subredditManager'].sortBy === 'lastVisited') { + modules['subredditManager'].populateSubredditDropdown('lastVisitedAsc'); + } else { + modules['subredditManager'].populateSubredditDropdown('lastVisited'); + } + }, false); + modules['subredditManager'].lvHeader.textContent = 'Last Visited'; + modules['subredditManager'].lvHeader.setAttribute('width','120'); + + var scHeader = document.createElement('td'); + $(scHeader).width(50); + $(scHeader).html('View all »'); + theRow.appendChild(modules['subredditManager'].srHeader); + theRow.appendChild(modules['subredditManager'].lvHeader); + theRow.appendChild(scHeader); + theHead.appendChild(theRow); + modules['subredditManager'].srList.appendChild(theHead); + + var theBody = document.createElement('tbody'); + if (!badJSON) { + var subredditCount = modules['subredditManager'].mySubreddits.length; + + if (typeof this.subredditsLastViewed === 'undefined') { + var check = RESStorage.getItem('RESmodules.subredditManager.subredditsLastViewed.'+RESUtils.loggedInUser()); + if (check) { + this.subredditsLastViewed = safeJSON.parse(check, 'RESmodules.subredditManager.subredditsLastViewed.'+RESUtils.loggedInUser()); + } else { + this.subredditsLastViewed = {}; + } + } + + // copy modules['subredditManager'].mySubreddits to a placeholder array so we can sort without modifying it... + var sortableSubreddits = modules['subredditManager'].mySubreddits; + if (sortBy === 'lastVisited') { + $(modules['subredditManager'].lvHeader).html('Last Visited
        '); + modules['subredditManager'].srHeader.textContent = 'subreddit'; + + sortableSubreddits.sort(function(a, b) { + var adisp = a.display_name.toLowerCase(); + var bdisp = b.display_name.toLowerCase(); + + (typeof modules['subredditManager'].subredditsLastViewed[adisp] === 'undefined') ? alv = 0 : alv = parseInt(modules['subredditManager'].subredditsLastViewed[adisp].last_visited, 10); + (typeof modules['subredditManager'].subredditsLastViewed[bdisp] === 'undefined') ? blv = 0 : blv = parseInt(modules['subredditManager'].subredditsLastViewed[bdisp].last_visited, 10); + + if (alv < blv) return 1; + if (alv == blv) { + if (adisp > bdisp) return 1; + return -1; + } + return -1; + }); + } else if (sortBy === 'lastVisitedAsc') { + $(modules['subredditManager'].lvHeader).html('Last Visited
        '); + modules['subredditManager'].srHeader.textContent = 'subreddit'; + + sortableSubreddits.sort(function(a, b) { + var adisp = a.display_name.toLowerCase(); + var bdisp = b.display_name.toLowerCase(); + + (typeof modules['subredditManager'].subredditsLastViewed[adisp] === 'undefined') ? alv = 0 : alv = parseInt(modules['subredditManager'].subredditsLastViewed[adisp].last_visited, 10); + (typeof modules['subredditManager'].subredditsLastViewed[bdisp] === 'undefined') ? blv = 0 : blv = parseInt(modules['subredditManager'].subredditsLastViewed[bdisp].last_visited, 10); + + if (alv > blv) return 1; + if (alv == blv) { + if (adisp > bdisp) return 1; + return -1; + } + return -1; + }); + } else if (sortBy === 'subredditDesc') { + modules['subredditManager'].lvHeader.textContent = 'Last Visited'; + $(modules['subredditManager'].srHeader).html('subreddit
        '); + + sortableSubreddits.sort(function(a,b) { + var adisp = a.display_name.toLowerCase(); + var bdisp = b.display_name.toLowerCase(); + + if (adisp < bdisp) return 1; + if (adisp == bdisp) return 0; + return -1; + }); + } else { + modules['subredditManager'].lvHeader.textContent = 'Last Visited'; + $(modules['subredditManager'].srHeader).html('subreddit
        '); + + sortableSubreddits.sort(function(a,b) { + var adisp = a.display_name.toLowerCase(); + var bdisp = b.display_name.toLowerCase(); + + if (adisp > bdisp) return 1; + if (adisp == bdisp) return 0; + return -1; + }); + } + for (var i=0; i'+escapeHTML(modules['subredditManager'].mySubreddits[i].display_name)+''); + theRow.appendChild(theSR); + + var theLV = document.createElement('td'); + theLV.textContent = dateString; + theLV.setAttribute('class','RESvisited'); + theRow.appendChild(theLV); + + var theSC = document.createElement('td'); + theSC.setAttribute('class','RESshortcut'); + theSC.setAttribute('data-subreddit',modules['subredditManager'].mySubreddits[i].display_name); + + var idx = -1; + for (var j = 0, len = modules['subredditManager'].mySubredditShortcuts.length; j < len; j++) { + if (modules['subredditManager'].mySubredditShortcuts[j].subreddit === modules['subredditManager'].mySubreddits[i].display_name) { + idx = j; + break; + } + } + + if (idx !== -1) { + theSC.addEventListener('click', function(e) { + if (e.stopPropagation) { + e.stopPropagation(); // Stops from triggering the click on the bigger box, which toggles this window closed... + } + + var subreddit = $(e.target).data('subreddit'); + modules['subredditManager'].removeSubredditShortcut(subreddit); + }, false); + + theSC.textContent = '-shortcut'; + } else { + theSC.addEventListener('click', function(e) { + if (e.stopPropagation) { + e.stopPropagation(); // Stops from triggering the click on the bigger box, which toggles this window closed... + } + + var subreddit = $(e.target).data('subreddit'); + modules['subredditManager'].addSubredditShortcut(subreddit); + }, false); + + theSC.textContent = '+shortcut'; + } + + theRow.appendChild(theSC); + theBody.appendChild(theRow); + } + } else { + var theTD = document.createElement('td'); + theTD.textContent = 'There was an error getting your subreddits. You may have third party cookies disabled by your browser. For this function to work, you\'ll need to add an exception for cookies from reddit.com'; + theTD.setAttribute('colspan','3'); + + var theRow = document.createElement('tr'); + theRow.appendChild(theTD); + theBody.appendChild(theRow); + } + + modules['subredditManager'].srList.appendChild(theBody); + }, + toggleSubredditShortcut: function(e) { + e.stopPropagation(); // Stops from triggering the click on the bigger box, which toggles this window closed... + + var isShortcut = false; + for (var i = 0, len = modules['subredditManager'].mySubredditShortcuts.length; i < len; i++) { + if (modules['subredditManager'].mySubredditShortcuts[i].subreddit.toLowerCase() === $(this).data('subreddit').toLowerCase()) { + isShortcut = true; + break; + } + } + + if (isShortcut) { + modules['subredditManager'].removeSubredditShortcut($(this).data('subreddit')); + $(this) + .attr('title', 'Add this subreddit to your shortcut bar') + .text('+shortcut') + .removeClass('remove'); + } else { + modules['subredditManager'].addSubredditShortcut($(this).data('subreddit')); + $(this) + .attr('title', 'Remove this subreddit from your shortcut bar') + .text('-shortcut') + .addClass('remove'); + } + + modules['subredditManager'].redrawShortcuts(); + }, + getLatestShortcuts: function() { + // re-retreive the latest data to ensure we're not losing info between tab changes... + var shortCuts = RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.' + RESUtils.loggedInUser()); + if (!shortCuts) { + shortCuts = '[]'; + } + + this.mySubredditShortcuts = safeJSON.parse(shortCuts, 'RESmodules.subredditManager.subredditShortcuts.' + RESUtils.loggedInUser()); + this.parseDates(); + }, + // JSON specification doesn't specify what to do with dates - so unstringify here + parseDates: function () { + for (var i = 0, len = this.mySubredditShortcuts.length; i < len; i++) { + this.mySubredditShortcuts[i].addedDate = this.mySubredditShortcuts[i].addedDate + ? new Date(this.mySubredditShortcuts[i].addedDate) + : new Date(0); + } + }, + saveLatestShortcuts: function() { + // Retreive the latest data to ensure we're not losing info + if (!modules['subredditManager'].mySubredditShortcuts) { + modules['subredditManager'].mySubredditShortcuts = []; + } + + RESStorage.setItem('RESmodules.subredditManager.subredditShortcuts.' + RESUtils.loggedInUser(), JSON.stringify(modules['subredditManager'].mySubredditShortcuts)); + }, + addSubredditShortcut: function(subreddit, displayname) { + modules['subredditManager'].getLatestShortcuts(); + + var idx = -1; + for (var i = 0, len=modules['subredditManager'].mySubredditShortcuts.length; i < len; i++) { + if (modules['subredditManager'].mySubredditShortcuts[i].subreddit.toLowerCase() === subreddit.toLowerCase()) { + idx = i; + break; + } + } + + if (idx !== -1) { + alert('Whoops, you already have a shortcut for that subreddit'); + } else { + displayname = displayname || subreddit; + var subredditObj = { + subreddit: subreddit, + displayName: displayname.toLowerCase(), + addedDate: new Date() + } + + modules['subredditManager'].mySubredditShortcuts.push(subredditObj); + if (RESUtils.proEnabled()) { + if (typeof modules['subredditManager'].RESPro === 'undefined') { + if (RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser())) { + var temp = safeJSON.parse(RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser()), 'RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser()); + } else { + var temp = { add: {}, del: {} }; + } + + modules['subredditManager'].RESPro = temp; + } + + if (typeof modules['subredditManager'].RESPro.add === 'undefined') { + modules['subredditManager'].RESPro.add = {} + } + + if (typeof modules['subredditManager'].RESPro.del === 'undefined') { + modules['subredditManager'].RESPro.del = {} + } + + // add this subreddit next time we sync... + modules['subredditManager'].RESPro.add[subreddit] = true; + + // make sure we don't run a delete on this subreddit next time we sync... + if (typeof modules['subredditManager'].RESPro.del[subreddit] !== 'undefined') delete modules['subredditManager'].RESPro.del[subreddit]; + + RESStorage.setItem('RESmodules.subredditManager.subredditShortcuts.RESPro.'+RESUtils.loggedInUser(), JSON.stringify(modules['subredditManager'].RESPro)); + } + + modules['subredditManager'].saveLatestShortcuts(); + modules['subredditManager'].redrawShortcuts(); + modules['subredditManager'].populateSubredditDropdown(); + + if (RESUtils.proEnabled()) { + modules['RESPro'].saveModuleData('subredditManager'); + } + + RESUtils.notification({ + moduleID: 'subredditManager', + message: 'Subreddit shortcut added. You can edit by double clicking, or trash by dragging to the trash can.' + }); + } + }, + removeSubredditShortcut: function(subreddit) { + this.getLatestShortcuts(); + + var idx = -1; + for (var i = 0, len = modules['subredditManager'].mySubredditShortcuts.length; i < len; i++) { + if (modules['subredditManager'].mySubredditShortcuts[i].subreddit.toLowerCase() === subreddit.toLowerCase()) { + idx = i; + break; + } + } + + if (idx !== -1) { + modules['subredditManager'].mySubredditShortcuts.splice(idx, 1); + + if (RESUtils.proEnabled()) { + if (typeof modules['subredditManager'].RESPro === 'undefined') { + if (RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser())) { + var temp = safeJSON.parse(RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser()), 'RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser()); + } else { + var temp = { add: {}, del: {} }; + } + + modules['subredditManager'].RESPro = temp; + } + if (typeof modules['subredditManager'].RESPro.add === 'undefined') { + modules['subredditManager'].RESPro.add = {} + } + if (typeof modules['subredditManager'].RESPro.del === 'undefined') { + modules['subredditManager'].RESPro.del = {} + } + + // delete this subreddit next time we sync... + modules['subredditManager'].RESPro.del[subreddit] = true; + + // make sure we don't run an add on this subreddit + if (typeof modules['subredditManager'].RESPro.add[subreddit] !== 'undefined') delete modules['subredditManager'].RESPro.add[subreddit]; + + RESStorage.setItem('RESmodules.subredditManager.subredditShortcuts.RESPro.' + RESUtils.loggedInUser(), JSON.stringify(modules['subredditManager'].RESPro)); + } + + modules['subredditManager'].saveLatestShortcuts(); + modules['subredditManager'].redrawShortcuts(); + modules['subredditManager'].populateSubredditDropdown(); + + if (RESUtils.proEnabled()) { + modules['RESPro'].saveModuleData('subredditManager'); + } + } + }, + setLastViewtime: function() { + var check = RESStorage.getItem('RESmodules.subredditManager.subredditsLastViewed.' + RESUtils.loggedInUser()); + + if (!check) { + this.subredditsLastViewed = {}; + } else { + this.subredditsLastViewed = safeJSON.parse(check, 'RESmodules.subredditManager.subredditsLastViewed.' + RESUtils.loggedInUser()); + } + + var now = new Date(); + var thisReddit = RESUtils.currentSubreddit().toLowerCase(); + this.subredditsLastViewed[thisReddit] = { + last_visited: now.getTime() + } + + RESStorage.setItem('RESmodules.subredditManager.subredditsLastViewed.' + RESUtils.loggedInUser(), JSON.stringify(this.subredditsLastViewed)); + }, + subscribeToSubreddit: function(subredditName, subscribe) { + // subredditName should look like t5_123asd + subscribe = subscribe !== false; // default to true + var userHash = RESUtils.loggedInUserHash(); + + var formData = new FormData(); + formData.append('sr', subredditName); + formData.append('action', subscribe ? 'sub' : 'unsub'); + formData.append('uh', userHash); + + GM_xmlhttpRequest({ + method: "POST", + url: location.protocol + "//"+location.hostname+"/api/subscribe?app=res", + data: formData + }); + + } + +}; // note: you NEED this semicolon at the end! + +// RES Pro needs some work still... not ready yet. +/* +modules['RESPro'] = { + moduleID: 'RESPro', + moduleName: 'RES Pro', + category: 'Pro Features', + options: { + // any configurable options you have go here... + // options must have a type and a value.. + // valid types are: text, boolean (if boolean, value must be true or false) + // for example: + username: { + type: 'text', + value: '', + description: 'Your RES Pro username' + }, + password: { + type: 'password', + value: '', + description: 'Your RES Pro password' + }, + syncFrequency: { + type: 'enum', + values: [ + { name: 'Hourly', value: '3600000' }, + { name: 'Daily', value: '86400000' }, + { name: 'Manual Only', value: '-1' } + ], + value: '86400000', + description: 'How often should RES automatically sync settings?' + } + }, + description: 'RES Pro allows you to sync settings and data to a server. It requires an account, which you can sign up for here', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/?/i, + /^https?:\/\/([a-z]+)\.reddit\.com\/[-\w\.\/]+/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // do stuff now! + // if we haven't synced in more than our settings, and settings != manual, sync! + if (this.options.syncFrequency.value > 0) { + var lastSync = parseInt(RESStorage.getItem('RESmodules.RESPro.lastSync')) || 0; + var now = new Date(); + if ((now.getTime() - lastSync) > this.options.syncFrequency.value) { + this.authenticate(this.autoSync); + } + } + + } + }, + autoSync: function() { + modules['RESPro'].authenticate(modules['RESPro'].savePrefs); + + // modules['RESPro'].authenticate(function() { + // modules['RESPro'].saveModuleData('saveComments'); + // }); + }, + saveModuleData: function(module) { + switch(module){ + case 'userTagger': + // THIS IS NOT READY YET! We need to merge votes on the backend.. hard stuff... + // in this case, we want to send the JSON from RESmodules.userTagger.tags; + var tags = RESStorage.getItem('RESmodules.userTagger.tags'); + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=PUT&type=module_data&module='+module+'&data='+tags, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + // console.log(resp); + if (resp.success) { + if (RESConsole.proUserTaggerSaveButton) RESConsole.proUserTaggerSaveButton.textContent = 'Saved!'; + } else { + alert(response.responseText); + } + } + }); + break; + case 'saveComments': + var savedComments = RESStorage.getItem('RESmodules.saveComments.savedComments'); + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=PUT&type=module_data&module='+module+'&data='+savedComments, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + // console.log(response.responseText); + var resp = JSON.parse(response.responseText); + if (resp.success) { + if (RESConsole.proSaveCommentsSaveButton) RESConsole.proSaveCommentsSaveButton.textContent = 'Saved!'; + var thisComments = safeJSON.parse(savedComments); + delete thisComments.RESPro_add; + delete thisComments.RESPro_delete; + thisComments = JSON.stringify(thisComments); + RESStorage.setItem('RESmodules.saveComments.savedComments',thisComments); + RESUtils.notification({ + header: 'RES Pro Notification', + message: 'Saved comments synced to server' + }); + } else { + alert(response.responseText); + } + } + }); + break; + case 'subredditManager': + var subredditManagerData = {}; + subredditManagerData.RESPro = {}; + + for (var key in RESStorage) { + // console.log(key); + if (key.indexOf('RESmodules.subredditManager') !== -1) { + var keySplit = key.split('.'); + var username = keySplit[keySplit.length-1]; + if ((keySplit.indexOf('subredditsLastViewed') === -1) && (keySplit.indexOf('subreddits') === -1)) { + // console.log(key); + (keySplit.indexOf('RESPro') !== -1) ? subredditManagerData.RESPro[username] = JSON.parse(RESStorage[key]) : subredditManagerData[username] = JSON.parse(RESStorage[key]); + // if (key.indexOf('RESPro') === -1) console.log(username + ' -- ' + RESStorage[key]); + if (key.indexOf('RESPro') !== -1) RESStorage.removeItem('RESmodules.subredditManager.subredditShortcuts.RESPro.'+username); + } + } + } + var stringData = JSON.stringify(subredditManagerData); + stringData = encodeURIComponent(stringData); + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=PUT&type=module_data&module='+module+'&data='+stringData, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + console.log(response.responseText); + var resp = JSON.parse(response.responseText); + if (resp.success) { + if (RESConsole.proSubredditManagerSaveButton) RESConsole.proSubredditManagerSaveButton.textContent = 'Saved!'; + RESUtils.notification({ + header: 'RES Pro Notification', + message: 'Subreddit shortcuts synced to server' + }); + } else { + alert(response.responseText); + } + } + }); + break; + default: + console.log('invalid module specified: ' + module); + break; + } + }, + getModuleData: function(module) { + switch(module){ + case 'saveComments': + if (RESConsole.proSaveCommentsGetButton) RESConsole.proSaveCommentsGetButton.textContent = 'Loading...'; + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=GET&type=module_data&module='+module, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + if (resp.success) { + var serverResponse = JSON.parse(response.responseText); + var serverData = serverResponse.data; + currentData = safeJSON.parse(RESStorage.getItem('RESmodules.saveComments.savedComments'), 'RESmodules.saveComments.savedComments'); + for (var attrname in serverData) { + if (typeof currentData[attrname] === 'undefined') { + currentData[attrname] = serverData[attrname]; + } + } + // console.log(JSON.stringify(prefsData)); + RESStorage.setItem('RESmodules.saveComments.savedComments', JSON.stringify(currentData)); + if (RESConsole.proSaveCommentsGetButton) RESConsole.proSaveCommentsGetButton.textContent = 'Saved Comments Loaded!'; + } else { + alert(response.responseText); + } + } + }); + break; + case 'subredditManager': + if (RESConsole.proSubredditManagerGetButton) RESConsole.proSubredditManagerGetButton.textContent = 'Loading...'; + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=GET&type=module_data&module='+module, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + if (resp.success) { + var serverResponse = JSON.parse(response.responseText); + var serverData = serverResponse.data; + for (var username in serverResponse.data) { + var newSubredditData = serverResponse.data[username]; + var oldSubredditData = safeJSON.parse(RESStorage.getItem('RESmodules.subredditManager.subredditShortcuts.'+username), 'RESmodules.subredditManager.subredditShortcuts.'+username); + if (oldSubredditData == null) oldSubredditData = []; + for (var newidx in newSubredditData) { + var exists = false; + for (var oldidx in oldSubredditData) { + if (oldSubredditData[oldidx].subreddit == newSubredditData[newidx].subreddit) { + oldSubredditData[oldidx].displayName = newSubredditData[newidx].displayName; + exists = true; + break; + } + } + if (!exists) { + oldSubredditData.push(newSubredditData[newidx]); + } + } + RESStorage.setItem('RESmodules.subredditManager.subredditShortcuts.'+username,JSON.stringify(oldSubredditData)); + } + } else { + alert(response.responseText); + } + } + }); + break; + default: + console.log('invalid module specified: ' + module); + break; + } + }, + savePrefs: function() { + // (typeof unsafeWindow !== 'undefined') ? ls = unsafeWindow.localStorage : ls = localStorage; + if (RESConsole.proSaveButton) RESConsole.proSaveButton.textContent = 'Saving...'; + var RESOptions = {}; + // for (var i = 0, len=ls.length; i < len; i++) { + for(var i in RESStorage) { + if ((typeof RESStorage.getItem(i) !== 'function') && (typeof RESStorage.getItem(i) !== 'undefined')) { + var keySplit = i.split('.'); + if (keySplit) { + var keyRoot = keySplit[0]; + switch (keyRoot) { + case 'RES': + var thisNode = keySplit[1]; + if (thisNode === 'modulePrefs') { + RESOptions[thisNode] = safeJSON.parse(RESStorage.getItem(i), i); + } + break; + case 'RESoptions': + var thisModule = keySplit[1]; + if (thisModule !== 'accountSwitcher') { + RESOptions[thisModule] = safeJSON.parse(RESStorage.getItem(i), i); + } + break; + default: + //console.log('Not currently handling keys with root: ' + keyRoot); + break; + } + } + } + } + // Post options blob. + var RESOptionsString = JSON.stringify(RESOptions); + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=PUT&type=all_options&data='+RESOptionsString, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + // console.log(resp); + if (resp.success) { + var now = new Date(); + RESStorage.setItem('RESmodules.RESPro.lastSync',now.getTime()); + if (RESConsole.proSaveButton) RESConsole.proSaveButton.textContent = 'Saved.'; + RESUtils.notification({ + header: 'RES Pro Notification', + message: 'RES Pro - module options saved to server.' + }); + } else { + alert(response.responseText); + } + } + }); + }, + getPrefs: function() { + console.log('get prefs called'); + if (RESConsole.proGetButton) RESConsole.proGetButton.textContent = 'Loading...'; + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESsync.php', + data: 'action=GET&type=all_options', + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + if (resp.success) { + var modulePrefs = JSON.parse(response.responseText); + var prefsData = modulePrefs.data; + //console.log('prefsData:'); + //console.log(prefsData); + for (var thisModule in prefsData){ + if (thisModule === 'modulePrefs') { + var thisOptions = prefsData[thisModule]; + RESStorage.setItem('RES.modulePrefs',JSON.stringify(thisOptions)); + } else { + var thisOptions = prefsData[thisModule]; + RESStorage.setItem('RESoptions.'+thisModule,JSON.stringify(thisOptions)); + } + } + if (RESConsole.proGetButton) RESConsole.proGetButton.textContent = 'Preferences Loaded!'; + RESUtils.notification({ + header: 'RES Pro Notification', + message: 'Module options loaded.' + }); + // console.log(response.responseText); + } else { + alert(response.responseText); + } + } + }); + }, + configure: function() { + if (!RESConsole.isOpen) RESConsole.open(); + RESConsole.menuClick(document.getElementById('Menu-'+this.category)); + RESConsole.drawConfigOptions('RESPro'); + }, + authenticate: function(callback) { + if (! this.isEnabled()) { + return false; + } else if ((modules['RESPro'].options.username.value === '') || (modules['RESPro'].options.password.value === '')) { + modules['RESPro'].configure(); + } else if (RESStorage.getItem('RESmodules.RESPro.lastAuthFailed') !== 'true') { + if (typeof modules['RESPro'].lastAuthFailed === 'undefined') { + GM_xmlhttpRequest({ + method: "POST", + url: 'http://reddit.honestbleeps.com/RESlogin.php', + data: 'uname='+modules['RESPro'].options.username.value+'&pwd='+modules['RESPro'].options.password.value, + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + onload: function(response) { + var resp = JSON.parse(response.responseText); + if (resp.success) { + // RESConsole.proAuthButton.textContent = 'Authenticated!'; + RESStorage.removeItem('RESmodules.RESPro.lastAuthFailed'); + if (callback) { + callback(); + } + } else { + // RESConsole.proAuthButton.textContent = 'Authentication failed.'; + modules['RESPro'].lastAuthFailed = true; + RESStorage.setItem('RESmodules.RESPro.lastAuthFailed','true'); + RESUtils.notification({ + header: 'RES Pro Notification', + message: 'Authentication failed - check your username and password.' + }); + } + } + }); + } + } + } +}; +*/ +modules['RESTips'] = { + moduleID: 'RESTips', + moduleName: 'RES Tips and Tricks', + category: 'UI', + options: { + // any configurable options you have go here... + // options must have a type and a value.. + // valid types are: text, boolean (if boolean, value must be true or false) + // for example: + dailyTip: { + type: 'boolean', + value: true, + description: 'Show a random tip once every 24 hours.' + } + }, + description: 'Adds tips/tricks help to RES console', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[\?]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if (this.isEnabled() && this.isMatchURL()) { + RESUtils.addCSS('.res-help { cursor: help; }'); + RESUtils.addCSS('.res-help #resHelp { cursor: default; }'); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + this.menuItem = createElementWithID('li','RESTipsMenuItem'); + this.menuItem.textContent = 'tips & tricks'; + this.menuItem.addEventListener('click', function(e) { + modules['RESTips'].randomTip(); + }, false); + + $('#RESDropdownOptions').append(this.menuItem); + + if (this.options.dailyTip.value) { + this.dailyTip(); + } + + /* + guiders.createGuider({ + attachTo: '#RESSettingsButton', + // buttons: [{name: "Next"}], + description: "Guiders are a user interface design pattern for introducing features of software. This dialog box, for example, is the first in a series of guiders that together make up a guide.", + id: "first", + // next: "second", + overlay: true, + xButton: true, + title: "Welcome to Guiders.js!" + }).show(); + */ + /* + setTimeout(function() { + guiders.createGuider({ + attachTo: "#RESSettingsButton", + buttons: [{name: "Close"}, + {name: "Next"}], + description: "This is just some sorta test guider, here... woop woop.", + id: "first", + next: "second", + // offset: { left: -200, top: 120 }, + position: 5, + title: "Guiders are typically attached to an element on the page." + }).show(); + guiders.createGuider({ + attachTo: "a.toggleImage:first", + buttons: [{name: "Close"}, + {name: "Next"}], + description: "An example of an image expando", + id: "second", + next: "third", + // offset: { left: -200, top: 120 }, + position: 3, + title: "Guiders are typically attached to an element on the page." + }); + }, 2000); + */ + } + }, + dailyTip: function() { + var lastCheck = parseInt(RESStorage.getItem('RESLastToolTip'), 10) || 0; + var now = new Date(); + // 86400000 = 1 day + if ((now.getTime() - lastCheck) > 86400000) { + // mark off that we've displayed a new tooltip + RESStorage.setItem('RESLastToolTip',now.getTime()); + if (lastCheck === 0) { + this.showTip(0); + } else { + setTimeout(function() { + modules['RESTips'].randomTip(); + }, 500); + } + } + }, + randomTip: function() { + this.currTip = Math.floor(Math.random()*this.tips.length); + this.showTip(this.currTip); + }, + disableDailyTipsCheckbox: function(e) { + modules['RESTips'].options.dailyTip.value = e.target.checked; + RESStorage.setItem('RESoptions.RESTips', JSON.stringify(modules['RESTips'].options)); + }, + nextTip: function() { + if (typeof this.currTip === 'undefined') this.currTip = 0; + modules['RESTips'].nextPrevTip(1); + }, + prevTip: function() { + if (typeof this.currTip === 'undefined') this.currTip = 0; + modules['RESTips'].nextPrevTip(-1); + }, + nextPrevTip: function(idx) { + if (typeof this.currTip === 'undefined') this.currTip = 0; + // if (idx<0) guiders.hideAll(); + guiders.hideAll(); + this.currTip += idx; + if (this.currTip < 0) { + this.currTip = this.tips.length-1; + } else if (this.currTip >= this.tips.length) { + this.currTip = 0; + } + this.showTip(this.currTip); + }, + generateContent: function(help, elem) { + var description = [] + + if (help.message) description.push(help.message); + + if (help.keyboard) { + + // TODO: microtemplate + var disabled = !modules['keyboardNav'].isEnabled(); + description.push('

        '); + description.push('Keyboard Navigation' + (disabled ? ' (disabled)' : '')); + description.push('

        '); + + var keyboardTable = RESUtils.generateTable(help.keyboard, this.generateContentKeyboard, elem); + if (keyboardTable) description.push(keyboardTable); + } + + if (help.option) { + description.push('

        '); + description.push(' RES Settings'); + description.push('

        '); + + var optionTable = RESUtils.generateTable(help.option, this.generateContentOption, elem); + if (optionTable) description.push(optionTable); + } + + description = description.join("\n"); + return description; + }, + generateContentKeyboard: function (keyboardNavOption, index, array, elem) { + var keyCode = modules['keyboardNav'].getNiceKeyCode(keyboardNavOption); + if (!keyCode) return; + + var description = []; + description.push(''); + description.push('' + keyCode.toLowerCase() + ''); + description.push('' + keyboardNavOption + ''); + description.push(''); + description.push(' '); // for styling + description.push('' + modules['keyboardNav'].options[keyboardNavOption].description + ''); + description.push(''); + + return description; + }, + generateContentOption: function (option, index, array, elem) { + var module = modules[option.moduleID]; + if (!module) return; + + var description = []; + + description.push(""); + description.push("" + module.category + ''); + + description.push(''); + description.push(modules['settingsNavigation'].makeUrlHashLink(option.moduleID, null, module.moduleName)); + description.push(''); + + description.push(''); + description.push(option.key + ? modules['settingsNavigation'].makeUrlHashLink(option.moduleID, option.key) + : ' '); + description.push(''); + + if (module.options[option.key]) { + description.push(''); + description.push('' + module.options[option.key].description + ''); + } + description.push(""); + + return description; + }, + consoleTip: { + message: "Roll over the gear icon and click 'settings console' to explore the RES settings. You can enable, disable or change just about anything you like/dislike about RES!

        Once you've opened the console once, this message will not appear again.", + attachTo: "#openRESPrefs", + position: 5 + }, + tips: [ + { + message: 'Welcome to RES. You can turn modules on and off, and configure settings for the modules using the gear icon link at the top right. For feature requests, or just help getting a question answered, be sure to subscribe to /r/Enhancement.', + attachTo: "#openRESPrefs", + position: 5 + }, + { + message: "Click the tag icon next to a user to tag that user with any name you like - you can also color code the tag.", + attachTo: ".RESUserTagImage:visible", + position: 3, + option: { moduleID: 'userTagger' } + }, + { + message: "Don't forget to subscribe to /r/Enhancement to keep up to date on the latest versions of RES or suggest features! For bug reports, submit to /r/RESIssues" + }, + { + message: "Don't want to see posts containing certain keywords? Want to filter out certain subreddits from /r/all? Try the filteReddit module!" , + option: { moduleID: 'filteReddit' } + }, + { + message: "Keyboard Navigation is one of the most underutilized features in RES. You should try it!" , + option: { moduleID: 'keyboardNav' }, + keyboard: 'toggleHelp' + }, + { + message: "Did you know you can configure the appearance of a number of things in RES? For example: keyboard navigation lets you configure the look of the 'selected' box, and commentBoxes lets you configure the borders / shadows." , + option: [ { moduleID: 'keyboardNav', key: 'focusBGColor' }, { moduleID: 'styleTweaks', key: 'commentBoxes' }] + }, + + { + message: "Do you subscribe to a ton of reddits? Give the subreddit tagger a try, it can make your homepage a bit more readable." , + option: { moduleID: 'subRedditTagger' } + }, + { + message: "If you haven't tried it yet, Keyboard Navigation is great. Just hit ? while browsing for instructions." , + option: { moduleID: 'keyboardNav' }, + keyboard: 'toggleHelp' + }, + { + message: "Roll over a user's name to get information about them such as their karma, and how long they've been a reddit user." , + option: { moduleID: 'userTagger', key: 'hoverInfo' } + }, + { + message: "Hover over the 'parent' link in comments pages to see the text of the parent being referred to." , + option: { moduleID: 'showParent' } + }, + { + message: "You can configure the color and style of the User Highlighter module if you want to change how the highlights look." , + option: { moduleID: 'userHighlight' } + }, + { + message: "Not a fan of how comments pages look? You can change the appearance in the Style Tweaks module" , + option: { moduleID: 'styleTweaks' } + }, + { + message: "Don't like the style in a certain subreddit? RES gives you a checkbox to disable styles individually - check the right sidebar!" + }, + { + message: "Looking for posts by submitter, post with photos, or posts in IAmA form? Try out the comment navigator." + }, + { + message: "Have you seen the RES Dashboard? It allows you to do all sorts of great stuff, like keep track of lower traffic subreddits, and manage your user tags and thread subscriptions!", + options: { moduleID: 'dashboard' } + }, + { + message: "Sick of seeing these tips? They only show up once every 24 hours, but you can disable that in the RES Tips and Tricks preferences.", + option: { moduleID: 'RESTips' } + }, + { + message: "Did you know that there is now a 'keep me logged in' option in the Account Switcher? Turn it on if you want to stay logged in to Reddit when using the switcher!", + option: { moduleID: 'accountSwitcher', key: 'keepLoggedIn' } + }, + { + message: "See that little [vw] next to users you've voted on? That's their vote weight - it moves up and down as you vote the same user up / down.", + option: { moduleID: 'userTagger', key: 'vwTooltip' } + } + ], + tour: [ + // array of guiders will go here... and we will add a "tour" button somewhere to start the tour... + ], + initTips: function() { + $('body').on('click', '#disableDailyTipsCheckbox', modules['RESTips'].disableDailyTipsCheckbox); + // create the special "you have never visited the console" guider... + this.createGuider(0, 'console'); + for (var i=0, len=this.tips.length; i= len) ? 0 : (parseInt(i+1, 10)); + var nextID = "tip"+nextidx; + var thisChecked = (modules['RESTips'].options.dailyTip.value) ? 'checked="checked"' : ''; + var guiderObj = { + attachTo: attachTo, + buttons: [{ + name: "Prev", + onclick: modules['RESTips'].prevTip + }, + { + name: "Next", + onclick: modules['RESTips'].nextTip + } + ], + description: description, + buttonCustomHTML: "", + id: thisID, + next: nextID, + onShow: modules['RESTips'].onShow, + onHide: modules['RESTips'].onHide, + position: this.tips[i].position, + xButton: true, + title: "RES Tips and Tricks" + }; + if (special === 'console') { + delete guiderObj.buttonCustomHTML; + delete guiderObj.next; + delete guiderObj.buttons; + + guiderObj.title = "RES is extremely configurable"; + + } + + guiders.createGuider(guiderObj); + }, + showTip: function(idx, special) { + if (typeof this.tipsInitialized === 'undefined') { + this.initTips(); + this.tipsInitialized = true; + } + if (!special) { + guiders.show('tip'+idx); + } else { + guiders.show('console'); + } + }, + onShow: function() { + modules['styleTweaks'].setSRStyleToggleVisibility(false, 'tipstricks'); + }, + onHide: function() { + modules['styleTweaks'].setSRStyleToggleVisibility(true, 'tipstricks'); + } +}; + + +modules['settingsNavigation'] = { + moduleID: 'settingsNavigation', + moduleName: 'RES Settings Navigation', + category: 'UI', + description: 'Helping you get around the RES Settings Console with greater ease', + hidden: true, + options: { + }, + isEnabled: function() { + // return RESConsole.getModulePrefs(this.moduleID); + return true; + }, + include: [ + /^https?:\/\/([-\w\.]+\.)?reddit\.com\/[-\w\.\/]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + RESUtils.addCSS('#RESSearchMenuItem { \ + display: block; \ + float: right; \ + margin: 7px; \ + width: 21px;height: 21px; \ + border: 1px #c9def2 solid; \ + border-radius: 3px; \ + background: transparent center center no-repeat; \ + background-image: ' + this.searchButtonIcon + '; \ + } '); + RESUtils.addCSS('li:hover > #RESSearchMenuItem { \ + border-color: #369; \ + background-image: ' + this.searchButtonIconHover + '; \ + }'); + }, + go: function() { + RESUtils.addCSS(modules['settingsNavigation'].css); + this.menuItem = createElementWithID('i','RESSearchMenuItem'); + this.menuItem.setAttribute('title', 'search settings'); + this.menuItem.addEventListener('click', function(e) { + modules['settingsNavigation'].showSearch() + }, false); + RESConsole.settingsButton.appendChild(this.menuItem); + + if (!(this.isEnabled() && this.isMatchURL())) return; + + window.addEventListener('hashchange', modules['settingsNavigation'].onHashChange); + window.addEventListener('popstate', modules['settingsNavigation'].onPopState); + setTimeout(modules['settingsNavigation'].onHashChange, 300); // for initial pageload; wait until after RES has completed loading + + this.consoleTip(); + }, + searchButtonIcon: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAONJREFUOBGlkz0KwkAQRo2ICt5IsBE9gI1dwAOk8AqCgufQPo2CYGlhIVh4Aj2ClYVg4hvdCclmI8gOPHbn78vsLgnSNK35WN2nWXq9BRoVEwyIj6ANO4ghgbLJHVjM8BM4wwGesIYm2LU1O9ClSCwCzfXZi8gkF9NcSWBB0dVRGBPbOOLOSww4qJA38d3vbanqEabEA5Mbsj4gNH42vvgFxxTIJYpd4AgvcbAVdKDQ8/lKflaz12ds4e+hBxFsIYQ7fM1W/OHPyYktIZuiagLVt9cxgRucNPGvgPZlq/e/4C3wBoAXSrzY2Qd2AAAAAElFTkSuQmCC')", + searchButtonIconHover: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAPBJREFUOBGlkTEKAjEQRTciIth4HkvRA9jYCR7AwisICvZiZ6X9NlvYW1gIFp5AT2Eh6PpGMjLuBkUy8JmZ5P+fZOLyPE9iohIjFm20QTV0A+dch/UeqIMtSHnqg1wOmYEFjAkQ8hHswA1sQM3ytC6KWxBlqqM3IUna9GIy1DWbiwYziGdLkJpIQVZclz40REbgnKhMSB/+b+sKSZ8wpnb+9C71FQwsV+uPJ3iBDFFOO4E9uPt+TW6oUHPJwJvINy7BCvTBAohpBpoqfnFt861GOPUmc8vTd7L3O5it3OaCwUHZfxmoyObQN9r9n3W0wRPmWv0jZnGemgAAAABJRU5ErkJggg==')", + consoleTip: function() { + // first, ensure that we've at least run dailyTip once (so RES first-run has happened)... + var lastToolTip = RESStorage.getItem('RESLastToolTip'); + if (lastToolTip) { + // if yes, see if the user has ever opened the settings console. + var hasOpenedConsole = RESStorage.getItem('RESConsole.hasOpenedConsole'); + if (!hasOpenedConsole) { + // if no, nag them once daily that the console exists until they use it. Once it's been opened, this check will never run again. + var lastCheckDailyTip = parseInt(RESStorage.getItem('RESLastToolTip'), 10) || 0; + var now = new Date(); + // 86400000 = 1 day - remind users once a day if they've never opened the settings that they should check out the console sometime... + var lastCheck = parseInt(RESStorage.getItem('RESConsole.hasOpenedCheck'), 10) || 0; + if (((now.getTime() - lastCheckDailyTip) > 1000) && ((now.getTime() - lastCheck) > 86400000)) { + RESStorage.setItem('RESConsole.hasOpenedCheck', now.getTime()); + modules['RESTips'].showTip(0,'console'); + } + } + } + }, + makeUrlHashLink: function (moduleID, optionKey, displayText, cssClass) { + if (!displayText) { + if (optionKey) { + displayText = optionKey; + } else if (modules[moduleID]) { + displayText = modules[moduleID].moduleName; + } else if (moduleID) { + displayText = moduleID; + } else { + displayText = 'Settings'; + } + } + + var hash = modules['settingsNavigation'].makeUrlHash(moduleID, optionKey); + var link = ['', displayText, ''].join(''); + return link; + }, + makeUrlHash: function(moduleID, optionKey) { + var hashComponents = ['#!settings'] + + if (moduleID) { + hashComponents.push(moduleID); + } + + if (moduleID && optionKey) { + hashComponents.push(optionKey); + } + + var hash = hashComponents.join('/'); + return hash; + }, + setUrlHash: function(moduleID, optionKey) { + var titleComponents = ['RES Settings']; + + if (moduleID) { + var module = modules[moduleID]; + var moduleName = module && module.moduleName || moduleID; + titleComponents.push(moduleName); + + if (optionKey) { + titleComponents.push(optionKey); + } + } + + var hash = this.makeUrlHash(moduleID, optionKey); + var title = titleComponents.join(' - '); + + if (window.location.hash != hash) { + window.history.pushState(hash, title, hash); + } + }, + resetUrlHash: function() { + window.location.hash = ""; + }, + onHashChange: function (event) { + var hash = window.location.hash; + if (hash.substring(0, 10) !== '#!settings') return; + + var params = hash.match(/\/[\w\s]+/g); + if (params && params[0]) { + var moduleID = params[0].substring(1); + } + if (params && params[1]) { + var optionKey = params[1].substring(1); + } + + modules['settingsNavigation'].loadSettingsPage(moduleID, optionKey); + }, + onPopState: function (event) { + var state = typeof event.state === "string" && event.state.split('/'); + if (!state || state[0] !== '#!settings') { + if (RESConsole.isOpen) { + RESConsole.close(); + } + return; + } + + var moduleID = state[1]; + var optionKey = state[2]; + + modules['settingsNavigation'].loadSettingsPage(moduleID, optionKey); + }, + loadSettingsPage: function(moduleID, optionKey, optionValue) { + if (moduleID && modules.hasOwnProperty(moduleID)) { + var module = modules[moduleID]; + } + if (module) { + var category = module.category; + } + + + RESConsole.open(module && module.moduleID); + if (module) { + if (optionKey && module.options.hasOwnProperty(optionKey)) { + var optionsPanel = $(RESConsole.RESConsoleContent); + var optionElement = optionsPanel.find('label[for="' + optionKey + '"]'); + var optionParent = optionElement.parent(); + optionParent.addClass('highlight'); + if (optionElement.length) { + var configPanel = $(RESConsole.RESConsoleConfigPanel); + var offset = optionElement.offset().top - configPanel.offset().top; + optionsPanel.scrollTop(offset); + } + } + } else { + switch(moduleID) { + case 'search': + this.search(optionKey); + break; + default: + break; + } + } + }, + search: function(query) { + RESConsole.openCategoryPanel('About RES'); + modules['settingsNavigation'].drawSearchResults(query); + modules['settingsNavigation'].getSearchResults(query); + modules['settingsNavigation'].setUrlHash('search', query); + }, + showSearch: function () { + RESConsole.hidePrefsDropdown(); + modules['settingsNavigation'].drawSearchResults(); + $('#SearchRES-input').focus(); + }, + doneSearch: function (query, results) { + modules['settingsNavigation'].drawSearchResults(query, results); + }, + getSearchResults: function (query) { + if (!(query && query.toString().length)) { + modules['settingsNavigation'].doneSearch(query, []); + } + + var queryTerms = modules['settingsNavigation'].prepareSearchText(query, true).split(' '); + var results = []; + + // Search options + for (var moduleKey in modules) { + if (!modules.hasOwnProperty(moduleKey)) continue; + var module = modules[moduleKey]; + + + var searchString = module.moduleID + module.moduleName + module.category + module.description; + searchString = modules['settingsNavigation'].prepareSearchText(searchString, false); + var matches = modules['settingsNavigation'].searchMatches(queryTerms, searchString); + if (matches) { + var result = modules['settingsNavigation'].makeModuleSearchResult(moduleKey); + result.rank = matches; + results.push(result); + } + + + var options = module.options; + + for (var optionKey in options) { + if (!options.hasOwnProperty(optionKey)) continue; + var option = options[optionKey]; + + var searchString = module.moduleID + module.moduleName + module.category + optionKey + option.description; + searchString = modules['settingsNavigation'].prepareSearchText(searchString, false); + var matches = modules['settingsNavigation'].searchMatches(queryTerms, searchString); + if (matches) { + var result = modules['settingsNavigation'].makeOptionSearchResult(moduleKey, optionKey); + result.rank = matches; + results.push(result); + } + } + } + + results.sort(function(a, b) { + var comparison = b.rank - a.rank; + + /* + if (comparison === 0) { + comparison = + a.title < b.title ? -1 + : a.title > b.title ? 1 + : 0; + + } + + if (comparison === 0) { + comparison = + a.description < b.description ? -1 + : a.description > b.description ? 1 + : 0; + } + */ + + return comparison; + }); + + modules['settingsNavigation'].doneSearch(query, results); + + }, + searchMatches: function(needles, haystack) { + if (!(haystack && haystack.length)) + return false; + + var numMatches = 0; + for (var i = 0; i < needles.length; i++) { + if (haystack.indexOf(needles[i]) !== -1) + numMatches++; + } + + return numMatches; + }, + prepareSearchText: function (text, preserveSpaces) { + if (typeof text === "undefined" || text === null) { + return ''; + } + + var replaceSpacesWith = !!preserveSpaces ? ' ' : '' + return text.toString().toLowerCase() + .replace(/[,\/]/g,replaceSpacesWith).replace(/\s+/g, replaceSpacesWith); + }, + makeOptionSearchResult: function (moduleKey, optionKey) { + var module = modules[moduleKey]; + var option = module.options[optionKey]; + + var result = {}; + result.type = 'option'; + result.breadcrumb = ['Settings', + module.category, + module.moduleName + ' (' + module.moduleID + ')' + ].join(' > '); + result.title = optionKey; + result.description = option.description; + result.moduleID = moduleKey; + result.optionKey = optionKey; + + return result; + }, + makeModuleSearchResult: function (moduleKey) { + var module = modules[moduleKey]; + + var result = {}; + result.type = 'module'; + result.breadcrumb = ['Settings', + module.category, + '(' + module.moduleID + ')' + ].join(' > '); + result.title = module.moduleName; + result.description = module.description; + result.moduleID = moduleKey; + + return result; + }, + + onSearchResultSelected: function(result) { + if (!result) return; + + switch (result.type) { + case 'module': + modules['settingsNavigation'].loadSettingsPage(result.moduleID); + break; + case 'option': + modules['settingsNavigation'].loadSettingsPage(result.moduleID, result.optionKey); + break; + default: + alert('Could not load search result'); + break; + } + }, + // ---------- View ------ + css: '\ + #SearchRES #SearchRES-results-container { \ + display: none; \ + } \ + #SearchRES #SearchRES-results-container + #SearchRES-boilerplate { margin-top: 1em; border-top: 1px black solid; padding-top: 1em; } \ + #SearchRES h4 { \ + margin-top: 1.5em; \ + } \ + #SearchRES-results { \ + } \ + #SearchRES-results li { \ + list-style-type: none; \ + border-bottom: 1px dashed #ccc; \ + cursor: pointer; \ + margin-left: 0px; \ + padding-left: 10px; \ + padding-top: 24px; \ + padding-bottom: 24px; \ + } \ + #SearchRES-results li:hover { \ + background-color: #FAFAFF; \ + } \ + .SearchRES-result-title { \ + margin-bottom: 12px; \ + font-weight: bold; \ + color: #666; \ + } \ + .SearchRES-breadcrumb { \ + font-weight: normal; \ + color: #888; \ + } \ + .SearchRES-result-copybutton {\ + float: right; \ + opacity: 0.4; \ + padding: 10px; \ + width: 26px; \ + height: 22px; \ + background: no-repeat center center; \ + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAWCAYAAADeiIy1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+5pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjY1RTYzOTA2ODZDRjExREJBNkUyRDg4N0NFQUNCNDA3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkUwRjM3M0QxMDY5NTExRTI5OUZEQTZGODg4RDc1ODdCIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkUwRjM3M0QwMDY5NTExRTI5OUZEQTZGODg4RDc1ODdCIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMTgwMTE3NDA3MjA2ODExODA4M0ZFMkJBM0M1RUU2NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNjgwMTE3NDA3MjA2ODExODA4M0U3NkRBMDNEMDVDMSIvPiA8ZGM6dGl0bGU+IDxyZGY6QWx0PiA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPmdseXBoaWNvbnM8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pn00ay4AAAEISURBVHjavJWBDYQgDEXhcgMwwm2gIzgCIzjCjeAIjsAIN8KN4Ai6wW3AtTm4EChYkdjkm2javLa2IKy1olZgL9CD5XsShI8PSF8B8pqvAqEWUJ8FYemglQhEmfg/gA0uhvLHVo5EUtmAf3ZgCoNB74xvLkEVgnwlJtOep8vSVihM9veRAKiDFdhCK/VdECal9JBONLSkIhzVBpWUW4cT1giSDEMMmqP+GjdxA2OPiuMdgxb3bQozOr2wBMhSGTFAppQYBZoqDtWR4ZuA1MFromf60gt7AKZyp0oo6UD4ImuWEJYbB6Dbi28BYsXfQJsLMBUQH7Nx/HWDU4B3le9cfCWtHAjqK8AAypyhqqqagq4AAAAASUVORK5CYII="); \ + display: none; \ + } \ + #SearchRES-results li:hover .SearchRES-result-copybutton { display: block; } \ + #SearchRES-input-submit { \ + margin-left: 8px; \ + } \ + #SearchRES-input { \ + width: 200px; \ + height: 22px; \ + font-size: 14px; \ + } \ + #SearchRES-input-container { \ + float: left; \ + margin-left: 3em; \ + margin-top: 7px; \ + } \ + ', + searchPanelHtml: '\ +

        Search RES Settings Console

        \ +
        \ +

        Results for:

        \ +
          \ +

          No results found

          \ +
          \ +
          \ +

          You can search for RES options by module name, option name, and description. For example, try searching for "daily trick" in one of the following ways:

          \ +
            \ +
          • type daily trick in the search box above and click the button
          • \ +
          • press . to open the RES console, type in search daily trick, and press Enter
          • \ +
          \ + ', + searchPanel: null, + renderSearchPanel: function() { + var searchPanel = $('
          ').html(modules['settingsNavigation'].searchPanelHtml); + searchPanel.delegate('#SearchRES-results-container .SearchRES-result-item', 'click', modules['settingsNavigation'].handleSearchResultClick); + + modules['settingsNavigation'].searchPanel = searchPanel; + return searchPanel; + }, + searchForm: null, + renderSearchForm: function() { + var RESSearchContainer = createElementWithID('form', 'SearchRES-input-container'); + + var RESSearchBox = createElementWithID('input', 'SearchRES-input'); + RESSearchBox.setAttribute('type', 'text'); + RESSearchBox.setAttribute('placeholder', 'search RES settings'); + + var RESSearchButton = createElementWithID('input', 'SearchRES-input-submit'); + RESSearchButton.classList.add('blueButton'); + RESSearchButton.setAttribute('type', 'submit'); + RESSearchButton.setAttribute('value', 'search'); + + RESSearchContainer.appendChild(RESSearchBox); + RESSearchContainer.appendChild(RESSearchButton); + + RESSearchContainer.addEventListener('submit', function (e) { + e.preventDefault(); + modules['settingsNavigation'].search(RESSearchBox.value); + + return false; + }); + + searchForm = RESSearchContainer; + return RESSearchContainer; + }, + drawSearchResultsPage: function() { + if (!RESConsole.isOpen) { + RESConsole.open(); + } + + if (!$('#SearchRES').is(':visible')) { + RESConsole.openCategoryPanel('About RES'); + + // Open "Search RES" page + $('#Button-SearchRES', this.RESConsoleContent).trigger('click', { duration: 0 }); + } + }, + drawSearchResults: function (query, results) { + modules['settingsNavigation'].drawSearchResultsPage(); + + var resultsContainer = $('#SearchRES-results-container', modules['settingsNavigation'].searchPanel); + + if (!(query && query.length)) { + resultsContainer.hide(); + return; + } + + resultsContainer.show(); + resultsContainer.find('#SearchRES-query').text(query); + $("#SearchRES-input", modules['settingsNavigation'].searchForm).val(query); + + if (!(results && results.length)) { + resultsContainer.find('#SearchRES-results-none').show(); + resultsContainer.find('#SearchRES-results').hide(); + } else { + resultsContainer.find('#SearchRES-results-none').hide(); + var resultsList = $('#SearchRES-results', resultsContainer).show(); + + resultsList.empty(); + for (var i = 0; i < results.length; i++) { + var result = results[i]; + + var element = modules['settingsNavigation'].drawSearchResultItem(result); + resultsList.append(element); + } + } + }, + drawSearchResultItem: function(result) { + var element = $('
        • '); + element.addClass('SearchRES-result-item') + .data('SearchRES-result', result); + + $('', { class: 'SearchRES-result-copybutton'}) + .appendTo(element) + .attr('title', 'copy this for a comment') + + var breadcrumb = $('', {class: 'SearchRES-breadcrumb'}) + .text(result.breadcrumb + ' > '); + $('
          ', {class: 'SearchRES-result-title'}) + .append(breadcrumb) + .append(result.title) + .appendTo(element); + $('
          ', {class: 'SearchRES-result-description'}) + .appendTo(element) + .html(result.description); + + return element; + }, + handleSearchResultClick: function (e) { + var element = $(this); + var result = element.data('SearchRES-result'); + if ($(e.target).is('.SearchRES-result-copybutton')) { + modules['settingsNavigation'].onSearchResultCopy(result, element); + } else { + modules['settingsNavigation'].onSearchResultSelected(result); + } + e.preventDefault(); + }, + onSearchResultCopy: function(result, element) { + var markdown = modules['settingsNavigation'].makeOptionSearchResultLink(result); + alert(''); + }, + makeOptionSearchResultLink: function (result) { + var url = document.location.pathname + + modules['settingsNavigation'].makeUrlHash(result.moduleID, result.optionKey); + + var text = [ + result.breadcrumb, + '[' + result.title + '](' + url + ')', + ' \n', + result.description, + ' \n', + ' \n' + ].join(' '); + return text; + } +}; + + + + +modules['dashboard'] = { + moduleID: 'dashboard', + moduleName: 'RES Dashboard', + category: 'UI', + options: { + defaultPosts: { + type: 'text', + value: 3, + description: 'Number of posts to show by default in each widget' + }, + defaultSort: { + type: 'enum', + values: [ + { name: 'hot', value: 'hot' }, + { name: 'new', value: 'new' }, + { name: 'controversial', value: 'controversial' }, + { name: 'top', value: 'top' } + ], + value: 'hot', + description: 'Default sort method for new widgets' + }, + dashboardShortcut: { + type: 'boolean', + value: true, + description: 'Show +dashboard shortcut in sidebar for easy addition of dashboard widgets.' + }, + tagsPerPage: { + type: 'text', + value: 25, + description: 'How many user tags to show per page. (enter zero to show all on one page)' + } + }, + description: 'The RES Dashboard is home to a number of features including widgets and other useful tools', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([-\w\.]+\.)?reddit\.com\/[-\w\.\/]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + go: function() { + if (this.isEnabled()) { + this.getLatestWidgets(); + RESUtils.addCSS('.RESDashboardToggle { margin-right: 5px; color: white; background-image: url(http://www.redditstatic.com/bg-button-add.png); cursor: pointer; text-align: center; width: 68px; font-weight: bold; font-size: 10px; border: 1px solid #444; padding: 1px 6px; border-radius: 3px 3px 3px 3px; }'); + RESUtils.addCSS('.RESDashboardToggle.remove { background-image: url(http://www.redditstatic.com/bg-button-remove.png) }'); + if (this.isMatchURL()) { + $('#RESDropdownOptions').prepend('
        • '); + if (RESUtils.currentSubreddit()) { + RESUtils.addCSS('.RESDashboardToggle {}'); + // one more safety check... not sure how people's widgets[] arrays are breaking. + if (!(this.widgets instanceof Array)) { + this.widgets = []; + } + if (RESUtils.currentSubreddit('dashboard')) { + $('#noresults, #header-bottom-left .tabmenu:not(".viewimages")').hide(); + $('#header-bottom-left .redditname a:first').text('My Dashboard'); + this.drawDashboard(); + } + if (this.options.dashboardShortcut.value == true) this.addDashboardShortcuts(); + } + } + } + }, + getLatestWidgets: function() { + try { + this.widgets = JSON.parse(RESStorage.getItem('RESmodules.dashboard.' + RESUtils.loggedInUser())) || []; + } catch (e) { + this.widgets = []; + } + }, + loader: 'data:image/gif;base64,R0lGODlhEAAQAPQAAP///2+NyPb3+7zK5e3w95as1rPD4W+NyKC02oOdz8/Z7Nnh8HqVzMbS6XGOyI2l06m73gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==', + drawDashboard: function() { + // this first line hides the "you need RES 4.0+ to view the dashboard" link + RESUtils.addCSS('.id-t3_qi5iy {display: none;}'); + RESUtils.addCSS('.RESDashboardComponent { position: relative; border: 1px solid #ccc; border-radius: 3px 3px 3px 3px; overflow: hidden; margin-bottom: 10px; }'); + RESUtils.addCSS('.RESDashboardComponentHeader { box-sizing: border-box; padding: 5px 0 8px 0; background-color: #f0f3fc; overflow: hidden; }'); + RESUtils.addCSS('.RESDashboardComponentScrim { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 5; display: none; }'); + RESUtils.addCSS('.RESDashboardComponentLoader { box-sizing: border-box; position: absolute; background-color: #f2f9ff; border: 1px solid #b9d7f4; border-radius: 3px 3px 3px 3px; width: 314px; height: 40px; left: 50%; top: 50%; margin-left: -167px; margin-top: -20px; text-align: center; padding-top: 11px; }'); + RESUtils.addCSS('.RESDashboardComponentLoader span { position: relative; top: -6px; left: 5px; } '); + RESUtils.addCSS('.RESDashboardComponentContainer { padding: 10px 15px 0 15px; min-height: 100px; }'); + RESUtils.addCSS('.RESDashboardComponentContainer.minimized { display: none; }'); + RESUtils.addCSS('.RESDashboardComponent a.widgetPath, .addNewWidget, .editWidget { display: inline-block; margin-left: 0; margin-top: 7px; color: #000; font-weight: bold; }'); + RESUtils.addCSS('.editWidget { float: left; margin-right: 10px; } '); + RESUtils.addCSS('.RESDashboardComponent a.widgetPath { margin-left: 15px; vertical-align: top; width: 120px; overflow: hidden; text-overflow: ellipsis; }'); + RESUtils.addCSS('#RESDashboardAddComponent, #RESDashboardEditComponent { box-sizing: border-box; padding: 5px 8px 5px 8px; vertical-align: middle; background-color: #cee3f8; border: 1px solid #369;}'); + RESUtils.addCSS('#RESDashboardEditComponent { display: none; position: absolute; }'); + // RESUtils.addCSS('#RESDashboardComponentScrim, #RESDashboardComponentLoader { background-color: #ccc; opacity: 0.3; border: 1px solid red; display: none; }'); + RESUtils.addCSS('#addRedditFormContainer, #addMailWidgetContainer, #addUserFormContainer { display: none; }'); + RESUtils.addCSS('#addWidgetButtons, #addRedditFormContainer, #addMailWidgetContainer, #addUserFormContainer, #editRedditFormContainer { width: auto; min-width: 550px; height: 28px; float: right; text-align: right; }'); + RESUtils.addCSS('#editRedditFormContainer { width: auto; }'); + RESUtils.addCSS('#addUserForm, #addRedditForm { display: inline-block }'); + RESUtils.addCSS('#addUser { width: 200px; height: 24px; }'); + RESUtils.addCSS('#addRedditFormContainer ul.token-input-list-facebook, #editRedditFormContainer ul.token-input-list-facebook { float: left; }'); + RESUtils.addCSS('#addReddit { width: 115px; background-color: #fff; border: 1px solid #96bfe8; margin-left: 6px; margin-right: 6px; padding: 1px 2px 1px 2px; }'); + RESUtils.addCSS('#addRedditDisplayName, #editRedditDisplayName { width: 140px; height: 24px; background-color: #fff; border: 1px solid #96bfe8; margin-left: 6px; margin-right: 6px; padding: 1px 2px 1px 2px; }'); + RESUtils.addCSS('#editReddit { width: 5px; } '); + RESUtils.addCSS('.addButton, .updateButton { cursor: pointer; display: inline-block; width: auto; padding: 3px 5px; font-size: 11px; color: #fff; border: 1px solid #636363; border-radius: 3px; background-color: #5cc410; margin-top: 3px; margin-left: 5px; }'); + RESUtils.addCSS('.cancelButton { width: 50px; text-align: center; cursor: pointer; display: inline-block; padding: 3px 5px; font-size: 11px; color: #fff; border: 1px solid #636363; border-radius: 3px; background-color: #D02020; margin-top: 3px; margin-left: 5px; }'); + RESUtils.addCSS('.backToWidgetTypes { display: inline-block; vertical-align: top; margin-top: 8px; font-weight: bold; color: #000; cursor: pointer; }'); + RESUtils.addCSS('.RESDashboardComponentHeader ul { font-family: Verdana; font-size: 13px; box-sizing: border-box; line-height: 22px; display: inline-block; margin-top: 2px; }'); + RESUtils.addCSS('.RESDashboardComponentHeader ul li { box-sizing: border-box; vertical-align: middle; height: 24px; display: inline-block; cursor: pointer; padding: 0 6px; border: 1px solid #c7c7c7; background-color: #fff; color: #6c6c6c; border-radius: 3px 3px 3px 3px; }'); + RESUtils.addCSS('.RESDashboardComponentHeader .editButton { display: inline-block; padding: 0; width: 24px; -moz-box-sizing: border-box; vertical-align: middle; margin-left: 10px; } '); + RESUtils.addCSS('.RESDashboardComponent.minimized ul li { display: none; }'); + RESUtils.addCSS('.RESDashboardComponent.minimized li.RESClose, .RESDashboardComponent.minimized li.minimize { display: inline-block; }'); + RESUtils.addCSS('ul.widgetSortButtons li { margin-right: 10px; }'); + RESUtils.addCSS('.RESDashboardComponentHeader ul li.active, .RESDashboardComponentHeader ul li:hover { background-color: #a6ccf1; color: #fff; border-color: #699dcf; }'); + RESUtils.addCSS('ul.widgetStateButtons li { margin-right: 5px; }'); + RESUtils.addCSS('ul.widgetStateButtons li:last-child { margin-right: 0; }'); + RESUtils.addCSS('ul.widgetStateButtons li.disabled { background-color: #ddd; }'); + RESUtils.addCSS('ul.widgetStateButtons li.disabled:hover { cursor: auto; background-color: #ddd; color: #6c6c6c; border: 1px solid #c7c7c7; }'); + RESUtils.addCSS('ul.widgetSortButtons { margin-left: 10px; }'); + RESUtils.addCSS('ul.widgetStateButtons { float: right; margin-right: 8px; }'); + RESUtils.addCSS('ul.widgetStateButtons li.updateTime { cursor: auto; background: none; border: none; color: #afafaf; font-size: 9px; padding-right: 0; }'); + RESUtils.addCSS('ul.widgetStateButtons li.minimize, ul.widgetStateButtons li.close { font-size: 24px; }'); + RESUtils.addCSS('.minimized ul.widgetStateButtons li.minimize { font-size: 14px; }'); + RESUtils.addCSS('ul.widgetStateButtons li.refresh { margin-left: 3px; width: 24px; position:relative; padding: 0; }'); + RESUtils.addCSS('ul.widgetStateButtons li.refresh div { height: 16px; width: 16px; position: absolute; left: 4px; top: 4px; background-image: url(\'http://e.thumbs.redditmedia.com/r22WT2K4sio9Bvev.png\'); background-repeat: no-repeat; background-position: -16px -209px; }'); + RESUtils.addCSS('#userTaggerContents .show { display: inline-block; }'); + RESUtils.addCSS('#tagPageControls { display: inline-block; position: relative; top: 9px;}'); + + var dbLinks = $('span.redditname a'); + if ($(dbLinks).length > 1) { + $(dbLinks[0]).addClass('active'); + } + + // add each subreddit widget... + // add the "add widget" form... + this.attachContainer(); + this.attachAddComponent(); + this.attachEditComponent(); + this.initUpdateQueue(); + }, + initUpdateQueue: function() { + modules['dashboard'].updateQueue = []; + for (var i in this.widgets) if (this.widgets[i]) this.addWidget(this.widgets[i]); + setTimeout(function () { + $('#RESDashboard').dragsort({ + dragSelector: "div.RESDashboardComponentHeader", + dragSelectorExclude: 'a, li, li.refreshAll, li.refresh > div, .editButton', + dragEnd: modules['dashboard'].saveOrder, + placeHolderTemplate: "
          " + }); + }, 300); + }, + addToUpdateQueue: function(updateFunction) { + modules['dashboard'].updateQueue.push(updateFunction); + if (!modules['dashboard'].updateQueueTimer) { + modules['dashboard'].updateQueueTimer = setInterval(modules['dashboard'].processUpdateQueue, 2000); + setTimeout(modules['dashboard'].processUpdateQueue, 100); + } + }, + processUpdateQueue: function() { + var thisUpdate = modules['dashboard'].updateQueue.pop(); + thisUpdate(); + if (modules['dashboard'].updateQueue.length < 1) { + clearInterval(modules['dashboard'].updateQueueTimer); + delete modules['dashboard'].updateQueueTimer; + } + }, + saveOrder: function() { + var data = $("#siteTable li.RESDashboardComponent").map(function() { return $(this).attr("id"); }).get(); + data.reverse(); + var newOrder = []; + for (var i=0, len=modules['dashboard'].widgets.length; i'); + if ((location.hash !== '') && (location.hash !== '#dashboardContents')) { + $('span.redditname a').removeClass('active'); + var activeTabID = location.hash.replace('#','#tab-'); + $(activeTabID).addClass('active'); + $('.dashboardPane').hide(); + $(location.hash).show(); + } else { + $('#userTaggerContents').hide(); + } + $('span.redditname a:first').click(function(e) { + e.preventDefault(); + location.hash = 'dashboardContents'; + $('span.redditname a').removeClass('active'); + $(this).addClass('active'); + $('.dashboardPane').hide(); + $('#dashboardContents').show(); + }); + }, + attachEditComponent: function() { + this.dashboardContents = $('#dashboardContents'); + this.dashboardEditComponent = $('
          '); + $(this.dashboardEditComponent).html(' \ +
          Edit widget
          \ +
          \ +
          \ +
          \ + '); + var thisEle = $(this.dashboardEditComponent).find('#editReddit'); + + $(this.dashboardEditComponent).find('#editRedditForm').submit( + function(e) { + e.preventDefault(); + var thisBasePath = $('#editReddit').val(); + if (thisBasePath !== '') { + if (thisBasePath.indexOf(',') !== -1) { + thisBasePath = thisBasePath.replace(/\,/g,'+'); + } + modules['dashboard'].widgetBeingEdited.formerBasePath = modules['dashboard'].widgetBeingEdited.basePath; + modules['dashboard'].widgetBeingEdited.basePath = '/r/'+thisBasePath; + modules['dashboard'].widgetBeingEdited.displayName = $('#editRedditDisplayName').val(); + modules['dashboard'].widgetBeingEdited.update(); + $('#editReddit').tokenInput('clear'); + $('#RESDashboardEditComponent').fadeOut(function() { + $('#editReddit').blur(); + }); + modules['dashboard'].widgetBeingEdited.widgetEle.find('.widgetPath').text(modules['dashboard'].widgetBeingEdited.displayName).attr('title','/r/'+thisBasePath); + modules['dashboard'].updateWidget(); + } + } + ); + $(this.dashboardEditComponent).find('.cancelButton').click( + function(e) { + $('#editReddit').tokenInput('clear'); + $('#RESDashboardEditComponent').fadeOut(function() { + $('#editReddit').blur(); + }); + } + ); + $(document.body).append(this.dashboardEditComponent); + }, + showEditForm: function() { + var basePath = modules['dashboard'].widgetBeingEdited.basePath; + var widgetEle = modules['dashboard'].widgetBeingEdited.widgetEle; + $('#editRedditDisplayName').val(modules['dashboard'].widgetBeingEdited.displayName); + var eleTop = $(widgetEle).position().top; + var eleWidth = $(widgetEle).width(); + $('#RESDashboardEditComponent').css('top',eleTop+'px').css('left','5px').css('width',(eleWidth+2)+'px').fadeIn('fast'); + basePath = basePath.replace(/^\/r\//,''); + var prepop = []; + var reddits = basePath.split('+'); + for (var i=0, len=reddits.length; i" + thisDesc + "" + } + }); + modules['dashboard'].firstEdit = true; + } else { + $('#editReddit').tokenInput('clear'); + for (var i=0, len=prepop.length; i'); + $(this.dashboardAddComponent).html(' \ +
          Add a new widget
          \ +
          \ +
          +mail widget
          \ +
          +user widget
          \ +
          +subreddit widget
          \ +
          \ +
          \ +
          « back
          \ +
          +inbox
          \ +
          +unread
          \ +
          +messages
          \ +
          +comment replies
          \ +
          +post replies
          \ +
          \ +
          \ +
          « back
          \ +
          \ +
          \ +
          \ +
          « back
          \ +
          \ +
          \ + '); + $(this.dashboardAddComponent).find('.backToWidgetTypes').click(function(e) { + $(this).parent().fadeOut(function() { + $('#addWidgetButtons').fadeIn(); + }); + }); + $(this.dashboardAddComponent).find('.widgetShortcut').click(function(e) { + var thisBasePath = $(this).attr('widgetPath'); + modules['dashboard'].addWidget({ + basePath: thisBasePath + }, true); + $('#addMailWidgetContainer').fadeOut(function() { + $('#addWidgetButtons').fadeIn(); + }); + }); + $(this.dashboardAddComponent).find('#addRedditWidget').click(function(e) { + $('#addWidgetButtons').fadeOut(function() { + $('#addRedditFormContainer').fadeIn(function() { + $('#token-input-addReddit').focus(); + }); + }); + }); + $(this.dashboardAddComponent).find('#addMailWidget').click(function(e) { + $('#addWidgetButtons').fadeOut(function() { + $('#addMailWidgetContainer').fadeIn(); + }); + });; + $(this.dashboardAddComponent).find('#addUserWidget').click(function(e) { + $('#addWidgetButtons').fadeOut(function() { + $('#addUserFormContainer').fadeIn(); + }); + });; + var thisEle = $(this.dashboardAddComponent).find('#addReddit'); + $(thisEle).tokenInput('/api/search_reddit_names.json?app=res', { + method: "POST", + queryParam: "query", + theme: "facebook", + allowFreeTagging: true, + zindex: 999999999, + onResult: function(response) { + var names = response.names; + var results = []; + for (var i=0, len=names.length; i" + thisDesc + "" + } + }); + + $(this.dashboardAddComponent).find('#addRedditForm').submit( + function(e) { + e.preventDefault(); + var thisBasePath = $('#addReddit').val(); + if (thisBasePath !== '') { + if (thisBasePath.indexOf(',') !== -1) { + thisBasePath = thisBasePath.replace(/\,/g,'+'); + } + var thisDisplayName = ($('#addRedditDisplayName').val()) ? $('#addRedditDisplayName').val() : thisBasePath; + modules['dashboard'].addWidget({ + basePath: thisBasePath, + displayName: thisDisplayName + }, true); + // $('#addReddit').val('').blur(); + $('#addReddit').tokenInput('clear'); + $('#addRedditFormContainer').fadeOut(function() { + $('#addReddit').blur(); + $('#addWidgetButtons').fadeIn(); + }); + } + } + ); + $(this.dashboardAddComponent).find('#addUserForm').submit( + function(e) { + e.preventDefault(); + var thisBasePath = '/user/'+$('#addUser').val(); + modules['dashboard'].addWidget({ + basePath: thisBasePath + }, true); + $('#addUser').val('').blur(); + $('#addUserFormContainer').fadeOut(function() { + $('#addWidgetButtons').fadeIn(); + }); + + } + ); + $(this.dashboardContents).append(this.dashboardAddComponent); + this.dashboardUL = $('
            '); + $(this.dashboardContents).append(this.dashboardUL); + }, + addWidget: function(optionsObject, isNew) { + if (optionsObject.basePath.slice(0,1) !== '/') optionsObject.basePath = '/r/'+optionsObject.basePath; + var exists=false; + for (var i=0, len=this.widgets.length; i
            querying the server. one moment please.
            '); + var editButtonHTML = (thisWidget.basePath.indexOf('/r/') === -1) ? '' : '
            '; + thisWidget.header = $(''); + thisWidget.sortControls = $('
            • hot
            • new
            • controversial
            • top
            '); + // return an optionsObject, which is what we'll store in the modules['dashboard'].widgets array. + thisWidget.optionsObject = function() { + return { + basePath: thisWidget.basePath, + displayName: thisWidget.displayName, + numPosts: thisWidget.numPosts, + sortBy: thisWidget.sortBy, + minimized: thisWidget.minimized + } + } + // set the sort by properly... + $(thisWidget.sortControls).find('li[sort='+thisWidget.sortBy+']').addClass('active'); + $(thisWidget.sortControls).find('li').click(function(e) { + thisWidget.sortChange($(e.target).attr('sort')); + }); + $(thisWidget.header).append(thisWidget.sortControls); + if ((thisWidget.basePath.indexOf('/r/') !== 0) && (thisWidget.basePath.indexOf('/user/') !== 0)) { + setTimeout(function() { + $(thisWidget.sortControls).hide(); + }, 100); + } + thisWidget.stateControls = $('
            • Refresh All
            • +row
            • -row
            • -
            • ×
            '); + $(thisWidget.stateControls).find('li').click(function (e) { + switch ($(e.target).attr('action')) { + case 'refresh': + thisWidget.update(); + break; + case 'refreshAll': + $('li[action="refresh"]').click(); + break; + case 'addRow': + if (thisWidget.numPosts === 10) break; + thisWidget.numPosts++; + if (thisWidget.numPosts === 10) $(thisWidget.stateControls).find('li[action=addRow]').addClass('disabled'); + $(thisWidget.stateControls).find('li[action=subRow]').removeClass('disabled'); + modules['dashboard'].saveWidget(thisWidget.optionsObject()); + thisWidget.update(); + break; + case 'subRow': + if (thisWidget.numPosts === 0) break; + thisWidget.numPosts--; + if (thisWidget.numPosts === 1) $(thisWidget.stateControls).find('li[action=subRow]').addClass('disabled'); + $(thisWidget.stateControls).find('li[action=addRow]').removeClass('disabled'); + modules['dashboard'].saveWidget(thisWidget.optionsObject()); + thisWidget.update(); + break; + case 'minimize': + $(thisWidget.widgetEle).toggleClass('minimized'); + if ($(thisWidget.widgetEle).hasClass('minimized')) { + $(e.target).text('+'); + thisWidget.minimized = true; + } else { + $(e.target).text('-'); + thisWidget.minimized = false; + thisWidget.update(); + } + $(thisWidget.contents).parent().slideToggle(); + modules['dashboard'].saveWidget(thisWidget.optionsObject()); + break; + case 'delete': + modules['dashboard'].removeWidget(thisWidget.optionsObject()); + break; + } + }); + $(thisWidget.header).append(thisWidget.stateControls); + thisWidget.sortChange = function(sortBy) { + thisWidget.sortBy = sortBy; + $(thisWidget.header).find('ul.widgetSortButtons li').removeClass('active'); + $(thisWidget.header).find('ul.widgetSortButtons li[sort='+sortBy+']').addClass('active'); + thisWidget.update(); + modules['dashboard'].saveWidget(thisWidget.optionsObject()); + } + thisWidget.edit = function(e) { + modules['dashboard'].widgetBeingEdited = thisWidget; + modules['dashboard'].showEditForm(); + } + $(thisWidget.header).find('.editButton').click(thisWidget.edit); + thisWidget.update = function() { + if (thisWidget.basePath.match(/\/user\//)) { + thisWidget.sortPath = (thisWidget.sortBy === 'hot') ? '/' : '?sort='+thisWidget.sortBy; + } else if (thisWidget.basePath.match(/\/r\//)) { + thisWidget.sortPath = (thisWidget.sortBy === 'hot') ? '/' : '/'+thisWidget.sortBy+'/'; + } else { + thisWidget.sortPath = ''; + } + thisWidget.url = location.protocol + '//' + location.hostname + '/' + thisWidget.basePath + thisWidget.sortPath; + $(thisWidget.contents).fadeTo('fast',0.25); + $(thisWidget.scrim).fadeIn(); + $.ajax({ + url: thisWidget.url, + data: { + limit: thisWidget.numPosts + }, + success: thisWidget.populate, + error: thisWidget.error + }); + } + thisWidget.container = $('
            '); + if (thisWidget.minimized) { + $(thisWidget.container).addClass('minimized'); + $(thisWidget.stateControls).find('li.minimize').addClass('minimized').text('+'); + } + thisWidget.scrim = $(thisWidget.widgetEle).find('.RESDashboardComponentScrim'); + thisWidget.contents = $(thisWidget.container).find('.RESDashboardComponentContents'); + thisWidget.init = function() { + if (RESUtils.currentSubreddit('dashboard')) { + thisWidget.draw(); + if (!thisWidget.minimized) modules['dashboard'].addToUpdateQueue(thisWidget.update); + } + } + thisWidget.draw = function() { + $(thisWidget.widgetEle).append(thisWidget.header); + $(thisWidget.widgetEle).append(thisWidget.container); + if (thisWidget.minimized) $(thisWidget.widgetEle).addClass('minimized'); + modules['dashboard'].dashboardUL.prepend(thisWidget.widgetEle); + // $(thisWidget.scrim).fadeIn(); + } + thisWidget.populate = function(response) { + var widgetContent = $(response).find('#siteTable'); + $(widgetContent).attr('id','siteTable_'+thisWidget.basePath.replace(/\/|\+/g,'_')); + if (widgetContent.length === 2) widgetContent = widgetContent[1]; + $(widgetContent).attr('url',thisWidget.url+'?limit='+thisWidget.numPosts); + if ((widgetContent) && ($(widgetContent).html() !== '')) { + // widgetContent will contain HTML from Reddit's page load. No XSS here or you'd already be hit, can't call escapeHTML on this either and wouldn't help anyhow. + $(thisWidget.contents).html(widgetContent); + $(thisWidget.contents).fadeTo('fast',1); + $(thisWidget.scrim).fadeOut(function(e) { + $(this).hide(); // make sure it is hidden in case the element isn't visible due to being on a different dashboard tab + }) + $(thisWidget.stateControls).find('.updateTime').text('updated: '+RESUtils.niceDateTime()); + } else { + if (thisWidget.url.indexOf('/message/') !== -1) { + $(thisWidget.contents).html('
            No messages were found.
            '); + } else { + $(thisWidget.contents).html('
            There were no results returned for this widget. If you made a typo, simply close the widget to delete it. If reddit is just under heavy load, try clicking refresh in a few moments.
            '); + } + $(thisWidget.contents).fadeTo('fast',1); + $(thisWidget.scrim).fadeOut(); + $(thisWidget.stateControls).find('.updateTime').text('updated: '+RESUtils.niceDateTime()); + } + // now run watcher functions from other modules on this content... + RESUtils.watchers.siteTable.forEach(function(callback) { + if (callback) callback(widgetContent[0]); + }); + + } + thisWidget.error = function(xhr, err) { + // alert('There was an error loading data for this widget. Did you type a bad path, perhaps? Removing this widget automatically.'); + // modules['dashboard'].removeWidget(thisWidget.optionsObject()); + if (xhr.status === 404) { + $(thisWidget.contents).html('
            This widget received a 404 not found error. You may have made a typo when adding it.
            '); + } else { + $(thisWidget.contents).html('
            There was an error loading data for this widget. Reddit may be under heavy load, or you may have provided an invalid path.
            '); + } + $(thisWidget.scrim).fadeOut(); + $(thisWidget.contents).fadeTo('fast',1); + } + }, + addDashboardShortcuts: function() { + var subButtons = document.querySelectorAll('.fancy-toggle-button'); + for (var h=0, len=subButtons.length; h0)) { + var subButtonsWrapper = $('
            '); + $(subButton).wrap(subButtonsWrapper); + // move this wrapper to the end (after any icons that may exist...) + if (isMulti) { + var theWrap = $(subButton).parent(); + $(theWrap).appendTo($(theWrap).parent()); + } + } + var dashboardToggle = document.createElement('span'); + dashboardToggle.setAttribute('class','REStoggle RESDashboardToggle'); + dashboardToggle.setAttribute('data-subreddit',thisSubredditFragment); + var exists=false; + for (var i=0, sublen=this.widgets.length; iview the dashboard

            ' + }); + e.target.classList.add('remove'); + } + }, + addTab: function(tabID, tabName) { + $('#siteTable.linklisting').append('
            '); + $('span.redditname').append(''+tabName+''); + $('#tab-'+tabID).click(function(e) { + location.hash = tabID; + $('span.redditname a').removeClass('active'); + $(this).addClass('active'); + $('.dashboardPane').hide(); + $('#'+tabID).show(); + }); + } +}; + +modules['subredditInfo'] = { + moduleID: 'subredditInfo', + moduleName: 'Subreddit Info', + category: 'UI', + options: { + hoverDelay: { + type: 'text', + value: 800, + description: 'Delay, in milliseconds, before hover tooltip loads. Default is 800.' + }, + fadeDelay: { + type: 'text', + value: 200, + description: 'Delay, in milliseconds, before hover tooltip fades away. Default is 200.' + }, + fadeSpeed: { + type: 'text', + value: 0.3, + description: 'Fade animation\'s speed. Default is 0.3, the range is 0-1. Setting the speed to 1 will disable the animation.' + }, + USDateFormat: { + type: 'boolean', + value: false, + description: 'Show date (subreddit created...) in US format (i.e. 08-31-2010)' + } + }, + description: 'Adds a hover tooltip to subreddits', + isEnabled: function() { + return RESConsole.getModulePrefs(this.moduleID); + }, + include: [ + /^https?:\/\/([a-z]+)\.reddit\.com\/[\?]*/i + ], + isMatchURL: function() { + return RESUtils.isMatchURL(this.moduleID); + }, + beforeLoad: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + var css = ''; + css += '.subredditInfoToolTip .subredditLabel { float: left; width: 140px; margin-bottom: 12px; }'; + css += '.subredditInfoToolTip .subredditDetail { float: left; width: 240px; margin-bottom: 12px; }'; + css += '.subredditInfoToolTip .blueButton { float: right; margin-left: 8px; }'; + css += '.subredditInfoToolTip .redButton { float: right; margin-left: 8px; }'; + RESUtils.addCSS(css); + } + }, + go: function() { + if ((this.isEnabled()) && (this.isMatchURL())) { + // create a cache for subreddit data so we only load it once even if the hover is triggered many times + this.subredditInfoCache = []; + this.srRe = /\/r\/(\w+)(?:\/(new|rising|controversial|top))?\/?$/i; + + // get subreddit links and add event listeners... + this.addListeners(); + RESUtils.watchForElement('siteTable', modules['subredditInfo'].addListeners); + } + }, + addListeners: function(ele) { + var ele = ele || document.body; + var subredditLinks = document.body.querySelectorAll('.listing-page a.subreddit, .comment .md a[href^="/r/"]'); + if (subredditLinks) { + var len=subredditLinks.length; + for (var i=0; i/r/' + escapeHTML(thisSubreddit) + ''); + header.appendChild(link[0]); + if (RESUtils.loggedInUser()) { + var subscribeToggle = $(''); + subscribeToggle + .attr('id', 'RESHoverInfoSubscriptionButton') + .addClass('RESFilterToggle') + .css('margin-left', '12px') + .hide() + .on('click', modules['subredditInfo'].toggleSubscription); + modules['subredditInfo'].updateToggleButton(subscribeToggle, false); + + header.appendChild(subscribeToggle[0]); + } + var body = '\ +
            \ + '+escapeHTML(thisSubreddit)+':
            \ + loading...\ +
            '; + def.notify(header, null); + if (typeof mod.subredditInfoCache[thisSubreddit] !== 'undefined') { + mod.writeSubredditInfo(mod.subredditInfoCache[thisSubreddit], def); + } else { + GM_xmlhttpRequest({ + method: "GET", + url: location.protocol + "//"+location.hostname+"/r/" + thisSubreddit + "/about.json?app=res", + onload: function(response) { + var thisResponse = safeJSON.parse(response.responseText, null, true); + if (thisResponse) { + mod.updateCache(thisSubreddit, thisResponse); + mod.writeSubredditInfo(thisResponse, def); + } else { + mod.writeSubredditInfo({}, def); + } + } + }); + } + }, + updateCache: function(subreddit, data) { + subreddit = subreddit.toLowerCase(); + if (!data.data) { + data = { data : data }; + } + this.subredditInfoCache = this.subredditInfoCache || []; + this.subredditInfoCache[subreddit] = $.extend(true, {}, this.subredditInfoCache[subreddit], data); + }, + writeSubredditInfo: function(jsonData, deferred) { + if (!jsonData.data) { + var srHTML = '
            Subreddit not found
            '; + var newBody = $(srHTML); + deferred.resolve(null, newBody) + return; + } + var utctime = jsonData.data.created_utc; + var d = new Date(utctime * 1000); + var isOver18; + jsonData.data.over18 === true ? isOver18 = 'Yes' : isOver18 = 'No'; + var srHTML = '
            '; + srHTML += '
            Subreddit created:
            ' + RESUtils.niceDate(d, this.options.USDateFormat.value) + ' (' + RESUtils.niceDateDiff(d) + ')
            '; + srHTML += '
            Subscribers:
            ' + RESUtils.addCommas(jsonData.data.subscribers) + '
            '; + srHTML += '
            Title:
            ' + escapeHTML(jsonData.data.title) + '
            '; + srHTML += '
            Over 18:
            ' + escapeHTML(isOver18) + '
            '; + // srHTML += '
            Description:
            ' + jsonData.data.description + '
            '; + srHTML += '
            '; + srHTML += '
            '; // closes bottomButtons div + srHTML += '
            '; + + var newBody = $(srHTML); + // bottom buttons will include: +filter +shortcut +dashboard (maybe sub/unsub too?) + if (modules['subredditManager'].isEnabled()) { + var theSC = document.createElement('span'); + theSC.setAttribute('style','display: inline-block !important;'); + theSC.setAttribute('class','REStoggle RESshortcut RESshortcutside'); + theSC.setAttribute('data-subreddit',jsonData.data.display_name.toLowerCase()); + var idx = -1; + for (var i=0, len=modules['subredditManager'].mySubredditShortcuts.length; i