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