Skip to content

Commit 26488ec

Browse files
committed
v2.5.0 merge
* Only inject into Github issues if you are on the SOX repo * Fix bugs in various features * Deprecate the 'hide HNQ' feauter (now implemented natively) * Improve SOX's performance with many behind-the-scenes changes: * Reduce jQuery usage * Reduce number of API requests by caching them for a short period * Improve SOX's use of MutationObservers by specifying specific targets to observe in all uses * Improve SOX's Github Community Profile * Remove EOL RawGit dependency
2 parents 3122edf + 6652482 commit 26488ec

10 files changed

+812
-600
lines changed

.eslintrc.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"no-trailing-spaces":"error",
3333
"prefer-const": "warn",
3434
"one-var": ["warn", "never"],
35-
"no-multiple-empty-lines": ["warn", { "max": 1 }]
35+
"no-multiple-empty-lines": ["warn", { "max": 1 }],
36+
"arrow-parens": ["warn", "as-needed"]
3637
}
37-
}
38+
}

README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![Join the chat at https://gitter.im/soscripted/sox](https://badges.gitter.im/soscripted/sox.svg)](https://gitter.im/soscripted/sox?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
22

3-
### SOX v2.4.0
3+
### SOX v2.5.0
44

55
Stack Overflow Extras (*SOX*) is a project that stemmed from the [Stack Overflow Optional Features (SOOF)](https://github.com/shu8/Stack-Overflow-Optional-Features) project.
66

@@ -10,10 +10,19 @@ Note: This project has no relation to Stack Overflow or Stack Exchange; it is si
1010

1111
## Installation & Requirements
1212

13-
1. Install [Tampermonkey](http://tampermonkey.net/) (for Chrome or Firefox). These are userscript managers that *must* be installed in order for this to work, as the script relies on certain `GM_*` functions in order to save your settings! Tampermonkey is available for many more browsers, and whilst we do not explicitly support them, SOX should work on them. **Note: Greasemonkey 4 and upwards [is not supported with SOX](https://github.com/soscripted/sox/issues/306).**
14-
2. Install the script. Clicking on 'install' below will make your userscript manager prompt you automatically to install it.
13+
1. Install a userscript manager; these are free extensions available for all popular browsers that allow you to manage and install userscripts, along with exposing certain code functions that SOX requires.
1514

16-
- Official Version: <kbd>[install](https://github.com/soscripted/sox/raw/v2.4.0/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/v2.4.0/sox.user.js)</kbd>
15+
We recommend [Tampermonkey](http://tampermonkey.net/) for Chrome and Firefox.
16+
17+
Whilst SOX only explicitly supports Chrome and Firefox, it should work on any popular browser that can run userscripts.
18+
19+
**Note: Greasemonkey 4 and upwards [is not supported with SOX](https://github.com/soscripted/sox/issues/306).**
20+
21+
**There seems to be [an issue with Tampermonkey on Firefox](https://github.com/Tampermonkey/tampermonkey/issues/477) where userscripts don't seem to run. If this happens, please restart your browser and/or computer before raising an issue on GitHub, as a restart seems to fix this!**
22+
23+
2. Install the script. Clicking on 'install' below will make Tampermonkey prompt you automatically to install it.
24+
25+
- Official Version: <kbd>[install](https://github.com/soscripted/sox/raw/v2.5.0/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/v2.5.0/sox.user.js)</kbd>
1726
- Development Version: <kbd>[install](https://github.com/soscripted/sox/raw/dev/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/dev/sox.user.js)</kbd>
1827

1928
3. Go to any site in the Stack Exchange Network (e.g. [Super User](http://superuser.com/) or [Stack Overflow](http://stackoverflow.com/)). You will automatically be asked to choose and save your settings. A toggle button (gears icon) will be added to your topbar where you can change these later on:

docs/index.html

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<!doctype html>
1+
<!DOCTYPE HTML>
22
<html>
33

44
<head>
55
<meta charset="utf-8">
66
<meta http-equiv="X-UA-Compatible" content="chrome=1">
7-
<title>sox by soscripted</title>
7+
<title>SOX by soscripted</title>
88

99
<link rel="stylesheet" href="/sox/assets/css/style.css?v=a419cba670696de26f5508f589ecdcb9bc1a6377">
1010
<meta name="viewport" content="width=device-width">
@@ -16,7 +16,7 @@
1616
<body>
1717
<div class="wrapper">
1818
<header>
19-
<h1>sox</h1>
19+
<h1>SOX</h1>
2020
<p>A userscript for the Stack Exchange websites to add a bunch of optional user-selectable features</p>
2121
<p class="view"><a href="https://github.com/soscripted/sox">View the Project on GitHub <small></small></a></p>
2222
</header>
@@ -46,43 +46,43 @@ <h3><a id="acccess-token" class="anchor" href="#access-token" aria-hidden="true"
4646

4747
<p>The SOX userscript adds a bunch of <strong>optional</strong> features to all sites in the Stack Exchange network. These can be toggled on or off from an easy to use control panel (see screenshot below).</p>
4848

49-
<p>Note: This project has no relation to Stack Overflow or Stack Exchange; it is simply a userscript that enhances the sites!</p>
49+
<p>Note: This project is <strong>not</strong> related to Stack Overflow or Stack Exchange; it is simply a userscript that enhances the sites!</p>
5050

51-
<p>##Installation &amp; Requirements</p>
51+
<h2>Installation &amp; Requirements</h2>
5252

5353
<ol>
5454
<li>Install <a href="http://www.greasespot.net/">Greasemonkey</a> (for Firefox), <a href="http://tampermonkey.net/">Tampermonkey</a> (for Chrome), or <a href="https://github.com/os0x/NinjaKit">NinjaKit</a> for Safari. These are userscript
5555
managers that <em>must</em> be installed in order for this to work, as the script relies on certain <code class="highlighter-rouge">GM_*</code> functions in order to save your settings!</li>
5656
<li>
57-
<p>Install the script. Clicking on install below will make your userscript manager prompt you automatically to install it.</p>
57+
<p>Install the script. Clicking on the 'install' button below will make your userscript manager prompt you automatically to install it.</p>
5858

5959
<ul>
60-
<li>Official Version: <kbd>[install](https://github.com/soscripted/sox/raw/v2.0.1/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/v2.0.1/sox.user.js)</kbd></li>
61-
<li>Development Version: <kbd>[install](https://github.com/soscripted/sox/raw/dev/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/dev/sox.user.js)</kbd></li>
60+
<li>Official Version: <kbd><a href="https://github.com/soscripted/sox/raw/master/sox.user.js">install</a></kbd>. <kbd><a href="https://github.com/soscripted/sox/blob/master/sox.user.js">view source</a></kbd></li>
61+
<li>Development Version: <kbd><a href="https://github.com/soscripted/sox/raw/dev/sox.user.js">install</a></kbd>. <kbd><a href="https://github.com/soscripted/sox/blob/dev/sox.user.js">view source</a></kbd></li>
6262
</ul>
6363
</li>
64-
<li>Go to any site in the Stack Exchange Network (eg. <a href="http://superuser.com/">Super User</a> or <a href="http://stackoverflow.com/">Stack Overflow</a>). You will automatically be asked to choose and save your settings. A toggle button
64+
<li>Go to any site in the Stack Exchange Network (e.g. <a href="http://superuser.com/">Super User</a> or <a href="http://stackoverflow.com/">Stack Overflow</a>). You will automatically be asked to choose and save your settings. A toggle button
6565
(gears icon) will be added to your topbar where you can change these later on:</li>
6666
</ol>
6767

6868
<p><img src="https://cloud.githubusercontent.com/assets/12533449/14296194/c732b1b2-fb2d-11e5-9563-1e34b12eada9.png" alt="newdialog" /></p>
6969

70-
<p>##What features are included?</p>
70+
<h2>What features are included?</h2>
7171

7272
<p>A full list of all the features is available on the SOX wiki page <a href="https://github.com/soscripted/sox/wiki/Features">here</a>.</p>
7373

74-
<p>##Bugs and Feature Requests</p>
74+
<h2>Bugs and Feature Requests</h2>
7575

7676
<p>Please post bugs and feature requests as issues on <a href="https://github.com/soscripted/sox">Github</a>, where we can track them easily and push updates quickly. Please <strong>do not</strong> post them as answers on Stack Apps – they are
7777
much harder to manage!</p>
7878

79-
<p>##Changes</p>
79+
<h2>Changes</h2>
8080

8181
<p>Please see the change log <a href="http://stackapps.com/a/6358">at Stack Apps</a>.</p>
8282
</section>
8383
<footer>
8484
<p>This project is maintained by <a href="https://github.com/soscripted">soscripted</a></p>
85-
<p><small>Hosted on GitHub Pages &mdash; Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
85+
<p><small>Hosted on GitHub Pages &mdash; theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
8686
</footer>
8787
</div>
8888
<script src="/sox/assets/js/scale.fix.js"></script>

sox.common.js

+130-26
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,39 @@
1717
sox.debug = function() {
1818
if (!sox.info.debugging) return;
1919
for (let arg = 0; arg < arguments.length; ++arg) {
20-
console.debug('SOX: ', arguments[arg]);
20+
console.debug('SOX:', arguments[arg]);
2121
}
2222
};
2323

2424
sox.log = function() {
2525
for (let arg = 0; arg < arguments.length; ++arg) {
26-
console.log('SOX: ', arguments[arg]);
26+
console.log('SOX:', arguments[arg]);
2727
}
2828
};
2929

3030
sox.warn = function() {
3131
for (let arg = 0; arg < arguments.length; ++arg) {
32-
console.warn('SOX: ', arguments[arg]);
32+
console.warn('SOX:', arguments[arg]);
3333
}
3434
};
3535

3636
sox.error = function() {
3737
for (let arg = 0; arg < arguments.length; ++arg) {
38-
console.error('SOX: ', arguments[arg]);
38+
console.error('SOX:', arguments[arg]);
3939
}
4040
};
4141

4242
sox.loginfo = function() {
4343
for (let arg = 0; arg < arguments.length; ++arg) {
44-
console.info('SOX: ', arguments[arg]);
44+
console.info('SOX:', arguments[arg]);
4545
}
4646
};
4747

4848
let Chat;
4949
let Stack;
5050
if (location.href.indexOf('github.com') === -1) { //need this so it works on FF -- CSP blocks window.eval() it seems
5151
Chat = (typeof window.CHAT === 'undefined' ? window.eval('typeof CHAT != \'undefined\' ? CHAT : undefined') : CHAT);
52-
sox.debug('CHAT', Chat);
5352
Stack = (typeof Chat === 'undefined' ? (typeof StackExchange === 'undefined' ? window.eval('if (typeof StackExchange != "undefined") StackExchange') : (StackExchange || window.StackExchange)) : undefined);
54-
sox.debug('Stack', Stack);
5553
}
5654

5755
sox.Stack = Stack;
@@ -104,7 +102,6 @@
104102
}
105103
},
106104
get accessToken() {
107-
sox.debug('SOX Access Token: ' + (GM_getValue('SOX-accessToken', false) === false ? 'NOT SET' : 'SET'));
108105
const accessToken = GM_getValue('SOX-accessToken', false);
109106
return (accessToken == -2 ? false : accessToken); //if the user was already asked once, the value is set to -2, so make sure this is returned as false
110107
},
@@ -138,16 +135,107 @@
138135
}
139136

140137
sox.helpers = {
141-
getFromAPI: function(type, id, sitename, filter, callback, sortby) {
142-
sox.debug('Getting From API with URL: https://api.stackexchange.com/2.2/' + type + '/' + id + '?order=desc&sort=' + (sortby || 'creation') + '&site=' + sitename + '&key=' + sox.info.apikey + '&access_token=' + sox.settings.accessToken);
138+
getFromAPI: function (details, callback) {
139+
let {
140+
ids,
141+
useCache = true,
142+
} = details;
143+
144+
const {
145+
endpoint,
146+
childEndpoint,
147+
sort = 'creation',
148+
order = 'desc',
149+
sitename,
150+
filter,
151+
limit,
152+
featureId,
153+
cacheDuration = 3, // Minutes to cache data for
154+
} = details;
155+
const baseURL = 'https://api.stackexchange.com/2.2/';
156+
const queryParams = [];
157+
158+
// Cache can only be used if the featureId and IDs (as an array) have been provided
159+
useCache = featureId && useCache && Array.isArray(ids);
160+
const apiCache = JSON.parse(GM_getValue('SOX-apiCache', '{}'));
161+
162+
if (!(featureId in apiCache)) apiCache[featureId] = {};
163+
const featureCache = apiCache[featureId];
164+
165+
if (!(endpoint in featureCache)) featureCache[endpoint] = [];
166+
const endpointCache = featureCache[endpoint];
167+
168+
const endpointToIdFieldNames = {
169+
'questions': 'question_id',
170+
'answers': 'answer_id',
171+
'users': 'user_id',
172+
'comments': 'comment_id',
173+
};
174+
175+
if (filter) queryParams.push(`filter=${filter}`);
176+
if (order) queryParams.push(`order=${order}`);
177+
if (limit) queryParams.push(`pagesize=${limit}`);
178+
queryParams.push(`sort=${sort}`);
179+
queryParams.push(`site=${sitename}`);
180+
queryParams.push(`key=${sox.info.apikey}`);
181+
queryParams.push(`access_token=${sox.settings.accessToken}`);
182+
const queryString = queryParams.join('&');
183+
184+
let finalItems = [];
185+
if (useCache) {
186+
// Count backwards so splicing doesn't change indices
187+
for (let i = ids.length; i >= 0; i--) {
188+
const cachedItemIndex = endpointCache.findIndex(item => {
189+
const idFieldName = endpointToIdFieldNames[endpoint];
190+
return item[idFieldName] === +ids[i];
191+
});
192+
193+
// Cache results for max. cacheDuraction minutes (convert to milliseconds)
194+
const earliestRequestTime = new Date().getTime() - (60 * cacheDuration * 1000);
195+
if (cachedItemIndex !== -1) {
196+
const cachedItem = endpointCache[cachedItemIndex];
197+
if (cachedItem.sox_request_time >= earliestRequestTime) {
198+
// If we have a cached item for this ID, delete it from `ids` so we don't request the API for it
199+
sox.debug(`API: [${featureId}:/${endpoint}/${ids[i]}] Using cached API item`);
200+
finalItems.push(cachedItem);
201+
ids.splice(i, 1);
202+
} else {
203+
// The cached item is now stale (too old); delete it
204+
sox.debug(`API: [${featureId}:/${endpoint}/${ids[i]}] Deleting stale cached item`);
205+
endpointCache.splice(cachedItemIndex, 1);
206+
}
207+
}
208+
}
209+
}
210+
211+
// IDs are optional for endpoints like /questions
212+
if (ids && Array.isArray(ids)) {
213+
if (ids.length) {
214+
ids = ids.join(';');
215+
} else if (useCache) {
216+
// The cache had details for all IDs; no need to request API at all
217+
sox.debug(`API: [${featureId}:/${endpoint}] API Cache had details for all requested IDs, skipping API request`);
218+
GM_setValue('SOX-apiCache', JSON.stringify(apiCache));
219+
sox.debug('API: Saving new cache', apiCache);
220+
callback(finalItems);
221+
return;
222+
}
223+
}
143224

144-
const filterQuery = filter ? '&filter=' + filter : '';
145-
// optional for queries like /questions
146-
const idPath = id ? '/' + id : '';
225+
const idPath = ids ? `/${ids}` : '';
226+
let queryURL;
227+
if (childEndpoint) {
228+
// e.g. /posts/{ids}/revisions
229+
queryURL = `${baseURL}${endpoint}${idPath}/${childEndpoint}?${queryString}`;
230+
} else {
231+
// e.g. /questions/{ids}
232+
queryURL = `${baseURL}${endpoint}${idPath}?${queryString}`;
233+
}
234+
sox.debug(`API: Sending request to URL: '${queryURL}'`);
147235

148236
$.ajax({
149237
type: 'get',
150-
url: 'https://api.stackexchange.com/2.2/' + type + idPath + '?order=desc&sort=' + (sortby || 'creation') + '&site=' + sitename + '&key=' + sox.info.apikey + '&access_token=' + sox.settings.accessToken + filterQuery,
238+
url: queryURL,
151239
success: function(d) {
152240
if (d.backoff) {
153241
sox.error('SOX Error: BACKOFF: ' + d.backoff);
@@ -158,17 +246,30 @@
158246
window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
159247
alert('Your access token is no longer valid. A window has been opened to request a new one.');
160248
} else {
161-
callback(d);
249+
if (useCache) {
250+
d.items.forEach(item => {
251+
item.sox_request_time = new Date().getTime();
252+
finalItems.push(item);
253+
endpointCache.push(item);
254+
});
255+
GM_setValue('SOX-apiCache', JSON.stringify(apiCache));
256+
sox.debug('API: saving new cache', apiCache);
257+
} else {
258+
finalItems = d.items;
259+
}
260+
callback(finalItems);
162261
}
163262
},
164263
error: function(a, b, c) {
165264
sox.error('SOX Error: ' + b + ' ' + c);
166265
},
167266
});
168267
},
169-
observe: function(elements, callback, toObserve) {
170-
sox.debug('observe: ' + elements);
171-
const observer = new MutationObserver(throttle((mutations) => {
268+
observe: function (targets, elements, callback) {
269+
sox.debug(`OBSERVE: '${elements}' on target(s)`, targets);
270+
if (!targets || (Array.isArray(targets) && !targets.length)) return;
271+
272+
const observer = new MutationObserver(throttle(mutations => {
172273
for (let i = 0; i < mutations.length; i++) {
173274
const mutation = mutations[i];
174275
const target = mutation.target;
@@ -177,8 +278,8 @@
177278
if (addedNodes) {
178279
for (let n = 0; n < addedNodes.length; n++) {
179280
if ($(addedNodes[n]).find(elements).length) {
180-
callback(target);
181281
sox.debug('fire: node: ', addedNodes[n]);
282+
callback(target);
182283
return;
183284
}
184285
}
@@ -190,19 +291,22 @@
190291
return;
191292
}
192293
}
193-
}, 250));
294+
}, 1500));
295+
296+
if (Array.isArray(targets)) {
297+
for (let i = 0; i < targets.length; i++) {
298+
const target = targets[i];
299+
if (!target) continue;
194300

195-
if (toObserve) {
196-
for (let i = 0; i < toObserve.length; i++) { //could be multiple elements with querySelectorAll
197-
observer.observe(toObserve[i], {
301+
observer.observe(target, {
198302
attributes: true,
199303
childList: true,
200304
characterData: true,
201305
subtree: true,
202306
});
203307
}
204308
} else {
205-
observer.observe(document.body, {
309+
observer.observe(targets, {
206310
attributes: true,
207311
childList: true,
208312
characterData: true,
@@ -247,7 +351,7 @@
247351
// answer ID, question ID, user ID, comment ID ("posts/comments/ID" NOT "comment1545_5566")
248352
getIDFromLink: function(link) {
249353
// test cases: https://regex101.com/r/6P9sDX/2
250-
const idMatch = link.match(/\/(\d+)($|\/|\?)/);
354+
const idMatch = link.match(/\/(\d+)/);
251355
return idMatch ? +idMatch[1] : null;
252356
},
253357
getSiteNameFromLink: function(link) {
@@ -426,4 +530,4 @@
426530
},
427531
};
428532

429-
})(window.sox = window.sox || {}, jQuery);
533+
})(window.sox = window.sox || {}, jQuery);

0 commit comments

Comments
 (0)