Skip to content

Commit ca23a27

Browse files
committed
v2.8.0 merge
2 parents b305977 + d471cc8 commit ca23a27

9 files changed

+1313
-1466
lines changed

README.md

+2-2
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.7.0
3+
### SOX v2.8.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

@@ -22,7 +22,7 @@ Note: This project has no relation to Stack Overflow or Stack Exchange; it is si
2222

2323
2. Install the script. Clicking on 'install' below will make Tampermonkey prompt you automatically to install it.
2424

25-
- Official Version: <kbd>[install](https://github.com/soscripted/sox/raw/v2.7.0/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/v2.7.0/sox.user.js)</kbd>
25+
- Official Version: <kbd>[install](https://github.com/soscripted/sox/raw/v2.8.0/sox.user.js)</kbd>. <kbd>[view source](https://github.com/soscripted/sox/blob/v2.8.0/sox.user.js)</kbd>
2626
- 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>
2727

2828
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:

sox.common.js

+114-89
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
/* globals CHAT, StackExchange, jQuery */
12
(function(sox, $) {
23
'use strict';
34
const SOX_SETTINGS = 'SOXSETTINGS';
45
const commonInfo = JSON.parse(GM_getResourceText('common'));
56
const lastVersionInstalled = GM_getValue('SOX-lastVersionInstalled');
7+
var hookAjaxObject = {};
68

79
sox.info = {
810
version: (typeof GM_info !== 'undefined' ? GM_info.script.version : 'unknown'),
@@ -49,7 +51,11 @@
4951
let Stack;
5052
if (location.href.indexOf('github.com') === -1) { //need this so it works on FF -- CSP blocks window.eval() it seems
5153
Chat = (typeof window.CHAT === 'undefined' ? window.eval('typeof CHAT != \'undefined\' ? CHAT : undefined') : CHAT);
52-
Stack = (typeof Chat === 'undefined' ? (typeof StackExchange === 'undefined' ? window.eval('if (typeof StackExchange != "undefined") StackExchange') : (StackExchange || window.StackExchange)) : undefined);
54+
Stack = (typeof Chat === 'undefined'
55+
? (typeof StackExchange === 'undefined'
56+
? window.eval('if (typeof StackExchange != "undefined") StackExchange')
57+
: (StackExchange || window.StackExchange))
58+
: undefined);
5359
}
5460

5561
sox.Stack = Stack;
@@ -91,15 +97,13 @@
9197
return settings === undefined ? undefined : JSON.parse(settings);
9298
},
9399
save: function(settings) {
94-
GM_setValue(SOX_SETTINGS, typeof settings === 'string' ? settings : JSON.stringify(settings)); //if importing, it will already be a string so don't stringify the string!
100+
// If importing, it will already be a string so there's no need to stringify it
101+
GM_setValue(SOX_SETTINGS, typeof settings === 'string' ? settings : JSON.stringify(settings));
95102
},
96103
reset: function() {
97104
const keys = GM_listValues();
98105
sox.debug(keys);
99-
for (let i = 0; i < keys.length; i++) {
100-
const key = keys[i];
101-
GM_deleteValue(key);
102-
}
106+
keys.forEach(key => GM_deleteValue(key));
103107
},
104108
get accessToken() {
105109
const accessToken = GM_getValue('SOX-accessToken', false);
@@ -149,10 +153,9 @@
149153
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#sox_${name}`);
150154
svg.appendChild(use);
151155

152-
if (css) $(svg).css(css);
153156
svg.classList.add('sox-sprite');
154157
svg.classList.add(`sox-sprite-${name}`);
155-
return $(svg);
158+
return svg;
156159
},
157160
};
158161

@@ -257,36 +260,29 @@
257260
}
258261
sox.debug(`API: Sending request to URL: '${queryURL}'`);
259262

260-
$.ajax({
261-
type: 'get',
262-
url: queryURL,
263-
success: function(d) {
264-
if (d.backoff) {
265-
sox.error('SOX Error: BACKOFF: ' + d.backoff);
266-
} else if (d.error_id == 502) {
267-
sox.error('THROTTLE VIOLATION', d);
268-
} else if (d.error_id == 403) {
269-
sox.warn('Access token invalid! Opening window to get new one');
270-
window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
271-
alert('Your access token is no longer valid. A window has been opened to request a new one.');
263+
fetch(queryURL).then(apiResponse => apiResponse.json()).then(responseJson => {
264+
if (responseJson.backoff) {
265+
sox.error('SOX Error: BACKOFF: ' + responseJson.backoff);
266+
} else if (responseJson.error_id == 502) {
267+
sox.error('THROTTLE VIOLATION', responseJson);
268+
} else if (responseJson.error_id == 403) {
269+
sox.warn('Access token invalid! Opening window to get new one');
270+
window.open('https://stackexchange.com/oauth/dialog?client_id=7138&scope=no_expiry&redirect_uri=http://soscripted.github.io/sox/');
271+
alert('Your access token is no longer valid. A window has been opened to request a new one.');
272+
} else {
273+
if (useCache) {
274+
responseJson.items.forEach(item => {
275+
item.sox_request_time = new Date().getTime();
276+
finalItems.push(item);
277+
endpointCache.push(item);
278+
});
279+
GM_setValue('SOX-apiCache', JSON.stringify(apiCache));
280+
sox.debug('API: saving new cache', apiCache);
272281
} else {
273-
if (useCache) {
274-
d.items.forEach(item => {
275-
item.sox_request_time = new Date().getTime();
276-
finalItems.push(item);
277-
endpointCache.push(item);
278-
});
279-
GM_setValue('SOX-apiCache', JSON.stringify(apiCache));
280-
sox.debug('API: saving new cache', apiCache);
281-
} else {
282-
finalItems = d.items;
283-
}
284-
callback(finalItems);
282+
finalItems = responseJson.items;
285283
}
286-
},
287-
error: function(a, b, c) {
288-
sox.error('SOX Error: ' + b + ' ' + c);
289-
},
284+
callback(finalItems);
285+
}
290286
});
291287
},
292288
observe: function (targets, elements, callback) {
@@ -340,7 +336,7 @@
340336
},
341337
newElement: function(type, elementDetails) {
342338
const extras = {};
343-
const allowed = ['text', 'checkbox', 'radio', 'textarea', 'span'];
339+
const allowed = ['text', 'checkbox', 'radio', 'textarea', 'span', 'div', 'a'];
344340

345341
if (allowed.indexOf(type) != -1) {
346342
if (type == 'text') {
@@ -384,50 +380,56 @@
384380
return siteMatch ? siteMatch[1] : null;
385381
},
386382
createModal: function (params) {
387-
const $dialog = $('<aside/>', {
388-
'class': 's-modal js-modal-overlay js-modal-close js-stacks-managed-popup js-fades-with-aria-hidden sox-custom-dialog',
389-
'role': 'dialog',
390-
'aria-hidden': false,
391-
});
392-
if (params.css) $dialog.css(params.css);
393-
if (params.id) $dialog.attr('id', params.id);
394-
const $dialogInnerContainer = $('<div/>', {
395-
'class': 's-modal--dialog js-modal-dialog ',
396-
'style': 'min-width: 568px;',// top: 227.736px; left: 312.653px;',
397-
});
398-
if (params.css) $dialogInnerContainer.css(params.css);
399-
const $header = $('<h1/>', {
400-
'class': 's-modal--header fs-headline1 fw-bold mr48 js-first-tabbable sox-custom-dialog-header',
401-
'html': params.header,
402-
});
403-
const $mainContent = $('<div/>', {
404-
'class': 's-modal--body sox-custom-dialog-content',
405-
});
406-
if (params.html) $mainContent.html(params.html);
407-
const $closeButton = $('<button/>', {
408-
'class': 's-modal--close s-btn s-btn__muted js-modal-close js-last-tabbable',
409-
'click': () => $('.sox-custom-dialog').remove(),
410-
}).append($('<svg aria-hidden="true" class="svg-icon m0 iconClearSm" width="14" height="14" viewBox="0 0 14 14"><path d="M12 3.41L10.59 2 7 5.59 3.41 2 2 3.41 5.59 7 2 10.59 3.41 12 7 8.41 10.59 12 12 10.59 8.41 7z"></path></svg>'));
411-
412-
$dialogInnerContainer.append($header).append($mainContent).append($closeButton);
413-
$dialog.append($dialogInnerContainer);
414-
415-
return $dialog;
383+
const closeButtonSvg = `<svg aria-hidden="true" class="svg-icon m0 iconClearSm" width="14" height="14" viewBox="0 0 14 14">
384+
<path d="M12 3.41L10.59 2 7 5.59 3.41 2 2 3.41 5.59 7 2 10.59 3.41 12 7 8.41 10.59 12 12 10.59 8.41 7z"></path>
385+
</svg>`;
386+
387+
const dialog = document.createElement('aside');
388+
dialog.className = 's-modal js-modal-overlay js-modal-close js-stacks-managed-popup js-fades-with-aria-hidden sox-custom-dialog';
389+
dialog.role = 'dialog';
390+
dialog.ariaHidden = false;
391+
if (params.id) dialog.id = params.id;
392+
393+
const dialogInnerContainer = document.createElement('div');
394+
dialogInnerContainer.className = 's-modal--dialog js-modal-dialog';
395+
dialogInnerContainer.style.minWidth = '568px'; // top: 227.736px; left: 312.653px;
396+
397+
// if (params.css) $dialog.css(params.css)
398+
// if (params.css) $dialogInnerContainer.css(params.css);
399+
400+
const header = document.createElement('h1');
401+
header.className = 's-modal--header fs-headline1 fw-bold mr48 js-first-tabbable sox-custom-dialog-header';
402+
header.innerHTML = params.header;
403+
const mainContent = document.createElement('div');
404+
mainContent.className = 's-modal--body sox-custom-dialog-content';
405+
if (params.html) mainContent.innerHTML = params.html;
406+
407+
const closeButton = document.createElement('button');
408+
closeButton.className = 's-modal--close s-btn s-btn__muted js-modal-close js-last-tabbable';
409+
closeButton.onclick = () => document.querySelector('.sox-custom-dialog').remove();
410+
closeButton.insertAdjacentHTML('beforeend', closeButtonSvg);
411+
412+
dialogInnerContainer.appendChild(header);
413+
dialogInnerContainer.appendChild(mainContent);
414+
dialogInnerContainer.appendChild(closeButton);
415+
dialog.appendChild(dialogInnerContainer);
416+
417+
return dialog;
416418
},
417419
addButtonToHelpMenu: function (params) {
418-
const $li = $('<li/>');
419-
const $a = $('<a/>', {
420-
'href': 'javascript:void(0)',
421-
'id': params.id,
422-
'text': `SOX: ${params.linkText}`,
423-
});
424-
const $span = $('<span/>', {
425-
'class': 'item-summary',
426-
'text': params.summary,
427-
});
428-
$li.on('click', params.click);
429-
$li.append($a.append($span));
430-
$('.topbar-dialog.help-dialog.js-help-dialog > .modal-content ul').append($li);
420+
const liElement = document.createElement('li');
421+
const anchor = document.createElement('a');
422+
anchor.href = 'javascript:void(0)';
423+
anchor.id = params.id;
424+
anchor.innerText = `SOX: ${params.linkText}`;
425+
const span = document.createElement('span');
426+
span.className = 'item-summary';
427+
span.innerText = params.summary;
428+
429+
liElement.addEventListener('click', params.click);
430+
anchor.appendChild(span);
431+
liElement.appendChild(anchor);
432+
document.querySelector('.topbar-dialog.help-dialog.js-help-dialog .modal-content ul').appendChild(liElement);
431433
},
432434
surroundSelectedText: function(textarea, start, end) {
433435
// same wrapper code on either side (`$...$`)
@@ -480,6 +482,27 @@
480482

481483
sox.Stack.MarkdownEditor.refreshAllPreviews();
482484
},
485+
getCssProperty: function(element, propertyValue) {
486+
return window.getComputedStyle(element).getPropertyValue(propertyValue);
487+
},
488+
runAjaxHooks: function() {
489+
let originalOpen = XMLHttpRequest.prototype.open;
490+
XMLHttpRequest.prototype.open = function() {
491+
this.addEventListener('load', function() {
492+
for (const key in hookAjaxObject) {
493+
if (this.responseURL.match(new RegExp(key))) hookAjaxObject[key](); // if the URL matches the regex, then execute the respective function
494+
}
495+
});
496+
originalOpen.apply(this, arguments);
497+
}
498+
},
499+
addAjaxListener: function(regexToMatch, functionToExecute) {
500+
if (!regexToMatch) { // all information has been inserted in hookAjaxObject
501+
sox.helpers.runAjaxHooks();
502+
return;
503+
}
504+
hookAjaxObject[regexToMatch] = functionToExecute;
505+
},
483506
};
484507

485508
sox.site = {
@@ -493,9 +516,9 @@
493516
currentApiParameter: sox.helpers.getSiteNameFromLink(location.href),
494517
get name() {
495518
if (Chat) {
496-
return $('#footer-logo a').attr('title');
519+
return document.querySelector('#footer-logo a').title;
497520
} else { //using StackExchange object doesn't give correct name (eg. `Biology` is called `Biology Stack Exchange` in the object)
498-
return $('.js-topbar-dialog-corral .modal-content.current-site-container .current-site-link div').attr('title');
521+
return document.querySelector('.js-topbar-dialog-corral .modal-content.current-site-container .current-site-link div').title;
499522
}
500523
},
501524

@@ -507,7 +530,7 @@
507530
return this.types.meta;
508531
} else {
509532
// check if site is in beta or graduated
510-
if ($('.beta-title').length > 0) {
533+
if (document.querySelector('.beta-title')) {
511534
return this.types.beta;
512535
} else {
513536
return this.types.main;
@@ -517,10 +540,10 @@
517540
return null;
518541
},
519542
get icon() {
520-
return 'favicon-' + $('.current-site a:not([href*=\'meta\']) .site-icon').attr('class').split('favicon-')[1];
543+
return 'favicon-' + document.querySelector('.current-site a:not([href*=\'meta\']) .site-icon').className.split('favicon-')[1];
521544
},
522-
url: location.hostname, //e.g. "meta.stackexchange.com"
523-
href: location.href, //e.g. "https://meta.stackexchange.com/questions/blah/blah"
545+
url: location.hostname, // e.g. "meta.stackexchange.com"
546+
href: location.href, // e.g. "https://meta.stackexchange.com/questions/blah/blah"
524547
};
525548

526549
sox.location = {
@@ -537,9 +560,11 @@
537560
matchWithPattern: function(pattern, urlToMatchWith) { //commented version @ https://jsfiddle.net/shub01/t90kx2dv/
538561
if (pattern == 'SE1.0') { //SE.com && Area51.SE.com special checking
539562
if (urlToMatchWith) {
540-
if (urlToMatchWith.match(/https?:\/\/stackexchange\.com\/?/) || (sox.location.matchWithPattern('*://area51.stackexchange.com/*') && sox.site.href.indexOf('.meta.') === -1)) return true;
563+
if (urlToMatchWith.match(/https?:\/\/stackexchange\.com\/?/)
564+
|| (sox.location.matchWithPattern('*://area51.stackexchange.com/*') && sox.site.href.indexOf('.meta.') === -1)) return true;
541565
} else {
542-
if (location.href.match(/https?:\/\/stackexchange\.com\/?/) || (sox.location.matchWithPattern('*://area51.stackexchange.com/*') && sox.site.href.indexOf('.meta.') === -1)) return true;
566+
if (location.href.match(/https?:\/\/stackexchange\.com\/?/) ||
567+
(sox.location.matchWithPattern('*://area51.stackexchange.com/*') && sox.site.href.indexOf('.meta.') === -1)) return true;
543568
}
544569
return false;
545570
}
@@ -590,8 +615,8 @@
590615
if (sox.site.type == sox.site.types.chat) {
591616
return Chat.RoomUsers.current().name;
592617
} else {
593-
const $uname = $('.top-bar div.gravatar-wrapper-24'); //used to be $('body > div.topbar > div > div.topbar-links > a > div.gravatar-wrapper-24');
594-
return ($uname.length ? $uname.attr('title') : false);
618+
const username = document.querySelector('.s-topbar--item.s-user-card .s-avatar');
619+
return (username ? username.title : '');
595620
}
596621
},
597622
get loggedIn() {

0 commit comments

Comments
 (0)