var settings, cache, utils, button, reddit, background; settings = new Store('settings', { 'cacheTime': 3, 'timeoutLength': 15, 'freshCutoff': 90, 'popupWidth': 640, 'shamelessPlug': false, 'waitForClick': false, 'checkMail': true, 'mailInterval': 5, 'mailSound': false, 'excludedDomains': 'secure.ingdirect.com\nchaseonline.chase.com\nonline.wellsfargo.com\nmail.google.com\ndocs.google.com', 'excludedRegex': 'chrome://.*\nchrome-extension://.*\nview-source://.*\nftp://.*\nhttps?://www\\.google\\.com/search.*\nhttps?://search\\.yahoo\\.com/search.*\nhttps?://www\\.bing\\.com/search.*\nhttps?://www\\.reddit\\.com/(?:r/(?:\\w|\\+)+/?)?(?:$|\\?count)' }); cache = new Store('cache'); /** * Create a new framework of utility functions. * @classDescription Creates a new framework of utility functions. * @type {Object} * @return {Boolean} Returns true. * @constructor */ function MHUtils() { return true; } /** * Convert an array to an object with properties of the array each having empty values, for use in "in" statements. * @alias MHUtils.objConvert(arr) * @param {Array} arr The array to be converted * @return {Object} Returns the object * @method */ MHUtils.prototype.objConvert = function (arr) { var obj = {}; for (var i = 0; i < arr.length; i++) { obj[arr[i]]=''; } return obj; }; /** * Escape special RegExp characters. * @alias MHUtils.regexEscape(str) * @param {String} str The string to be escaped * @return {String} Returns an escaped string * @method */ MHUtils.prototype.regexEscape = function (str) { return str.replace(/([.?*+\^$\[\]\\(){}\-])/g, "\\$1"); }; /** * Find the UNIX epoch time. * @alias MHUtils.epoch() * @return {Number} Returns the current epoch time * @method */ MHUtils.prototype.epoch = function () { return Math.floor(new Date().getTime() / 1000); }; /** * Iterate over an object. * @alias MHUtils.forEachIn(object, action) * @return {Boolean} Returns true. * @method */ MHUtils.prototype.forEachIn = function (object, action) { for (var property in object) { if (object.hasOwnProperty(property)) { action(property, object[property]); } } return true; }; /** * Iterates over an array. * @alias MHUtils.forEach(array, action) * @return {Boolean} Returns true. * @method */ MHUtils.prototype.forEach = function (array, action) { for (var i = 0; i < array.length; i++) { action(array[i]); } return true; }; /** * Parses a URL and returns a useful object. http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ * @alias MHUtils.parseURL(url) * @param {String} url A string with a full URL to be parsed. * @return {Object} Returns a URL object. * @method */ MHUtils.prototype.parseURL = function (url) { var a = document.createElement('a'); a.href = url; return { source: url, protocol: a.protocol.replace(':',''), host: a.hostname, port: a.port, query: a.search, params: (function (){ var ret = {}, seg = a.search.replace(/^\?/,'').split('&'), len = seg.length, i = 0, s; for (;i { const timeoutId = setTimeout(() => { reject(chrome.i18n.getMessage('api_timeout', settings.get('timeoutLength').toString())) }, ms); promise.then( (res) => { clearTimeout(timeoutId); resolve(res); }, (err) => { clearTimeout(timeoutId); reject(err); } ); }) } var promise = fetch(url, {method:data?'POST':'GET', redirect: 'follow', body: data }) if (settings.get('timeoutLength') !== 31) { promise = timeoutPromise(settings.get('timeoutLength') * 1000, promise) } // function loginRedirect(response) { // if (response.url.startsWith('https://www.reddit.com/login.json')) { // return fetch(response.url, { body: data }).then(function (response) { return response}) // } else { // return response // } // } promise // .then(loginRedirect) .then(processResponse) var req, apiTimeout, notificationArea; function processResponse(response) { if (!response.ok) { if (notificationArea) { notificationArea.innerHTML = '' + chrome.i18n.getMessage('api_error', response.status + ': ' + response.statusText) + '' } throw chrome.i18n.getMessage('api_error', response.status + ': ' + response.statusText); // throw Error(response.statusText); } if (response.url.startsWith('https://www.reddit.com/login.json')) { return fetch(response.url, { method:data?'POST':'GET', redirect: 'follow', body: data }).then(processResponse) } return response.json().then(function (json) { if (json.jquery && json.jquery[3][3][0] === '.error.USER_REQUIRED') { throw chrome.i18n.getMessage('login'); } if (cback) { cback(json); } if (notificationArea) { notificationArea.innerHTML = ''; notificationArea.style.display = 'none'; } }) } notificationArea = document.getElementById('notificationArea'); if (notificationArea) { notificationArea.style.display = 'block'; notificationArea.innerHTML = '' + chrome.i18n.getMessage('loading') + ''; } }; /** * Grabs info about a URL via the reddit API and caches it, then sets the browser button. * @alias RedditAPI.getInfo(url) * @param {String} url The URL of the page to grab info about. * @param {Number} tabId The ID of the tab to prepare. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.getInfo = function (url, tabId) { var apiTimeout, isCommentsPage, reqUrl, req, postsObj, postCount; function processResponse () { if (req.readyState === 4) { if (req.status === 200) { var response; clearTimeout(apiTimeout); response = JSON.parse(req.responseText); cache.set('modhash', response.data.modhash); postsObj = {}; postCount = 0; for (i = 0; i < response.data.children.length; i++) { var child; child = response.data.children[i]; postsObj[child.data.name] = { 'url': url, 'data': child.data }; postCount++; } cache.set(url, { 'count': postCount, 'posts': postsObj, 'cacheDate': utils.epoch(), 'isCommentsPage': isCommentsPage }); button.setBadgeFor(url, tabId); return true; } else { button.setBadgeError(tabId, chrome.i18n.getMessage('api_error', req.status.toString())); } } } function handleTimeout () { req.abort(); button.setBadgeError(tabId, chrome.i18n.getMessage('api_timeout', settings.get('timeoutLength').toString())); } button.setBadgeLoading(tabId); isCommentsPage = this.commentsMatchPattern.test(url); if (isCommentsPage) { var matches; matches = url.match(this.commentsMatchPattern); reqUrl = 'https://' + this.domain + '/by_id/t3_' + matches[3] + '.json?app=mh'; } else { reqUrl = 'https://' + this.domain + '/api/info.json?app=mh&url=' + encodeURIComponent(url); } req = new XMLHttpRequest(); req.open('GET', reqUrl, true); req.onreadystatechange = processResponse; req.send(null); if (settings.get('timeoutLength') !== 16) { apiTimeout = setTimeout(handleTimeout, settings.get('timeoutLength') * 1000); } }; /** * Votes a post up. * @alias RedditAPI.voteUpPost(event) * @param {String} thing The FULLNAME of the thing to vote up. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.voteUpPost = function (e) { var listItem, fullName, url, reqUrl, oldCache, voteWas, scoreWas, upsWas, downsWas, scoreElem, formData; function success () { if (voteWas === '1') { listItem.setAttribute('data-dir','0'); oldCache.posts[fullName].data.likes = null; listItem.setAttribute('data-score', scoreWas - 1); oldCache.posts[fullName].data.score = scoreWas - 1; scoreElem.innerHTML = scoreWas - 1; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [(upsWas - 1).toString(), downsWas.toString()])); listItem.setAttribute('data-ups', upsWas - 1); oldCache.posts[fullName].data.ups = upsWas - 1; } else if (voteWas === '0') { listItem.setAttribute('data-dir','1'); oldCache.posts[fullName].data.likes = true; listItem.setAttribute('data-score', scoreWas + 1); oldCache.posts[fullName].data.score = scoreWas + 1; scoreElem.innerHTML = scoreWas + 1; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [(upsWas + 1).toString(), downsWas.toString()])); listItem.setAttribute('data-ups', upsWas + 1); oldCache.posts[fullName].data.ups = upsWas + 1; } else if (voteWas === '-1') { listItem.setAttribute('data-dir','1'); oldCache.posts[fullName].data.likes = true; listItem.setAttribute('data-score', scoreWas + 2); oldCache.posts[fullName].data.score = scoreWas + 2; scoreElem.innerHTML = scoreWas + 2; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [(upsWas + 1).toString(), (downsWas - 1).toString()])); listItem.setAttribute('data-ups', upsWas + 1); oldCache.posts[fullName].data.ups = upsWas + 1; listItem.setAttribute('data-downs', downsWas - 1); oldCache.posts[fullName].data.downs = downsWas - 1; } cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode; fullName = listItem.id; voteWas = listItem.getAttribute('data-dir'); scoreWas = parseInt(listItem.getAttribute('data-score')); upsWas = parseInt(listItem.getAttribute('data-ups')); downsWas = parseInt(listItem.getAttribute('data-downs')); scoreElem = document.getElementById('count_' + listItem.id); url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/vote?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); if (voteWas === '1') { formData.append('dir','0'); } else if (voteWas === '0') { formData.append('dir','1'); } else if (voteWas === '-1') { formData.append('dir','1'); } this.apiTransmit('POST', reqUrl, formData, success); }; /** * Votes a post down. * @alias RedditAPI.voteDownPost(event) * @param {String} thing The FULLNAME of the thing to vote up. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.voteDownPost = function (e) { var listItem, fullName, url, reqUrl, oldCache, voteWas, scoreWas, upsWas, downsWas, scoreElem, formData; function success () { if (voteWas === '1') { listItem.setAttribute('data-dir','-1'); oldCache.posts[fullName].data.likes = false; listItem.setAttribute('data-score', scoreWas - 2); oldCache.posts[fullName].data.score = scoreWas - 2; scoreElem.innerHTML = scoreWas - 2; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [(upsWas - 1).toString(), (downsWas + 1).toString()])); listItem.setAttribute('data-ups', upsWas - 1); oldCache.posts[fullName].data.ups = upsWas - 1; listItem.setAttribute('data-downs', downsWas + 1); oldCache.posts[fullName].data.downs = downsWas + 1; } else if (voteWas === '0') { listItem.setAttribute('data-dir','-1'); oldCache.posts[fullName].data.likes = false; listItem.setAttribute('data-score', scoreWas - 1); oldCache.posts[fullName].data.score = scoreWas - 1; scoreElem.innerHTML = scoreWas - 1; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [upsWas.toString(), (downsWas + 1).toString()])); listItem.setAttribute('data-downs', downsWas + 1); oldCache.posts[fullName].data.downs = downsWas + 1; } else if (voteWas === '-1') { listItem.setAttribute('data-dir','0'); oldCache.posts[fullName].data.likes = null; listItem.setAttribute('data-score', scoreWas + 1); oldCache.posts[fullName].data.score = scoreWas + 1; scoreElem.innerHTML = scoreWas + 1; scoreElem.setAttribute('title', chrome.i18n.getMessage('score', [upsWas.toString(), (downsWas - 1).toString()])); listItem.setAttribute('data-downs', downsWas - 1); oldCache.posts[fullName].data.downs = downsWas - 1; } cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode; fullName = listItem.id; voteWas = listItem.getAttribute('data-dir'); scoreWas = parseInt(listItem.getAttribute('data-score')); upsWas = parseInt(listItem.getAttribute('data-ups')); downsWas = parseInt(listItem.getAttribute('data-downs')); scoreElem = document.getElementById('count_' + listItem.id); url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/vote?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); if (voteWas === '1') { formData.append('dir','-1'); } else if (voteWas === '0') { formData.append('dir','-1'); } else if (voteWas === '-1') { formData.append('dir','0'); } this.apiTransmit('POST', reqUrl, formData, success); }; /** * Saves a post. * @alias RedditAPI.savePost(event) * @param {String} thing The FULLNAME of the thing to save. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.savePost = function (e) { var listItem, fullName, url, reqUrl, oldCache, formData; function success () { listItem.setAttribute('data-saved', 'true'); listItem.className.replace(/\bsaved\b/,''); listItem.className += ' unsave'; e.srcElement.innerHTML = chrome.i18n.getMessage('action_unsave'); e.srcElement.onclick = function (event) {reddit.unsavePost(event)}; oldCache.posts[fullName].data.saved = true; cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode.parentNode; fullName = listItem.id; url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/save?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); this.apiTransmit('POST', reqUrl, formData, success); }; /** * Unsaves a post. * @alias RedditAPI.unsavePost(event) * @param {String} thing The FULLNAME of the thing to unsave. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.unsavePost = function (e) { var listItem, fullName, url, reqUrl, oldCache, formData; function success () { listItem.setAttribute('data-saved', 'false'); listItem.className.replace(/\bunsave\b/,''); listItem.className += ' saved'; e.srcElement.innerHTML = chrome.i18n.getMessage('action_save'); e.srcElement.onclick = function (event) {reddit.savePost(event)}; oldCache.posts[fullName].data.saved = false; cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode.parentNode; fullName = listItem.id; url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/unsave?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); this.apiTransmit('POST', reqUrl, formData, success); }; /** * Hides a post. * @alias RedditAPI.hidePost(event) * @param {String} thing The FULLNAME of the thing to hide. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.hidePost = function (e) { var listItem, fullName, url, reqUrl, oldCache, formData; function success () { listItem.setAttribute('data-hidestatus', 'true'); e.srcElement.innerHTML = chrome.i18n.getMessage('action_unhide'); e.srcElement.onclick = function (event) {reddit.unhidePost(event)}; oldCache.posts[fullName].data.hidden = true; cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode.parentNode; fullName = listItem.id; url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/hide?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); this.apiTransmit('POST', reqUrl, formData, success); }; /** * Unhides a post. * @alias RedditAPI.unhidePost(event) * @param {String} thing The FULLNAME of the thing to unhide. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.unhidePost = function (e) { var listItem, fullName, url, reqUrl, oldCache, formData; function success () { listItem.setAttribute('data-hidestatus', 'false'); e.srcElement.innerHTML = chrome.i18n.getMessage('action_hide'); e.srcElement.onclick = function (event) {reddit.hidePost(event)}; oldCache.posts[fullName].data.hidden = false; cache.set(url, oldCache); } listItem = e.srcElement.parentNode.parentNode.parentNode; fullName = listItem.id; url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/unhide?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); this.apiTransmit('POST', reqUrl, formData, success); }; /** * Brings up the report confirmation dialog. * @alias RedditAPI.confirmReport(event) * @param {Object} event The event object. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.confirmReport = function (e) { e.srcElement.className = 'report-confirm'; e.srcElement.removeAttribute('onclick'); e.srcElement.innerHTML = chrome.i18n.getMessage('report_confirm'); return true; }; /** * Resets the report link. * @alias RedditAPI.denyReport(event) * @param {Object} event The event object. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.denyReport = function (e) { e.srcElement.parentNode.className = 'report'; e.srcElement.parentNode.setAttribute('onclick', 'reddit.confirmReport(event)'); e.srcElement.parentNode.innerHTML = chrome.i18n.getMessage('action_report'); return true; }; /** * Reports a post. * @alias RedditAPI.reportPost(event) * @param {Object} event The event object. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.reportPost = function (e) { var listItem, fullName, url, reqUrl, oldCache, formData; function success () { listItem.setAttribute('data-hidestatus', 'true'); e.srcElement.parentNode.parentNode.childNodes[3].innerHTML = 'unhide'; e.srcElement.parentNode.parentNode.childNodes[3].onclick = function (event) {reddit.unhidePost(event)}; oldCache.posts[fullName].data.hidden = true; cache.set(url, oldCache); listItem.setAttribute('data-reportstatus', 'true'); e.srcElement.parentNode.innerHTML = chrome.i18n.getMessage('action_reported'); } listItem = e.srcElement.parentNode.parentNode.parentNode.parentNode; fullName = listItem.id; url = listItem.parentNode.getAttribute('data-url'); reqUrl = 'https://' + this.domain + '/api/report?app=mh'; oldCache = cache.get(url); formData = new FormData(); formData.append('id', fullName); formData.append('uh', cache.get('modhash')); this.apiTransmit('POST', reqUrl, formData, success); }; /** * Submits a comment. * @alias RedditAPI.submitComment(event) * @param {Object} event The event object. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.submitComment = function (e) { var listItem, fullName, status, submitButton, cancelButton, textarea, comment, formData; function success (response) { var postedCommentUrl, commentsUrl, url, oldCache; postedCommentUrl = response.jquery[18][3][0][0].data.id.split('_')[1]; commentsUrl = document.getElementById(fullName).parentNode.getElementsByClassName('thumblink')[0].getAttribute('href'); status.innerHTML = 'submitted! click to view your comment.'; submitButton.innerHTML = chrome.i18n.getMessage('button_submit'); cancelButton.innerHTML = chrome.i18n.getMessage('button_hide'); submitButton.setAttribute('disabled'); textarea.setAttribute('readonly'); textarea.onkeyup = function () {return true;}; url = document.getElementById(fullName).parentNode.getAttribute('data-url'); oldCache = cache.get(url); oldCache.posts[fullName].savedCommentText = ''; cache.set(url, oldCache); } submitButton = e.srcElement; listItem = submitButton.parentNode.parentNode.parentNode; fullName = listItem.id; status = submitButton.parentNode.getElementsByClassName('status')[0]; cancelButton = submitButton.parentNode.getElementsByClassName('cancel')[0]; textarea = e.srcElement.parentNode.getElementsByTagName('textarea')[0]; comment = settings.get('shamelessPlug') ? textarea.value + '\n\n*Posted from [Mostly Harmless](https://kerrick.github.com/Mostly-Harmless), a Google Chrome extension for awesome redditors.*' : textarea.value; if (textarea.value === '') { status.innerHTML = chrome.i18n.getMessage('error_empty'); } else { formData = new FormData(); formData.append('thing_id', fullName); formData.append('text', comment); formData.append('uh', cache.get('modhash')); status.innerHTML = 'submitting...'; try { reddit.apiTransmit('POST', 'https://' + this.domain + '/api/comment?app=mh', formData, success); } catch (error) { status.innerHTML = error; } } }; /** * Submits a link. * @alias RedditAPI.submitLink(event) * @param {Object} event The event object. * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.submitLink = function (e, tabId) { var url, title, subreddit, status, submitButton, formData; function success (response) { if (typeof response.jquery[16][3][0] !== 'undefined' && response.jquery[16][3][0].substring(0,15) === '.error.NO_LINKS') { status.innerHTML = 'that reddit only allows text posts'; } else if (typeof response.jquery[16][3][0] !== 'undefined' && response.jquery[16][3][0].substring(0,24) === '.error.SUBREDDIT_NOEXIST') { status.innerHTML = 'that reddit does not exist'; } else if (typeof response.jquery[12][3][0] !== 'undefined' && response.jquery[12][3][0].substring(0,18) === '.error.ALREADY_SUB') { status.innerHTML = 'already submitted! click to view that post.'; } else if (typeof response.jquery[10][3][0] !== 'undefined' && response.jquery[10][3][0].substring(0,14) === '.error.BAD_URL') { status.innerHTML = 'that URL cannot be submitted'; } else if (typeof response.jquery[16][3][0] !== 'undefined') { status.innerHTML = 'submitted! click to view your post.'; submitButton.innerHTML = chrome.i18n.getMessage('submit_page'); submitButton.setAttribute('disabled'); title.setAttribute('readonly'); subreddit.setAttribute('readonly'); cache.remove(url); button.setBadgeDefaults(parseInt(tabId)); } else { status.innerHTML = 'unknown error occured, please try again.' } } submitButton = e.srcElement; status = submitButton.parentNode.getElementsByClassName('status')[0]; title = document.getElementById('submit_title'); subreddit = document.getElementById('submit_reddit'); url = document.getElementById('submit_url'); if (title.value === '' || subreddit.value === '') { status.innerHTML = chrome.i18n.getMessage('error_empty'); } else { formData = new FormData(); formData.append('title', title.value); formData.append('url', url.value); formData.append('sr', subreddit.value); formData.append('kind', 'link'); formData.append('uh', cache.get('modhash')); status.innerHTML = 'submitting...'; try { reddit.apiTransmit('POST', 'https://' + this.domain + '/api/submit?app=mh', formData, success); } catch (error) { status.innerHTML = error; } } }; /** * Grabs the logged in user's reddits. * @alias RedditAPI.getReddits() * @return {Boolean} Returns true. * @method */ RedditAPI.prototype.getReddits = function () { reddit.apiTransmit('GET', 'https://' + this.domain + '/reddits/mine.json?app=mh', null, function (response) { cache.remove('reddits'); cache.set('reddits', response.data.children); return true; }); }; reddit = new RedditAPI('www.reddit.com'); /** * Creates a new framework of background processes * @classDescription Creates a new framework of background processes. * @type {Object} * @return {Boolean} Returns true. * @constructor */ function Background() { this.notificationIsShown = false; this.notification = null; return true; } /** * Clear cached data and reset cleared browser buttons * @alias Background.clearCache(tabId, info, tab) * @return {Boolean} Returns true. * @method */ Background.prototype.clearCache = function () { var oldCache, newCache; oldCache = cache.toObject(); newCache = { modhash: oldCache.modhash, reddits: oldCache.reddits }; utils.forEachIn(oldCache, function (cachedUrl, data) { chrome.tabs.query({url: cachedUrl}, function (results) { if (results.length > 0) { newCache[cachedUrl] = oldCache[cachedUrl]; } cache.fromObject(newCache); // It's messy to put it here since it'll get called over and over, but chrome.tabs.query is asynch. }); }); }; /** * Prepare the browser action (badge, popup, etc.) for a given tab. * @alias Background.prepareBrowserAction(tabId, info, tab) * @param {Number} tabId The ID of the tab to get data for. * @param {Object} info The info for the change as sent by Chrome. * @param {Object} tab The info for the tab as sent by Chrome. * @param {Boolean} force Optional: If true, this bypasses any checks. * @return {Boolean} Returns true. * @method */ Background.prototype.prepareBrowserAction = function (tabId, info, tab, force) { function setBadge() { if (cache.get(tab.url) === undefined || cache.get(tab.url).cacheDate - utils.epoch() < -60 * settings.get('cacheTime')) { console.log(chrome.i18n.getMessage('loading_api')); reddit.getInfo(tab.url, tabId); } else { console.log(chrome.i18n.getMessage('loading_cache')); button.setBadgeFor(tab.url, tabId); } return true; } if (force === true) { setBadge(); } else if (info.status === 'loading') { try { var excludedRegex, match; excludedRegex = settings.get('excludedRegex').split('\n'); excludedDomains = settings.get('excludedDomains').split('\n'); match = false; for (var i = 0; i < excludedRegex.length; i++) { if (tab.url.match(new RegExp(excludedRegex[i], "i")) !== null) { match = true; } } if (utils.parseURL(tab.url).host in utils.objConvert(excludedDomains)) { match = true; } if (settings.get('waitForClick') === false && match === false) { setBadge(); } else { button.setBadgeIgnore(tabId); } } catch (error) { button.setBadgeError(tabId, error.message); } } }; /** * Sets a timeout for the next time to run checkMail. * @alias Background.watchMail() * @return {Boolean} Returns true. * @method */ Background.prototype.watchMail = function () { var mailProcess, pop; function showNotification (hasMail) { var mailInterval; mailInterval = settings.get('mailInterval') * 1000 * 60; if (hasMail === true && background.notificationIsShown === false) { if (settings.get('mailSound') === true) { pop.play(); } background.notification = webkitNotifications.createNotification( '/pix/mail.png', // icon url - can be relative chrome.i18n.getMessage('orangered_received'), // notification title chrome.i18n.getMessage('orangered_action') // notification body text ); background.notification.onclick = function () { chrome.tabs.create({'url': 'https://' + reddit.domain + '/message/unread/', 'selected': true}); background.notification.cancel(); }; background.notification.onclose = function () { background.notificationIsShown = false; }; background.notificationIsShown = true; background.notification.show(); } else if (hasMail === false && background.notificationIsShown === true) { background.notification.cancel(); } window.setTimeout(checkPrefs, mailInterval); } function getMail () { reddit.apiTransmit('GET', 'https://' + reddit.domain + '/api/me.json?app=mh', null, function (response) { showNotification(response.data.has_mail); }); } function checkPrefs () { if (settings.get('checkMail') === true) { getMail(); } } pop = document.createElement('audio'); pop.src = '/pix/pop.ogg'; checkPrefs(); }; /** * Creates a new framework of popup processes * @classDescription Creates a new framework of background processes. * @type {Object} * @return {Boolean} Returns true. * @constructor */ function Popup() { return true; }; /** * Create and store the HTML for a list of posts. * @alias Popup.createListHTML(url) * @param {String} url The URL of the page to create the HTML for. * @return {String} Returns the generated HTML. * @method */ Popup.prototype.createListHTML = function (url) { var listHTML, staleCounter; if (cache.get(url) === undefined) { throw chrome.i18n.getMessage('error_not_cached'); } listHTML = '
'; listHTML += '
    '; staleCounter = 0; utils.forEachIn(cache.get(url).posts, function (name, value) { var data, voteDir, hiddenText, hideStatus, hideAction, saveText, saveStatus, saveAction, isFreshEnough, freshText, thumbSrc, commentText; data = value.data; if (data.likes === true) voteDir = 1; if (data.likes === null) voteDir = 0; if (data.likes === false) voteDir = -1; hideStatus = data.hidden; hiddenText = hideStatus === true ? 'unhide' : 'hide'; hideAction = hideStatus === true ? 'reddit.unhidePost(event)' : 'reddit.hidePost(event)'; saveStatus = data.saved; saveText = saveStatus === true ? 'unsave' : 'save'; saveAction = saveStatus === true ? 'reddit.unsavePost(event)' : 'reddit.savePost(event)'; isFreshEnough = settings.get('freshCutoff') === 91 ? 'true' : data.created_utc >= utils.epoch() - settings.get('freshCutoff') * 24 * 60 * 60; commentText = value.savedCommentText === undefined ? '' : value.savedCommentText; if (!isFreshEnough) staleCounter++; freshText = isFreshEnough ? 'fresh' : 'stale'; thumbSrc = data.thumbnail.indexOf('/') === 0 ? 'https://' + reddit.domain + data.thumbnail : data.thumbnail; listHTML += '
  1. '; listHTML += '
    '; listHTML += ''; listHTML += '' + data.score + ''; listHTML += ''; listHTML += '
    '; listHTML += ''; listHTML += '' + data.title + ''; listHTML += ''; listHTML += '
    '; listHTML += '' + data.title + ' '; listHTML += '(' + data.domain + ')'; listHTML += '
    '; listHTML += '' + chrome.i18n.getMessage('submitted_when', utils.prettyDate(utils.ISODateString(new Date(data.created_utc * 1000)))) + ' ' + chrome.i18n.getMessage('by') + ' '; listHTML += '' + data.author + ' ' + chrome.i18n.getMessage('to') + ' '; listHTML += '' + data.subreddit + ''; listHTML += '
    '; listHTML += ''; listHTML += '
    '; listHTML += '
    '; listHTML += '
    '; listHTML += '' + chrome.i18n.getMessage('leave_comment') + ''; listHTML += ''; listHTML += ''; listHTML += ''; listHTML += ''; listHTML += '
    '; listHTML += '
    '; listHTML += '
  2. '; }); listHTML += '
'; listHTML += ''; if (staleCounter > 0) { listHTML += '
' + chrome.i18n.getMessage('stale_posts_hiding', staleCounter.toString()) + '
'; } return listHTML; }; /** * Create the HTML for a submission form. * @alias Popup.createSubmitForm(tab) * @param {String} tab The Google Chrome Tab object. * @return {String} Returns the generated Form. * @method */ Popup.prototype.createSubmitForm = function (tab) { var submitHTML, redditCache; redditCache = cache.get('reddits'); submitHTML = '
'; submitHTML += '
'; submitHTML += '

' + chrome.i18n.getMessage('submit_page') + '

'; submitHTML += '
'; submitHTML += ''; submitHTML += ''; submitHTML += ''; submitHTML += '
'; submitHTML += '
'; submitHTML += ''; submitHTML += ''; submitHTML += '
'; submitHTML += '
'; submitHTML += ''; submitHTML += ''; if (redditCache !== undefined || reddit.getReddits() === true) { submitHTML += '' + chrome.i18n.getMessage('popular_choices') + ''; submitHTML += ''; } submitHTML += '
'; submitHTML += ''; submitHTML += '' submitHTML += '
'; return submitHTML; } /** * Replace the contents of the popup with a submit form. * @alias Popup.replaceWithSubmitForm() * @return {Boolean} Returns true. * @method */ Popup.prototype.replaceWithSubmitForm = function () { var close; chrome.tabs.getSelected(null, function(currTab) { document.getElementById('body').style.width = ''; document.getElementById('body').innerHTML = popup.createSubmitForm(currTab); close = document.createElement('button'); close = document.body.appendChild(close); close.innerHTML = chrome.i18n.getMessage('cancel_submission'); close.className = 'close-button'; close.setAttribute('onclick', 'window.close()'); }); }; /** * Show stale posts. * @alias Popup.showStalePosts() * @return {Boolean} Returns true. * @method */ Popup.prototype.showStalePosts = function () { var stalePosts; stalePosts = document.getElementsByClassName('stale'); document.getElementById('information').innerHTML = chrome.i18n.getMessage('stale_posts_showing'); while (stalePosts.length > 0) { stalePosts[0].className = 'stale-shown'; } }; /** * Show the comment form for a given post. * @alias Popup.showCommentForm(postId) * @param {String} postId The ID of the thing to show the comment form for. * @return {Boolean} Returns true. * @method */ Popup.prototype.showCommentForm = function (postId) { document.getElementById(postId).getElementsByClassName('comment')[0].style.display = 'block'; return true; }; /** * Cache a comment for a given post. * @alias Popup.cacheComment(event) * @param {Object} event The event object of the keyup. * @return {Boolean} Returns true. * @method */ Popup.prototype.cacheComment = function (e) { var value, postId, url, oldCache; value = e.srcElement.value; postId = e.srcElement.parentNode.parentNode.parentNode.id url = e.srcElement.parentNode.parentNode.parentNode.parentNode.getAttribute('data-url'); oldCache = cache.get(url); oldCache.posts[postId].savedCommentText = value; cache.set(url, oldCache); return true; };