|
| 1 | +/* globals CHAT, StackExchange, jQuery */ |
1 | 2 | (function(sox, $) {
|
2 | 3 | 'use strict';
|
3 | 4 | const SOX_SETTINGS = 'SOXSETTINGS';
|
4 | 5 | const commonInfo = JSON.parse(GM_getResourceText('common'));
|
5 | 6 | const lastVersionInstalled = GM_getValue('SOX-lastVersionInstalled');
|
| 7 | + var hookAjaxObject = {}; |
6 | 8 |
|
7 | 9 | sox.info = {
|
8 | 10 | version: (typeof GM_info !== 'undefined' ? GM_info.script.version : 'unknown'),
|
|
49 | 51 | let Stack;
|
50 | 52 | if (location.href.indexOf('github.com') === -1) { //need this so it works on FF -- CSP blocks window.eval() it seems
|
51 | 53 | 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); |
53 | 59 | }
|
54 | 60 |
|
55 | 61 | sox.Stack = Stack;
|
|
91 | 97 | return settings === undefined ? undefined : JSON.parse(settings);
|
92 | 98 | },
|
93 | 99 | 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)); |
95 | 102 | },
|
96 | 103 | reset: function() {
|
97 | 104 | const keys = GM_listValues();
|
98 | 105 | 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)); |
103 | 107 | },
|
104 | 108 | get accessToken() {
|
105 | 109 | const accessToken = GM_getValue('SOX-accessToken', false);
|
|
149 | 153 | use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#sox_${name}`);
|
150 | 154 | svg.appendChild(use);
|
151 | 155 |
|
152 |
| - if (css) $(svg).css(css); |
153 | 156 | svg.classList.add('sox-sprite');
|
154 | 157 | svg.classList.add(`sox-sprite-${name}`);
|
155 |
| - return $(svg); |
| 158 | + return svg; |
156 | 159 | },
|
157 | 160 | };
|
158 | 161 |
|
|
257 | 260 | }
|
258 | 261 | sox.debug(`API: Sending request to URL: '${queryURL}'`);
|
259 | 262 |
|
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); |
272 | 281 | } 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; |
285 | 283 | }
|
286 |
| - }, |
287 |
| - error: function(a, b, c) { |
288 |
| - sox.error('SOX Error: ' + b + ' ' + c); |
289 |
| - }, |
| 284 | + callback(finalItems); |
| 285 | + } |
290 | 286 | });
|
291 | 287 | },
|
292 | 288 | observe: function (targets, elements, callback) {
|
|
340 | 336 | },
|
341 | 337 | newElement: function(type, elementDetails) {
|
342 | 338 | const extras = {};
|
343 |
| - const allowed = ['text', 'checkbox', 'radio', 'textarea', 'span']; |
| 339 | + const allowed = ['text', 'checkbox', 'radio', 'textarea', 'span', 'div', 'a']; |
344 | 340 |
|
345 | 341 | if (allowed.indexOf(type) != -1) {
|
346 | 342 | if (type == 'text') {
|
|
384 | 380 | return siteMatch ? siteMatch[1] : null;
|
385 | 381 | },
|
386 | 382 | 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; |
416 | 418 | },
|
417 | 419 | 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); |
431 | 433 | },
|
432 | 434 | surroundSelectedText: function(textarea, start, end) {
|
433 | 435 | // same wrapper code on either side (`$...$`)
|
|
480 | 482 |
|
481 | 483 | sox.Stack.MarkdownEditor.refreshAllPreviews();
|
482 | 484 | },
|
| 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 | + }, |
483 | 506 | };
|
484 | 507 |
|
485 | 508 | sox.site = {
|
|
493 | 516 | currentApiParameter: sox.helpers.getSiteNameFromLink(location.href),
|
494 | 517 | get name() {
|
495 | 518 | if (Chat) {
|
496 |
| - return $('#footer-logo a').attr('title'); |
| 519 | + return document.querySelector('#footer-logo a').title; |
497 | 520 | } 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; |
499 | 522 | }
|
500 | 523 | },
|
501 | 524 |
|
|
507 | 530 | return this.types.meta;
|
508 | 531 | } else {
|
509 | 532 | // check if site is in beta or graduated
|
510 |
| - if ($('.beta-title').length > 0) { |
| 533 | + if (document.querySelector('.beta-title')) { |
511 | 534 | return this.types.beta;
|
512 | 535 | } else {
|
513 | 536 | return this.types.main;
|
|
517 | 540 | return null;
|
518 | 541 | },
|
519 | 542 | 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]; |
521 | 544 | },
|
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" |
524 | 547 | };
|
525 | 548 |
|
526 | 549 | sox.location = {
|
|
537 | 560 | matchWithPattern: function(pattern, urlToMatchWith) { //commented version @ https://jsfiddle.net/shub01/t90kx2dv/
|
538 | 561 | if (pattern == 'SE1.0') { //SE.com && Area51.SE.com special checking
|
539 | 562 | 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; |
541 | 565 | } 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; |
543 | 568 | }
|
544 | 569 | return false;
|
545 | 570 | }
|
|
590 | 615 | if (sox.site.type == sox.site.types.chat) {
|
591 | 616 | return Chat.RoomUsers.current().name;
|
592 | 617 | } 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 : ''); |
595 | 620 | }
|
596 | 621 | },
|
597 | 622 | get loggedIn() {
|
|
0 commit comments