User:MSchottlender-WMF/WhoWroteThatGadget.js

(function{function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})({1:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE.

var objectCreate = Object.create || objectCreatePolyfill var objectKeys = Object.keys || objectKeysPolyfill var bind = Function.prototype.bind || functionBindPolyfill

function EventEmitter { if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) { this._events = objectCreate(null); this._eventsCount = 0; }

this._maxListeners = this._maxListeners || undefined; } module.exports = EventEmitter;

// Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined; EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. var defaultMaxListeners = 10;

var hasDefineProperty; try { var o = {}; if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 }); hasDefineProperty = o.x === 0; } catch (err) { hasDefineProperty = false } if (hasDefineProperty) { Object.defineProperty(EventEmitter, 'defaultMaxListeners', {   enumerable: true,    get: function {      return defaultMaxListeners;    },    set: function(arg) {      // check whether the input is a positive number (whose value is zero or      // greater and not a NaN).      if (typeof arg !== 'number' || arg < 0 || arg !== arg)        throw new TypeError('"defaultMaxListeners" must be a positive number');      defaultMaxListeners = arg;    }  }); } else { EventEmitter.defaultMaxListeners = defaultMaxListeners; }

// Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { if (typeof n !== 'number' || n < 0 || isNaN(n)) throw new TypeError('"n" argument must be a positive number'); this._maxListeners = n; return this; };

function $getMaxListeners(that) { if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; }

EventEmitter.prototype.getMaxListeners = function getMaxListeners { return $getMaxListeners(this); };

// These standalone emit* functions are used to optimize calling of event // handlers for fast cases because emit itself often has a variable number of // arguments and can be deoptimized because of that. These functions always have // the same number of arguments and thus do not get deoptimized, so the code // inside them can execute faster. function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } function emitOne(handler, isFn, self, arg1) { if (isFn) handler.call(self, arg1); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1); } } function emitTwo(handler, isFn, self, arg1, arg2) { if (isFn) handler.call(self, arg1, arg2); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2); } } function emitThree(handler, isFn, self, arg1, arg2, arg3) { if (isFn) handler.call(self, arg1, arg2, arg3); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2, arg3); } }

function emitMany(handler, isFn, self, args) { if (isFn) handler.apply(self, args); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].apply(self, args); } }

EventEmitter.prototype.emit = function emit(type) { var er, handler, len, args, i, events; var doError = (type === 'error');

events = this._events; if (events) doError = (doError && events.error == null); else if (!doError) return false;

// If there is no 'error' event listener then throw. if (doError) { if (arguments.length > 1) er = arguments[1]; if (er instanceof Error) { throw er; // Unhandled 'error' event } else { // At least give some kind of context to the user var err = new Error('Unhandled "error" event. (' + er + ')'); err.context = er; throw err; }   return false; }

handler = events[type];

if (!handler) return false;

var isFn = typeof handler === 'function'; len = arguments.length; switch (len) { // fast cases case 1: emitNone(handler, isFn, this); break; case 2: emitOne(handler, isFn, this, arguments[1]); break; case 3: emitTwo(handler, isFn, this, arguments[1], arguments[2]); break; case 4: emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); break; // slower default: args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; emitMany(handler, isFn, this, args); }

return true; };

function _addListener(target, type, listener, prepend) { var m; var events; var existing;

if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');

events = target._events; if (!events) { events = target._events = objectCreate(null); target._eventsCount = 0; } else { // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (events.newListener) { target.emit('newListener', type,         listener.listener ? listener.listener : listener);

// Re-assign `events` because a newListener handler could have caused the // this._events to be assigned to a new object events = target._events; }   existing = events[type]; }

if (!existing) { // Optimize the case of one listener. Don't need the extra array object. existing = events[type] = listener; ++target._eventsCount; } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. existing = events[type] = prepend ? [listener, existing] : [existing, listener]; } else { // If we've already got an array, just append. if (prepend) { existing.unshift(listener); } else { existing.push(listener); }   }

// Check for listener leak if (!existing.warned) { m = $getMaxListeners(target); if (m && m > 0 && existing.length > m) { existing.warned = true; var w = new Error('Possible EventEmitter memory leak detected. ' +           existing.length + ' "' + String(type) + '" listeners ' +            'added. Use emitter.setMaxListeners to ' +            'increase limit.'); w.name = 'MaxListenersExceededWarning'; w.emitter = target; w.type = type; w.count = existing.length; if (typeof console === 'object' && console.warn) { console.warn('%s: %s', w.name, w.message); }     }    }  }

return target; }

EventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); };

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.prependListener = function prependListener(type, listener) { return _addListener(this, type, listener, true); };

function onceWrapper { if (!this.fired) { this.target.removeListener(this.type, this.wrapFn); this.fired = true; switch (arguments.length) { case 0: return this.listener.call(this.target); case 1: return this.listener.call(this.target, arguments[0]); case 2: return this.listener.call(this.target, arguments[0], arguments[1]); case 3: return this.listener.call(this.target, arguments[0], arguments[1],           arguments[2]); default: var args = new Array(arguments.length); for (var i = 0; i < args.length; ++i) args[i] = arguments[i]; this.listener.apply(this.target, args); } } }

function _onceWrap(target, type, listener) { var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; var wrapped = bind.call(onceWrapper, state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; }

EventEmitter.prototype.once = function once(type, listener) { if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); this.on(type, _onceWrap(this, type, listener)); return this; };

EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) { if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); this.prependListener(type, _onceWrap(this, type, listener)); return this; };

// Emits a 'removeListener' event if and only if the listener was removed. EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener;

if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function');

events = this._events; if (!events) return this;

list = events[type]; if (!list) return this;

if (list === listener || list.listener === listener) { if (--this._eventsCount === 0) this._events = objectCreate(null); else { delete events[type]; if (events.removeListener) this.emit('removeListener', type, list.listener || listener); }     } else if (typeof list !== 'function') { position = -1;

for (i = list.length - 1; i >= 0; i--) { if (list[i] === listener || list[i].listener === listener) { originalListener = list[i].listener; position = i;           break; }       }

if (position < 0) return this;

if (position === 0) list.shift; else spliceOne(list, position);

if (list.length === 1) events[type] = list[0];

if (events.removeListener) this.emit('removeListener', type, originalListener || listener); }

return this; };

EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) { var listeners, events, i;

events = this._events; if (!events) return this;

// not listening for removeListener, no need to emit if (!events.removeListener) { if (arguments.length === 0) { this._events = objectCreate(null); this._eventsCount = 0; } else if (events[type]) { if (--this._eventsCount === 0) this._events = objectCreate(null); else delete events[type]; }       return this; }

// emit removeListener for all listeners on all events if (arguments.length === 0) { var keys = objectKeys(events); var key; for (i = 0; i < keys.length; ++i) { key = keys[i]; if (key === 'removeListener') continue; this.removeAllListeners(key); }       this.removeAllListeners('removeListener'); this._events = objectCreate(null); this._eventsCount = 0; return this; }

listeners = events[type];

if (typeof listeners === 'function') { this.removeListener(type, listeners); } else if (listeners) { // LIFO order for (i = listeners.length - 1; i >= 0; i--) { this.removeListener(type, listeners[i]); }     }

return this; };

function _listeners(target, type, unwrap) { var events = target._events;

if (!events) return [];

var evlistener = events[type]; if (!evlistener) return [];

if (typeof evlistener === 'function') return unwrap ? [evlistener.listener || evlistener] : [evlistener];

return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); }

EventEmitter.prototype.listeners = function listeners(type) { return _listeners(this, type, true); };

EventEmitter.prototype.rawListeners = function rawListeners(type) { return _listeners(this, type, false); };

EventEmitter.listenerCount = function(emitter, type) { if (typeof emitter.listenerCount === 'function') { return emitter.listenerCount(type); } else { return listenerCount.call(emitter, type); } };

EventEmitter.prototype.listenerCount = listenerCount; function listenerCount(type) { var events = this._events;

if (events) { var evlistener = events[type];

if (typeof evlistener === 'function') { return 1; } else if (evlistener) { return evlistener.length; } }

return 0; }

EventEmitter.prototype.eventNames = function eventNames { return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; };

// About 1.5x faster than the two-arg version of Array#splice. function spliceOne(list, index) { for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) list[i] = list[k]; list.pop; }

function arrayClone(arr, n) { var copy = new Array(n); for (var i = 0; i < n; ++i) copy[i] = arr[i]; return copy; }

function unwrapListeners(arr) { var ret = new Array(arr.length); for (var i = 0; i < ret.length; ++i) { ret[i] = arr[i].listener || arr[i]; } return ret; }

function objectCreatePolyfill(proto) { var F = function {}; F.prototype = proto; return new F; } function objectKeysPolyfill(obj) { var keys = []; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { keys.push(k); } return k; } function functionBindPolyfill(context) { var fn = this; return function { return fn.apply(context, arguments); }; }

},{}],2:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/** * Interface to the [WikiWho](https://www.wikiwho.net/) WhoColor API. * * @class */ var Api = /*#__PURE__*/ function { /** * @param {Object} config * @cfg config.url The WikiWho base URL. * @cfg config.mwApi The mw.Api instance. * @constructor */ function Api { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

_classCallCheck(this, Api);

// Remove trailing slash from URL. this.url = (config.url || ).replace(/\/$/, ); this.mwApi = config.mwApi; this.results = null; } /**   * Fetch core messages needed for the revision popup, etc., making them available to mw.msg. * This is called just after the script is first loaded, and the request completes very quickly, * so we shouldn't need to bother with promises and such. */

_createClass(Api, [{   key: "fetchMessages",    value: function fetchMessages {      this.mwApi.loadMessages(['parentheses-start', 'talkpagelinktext', 'pipe-separator', 'contribslink', 'parentheses-end']);    }    /**     * Get parsed edit summary for the given revision.     * @param {number} revId     * @return {Promise} Resolving Object with keys 'comment' and 'size'.    	 */

}, {   key: "fetchEditSummary", value: function fetchEditSummary(revId) { return this.mwApi.ajax({       action: 'compare',        fromrev: revId,        torelative: 'prev',        prop: 'parsedcomment|size',        formatversion: 2      }).then(function (data) {        if (data.compare) {          return {            comment: data.compare.toparsedcomment,            size: data.compare.tosize - (data.compare.fromsize || 0)          };        }

return $.Deferred.reject; }, function (failData) { return $.Deferred.reject(failData); });   }    /**     * Get the value of a parameter from the given URL query string.     *     * @protected     * @param  {string} querystring URL query string     * @param  {string} param Parameter name     * @return {string|null} Parameter value; null if not found     */

}, {   key: "getQueryParameter", value: function getQueryParameter(querystring, param) { var urlParams, regex, results;

if (querystring === '') { return null; }

try { urlParams = new URLSearchParams(querystring); return urlParams.get(param); } catch (err) { // Fallback for IE and Edge // eslint-disable-next-line no-useless-escape param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); regex = new RegExp("[?&]".concat(param, "=([^&#]*)")); results = regex.exec(querystring); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); }   }    /**     * Get a WhoColor API URL based on a given wiki URL. *    * @param  {string} wikiUrl URL of the wiki page that we want to analyze. * @return {string} Ajax URL for the data from WhoColor. */

}, {   key: "getAjaxURL", value: function getAjaxURL(wikiUrl) { var parts, oldId, title, lang, matches, queryString, linkNode = document.createElement('a'); linkNode.href = wikiUrl; queryString = linkNode.search; title = this.getQueryParameter(queryString, 'title');

if (title) { // URL is like: https://en.wikipedia.org/w/index.php?title=Foo&oldid=123 matches = linkNode.hostname.match(/([a-z]+)\.wiki.*/i); lang = matches[1]; } else { // URL is like: https://en.wikipedia.org/wiki/Foo matches = wikiUrl.match(/:\/\/([a-z]+).wikipedia.org\/wiki\/([^#?]*)/i); lang = matches[1]; title = matches[2]; }

parts = [this.url, lang, 'whocolor/v1.0.0-beta', title]; // Add oldid if it's present.

oldId = this.getQueryParameter(queryString, 'oldid');

if (oldId) { parts.push(oldId); } // Compile the full URL.

return parts.join('/') + '/'; }   /**     * Get the WikiWho replacement for `.mw-parser-output` HTML. * @return {string} */

}, {   key: "getReplacementHtml", value: function getReplacementHtml { return this.results.extended_html; }   /**     * Get user and revision information for a given token. *    * @param {number} tokenId * @return {{revisionId: *, score: *, userId: *, username: *, revisionTime: *}|boolean} Object * that represents the token info or false if a token wasn't found. */

}, {   key: "getTokenInfo", value: function getTokenInfo(tokenId) { var revId, revision, username, isIP, score; // Get the token information. results.tokens structure: // [ [ conflict_score, str, o_rev_id, in, out, editor/class_name, age ], ... ]     // e.g. Array(7) [ 0, "indicate", 769691068, [], [], "18201938", 76652371.587203 ]

var token = this.results.tokens[tokenId];

if (!token) { return false; } // Get revision information. results.revisions structure: // { rev_id: [ timestamp, parent_rev_id, user_id, editor_name ], ... }     // e.g. Array(4) [ "2017-03-11T02:12:47Z", 769315355, "18201938", "Biogeographist" ]

revId = token[2]; revision = this.results.revisions[revId]; username = revision[3]; // WikiWho prefixes IP addresses with '0|'.

isIP = username.slice(0, 2) === '0|'; username = isIP ? username.slice(2) : username; // Get the user's edit score (percentage of content edited). // results.present_editors structure: // [ [ username, user_id, score ], ... ]

for (var i = 0; i < this.results.present_editors.length; i++) { if (this.results.present_editors[i][0] === username) { score = parseFloat(this.results.present_editors[i][2]).toFixed(1); break; }     } // Put it all together.

return { username: username, userId: token[5], isIP: isIP, revisionId: revId, revisionTime: new Date(revision[0]), score: score };   }    /**     * Get the WikiWho data for a given wiki page. *    * @param {string} wikiUrl URL of the wiki page that we want to analyze. * @return {Promise} A promise that resolves when the data is ready, * or rejects if there was an error. */

}, {   key: "getData", value: function getData(wikiUrl) { var _this = this;

var _getJsonData, retry = 1, retries = 4;

if (this.resultsPromise) { return this.resultsPromise; }

_getJsonData = function getJsonData { return $.getJSON(_this.getAjaxURL(wikiUrl)).then(function (result) {         // Handle error response.          if (!result.success) {            // The API gives us an error message, but we don't use it because it's only            // in English. Some of the error messages are:            // * result.info: "Requested data is not currently available in WikiWho            //   database. It will be available soon."            // * result.error: "The article (x) you are trying to request does not exist            //   in english Wikipedia."            // We do add the full error details to the console, for easier debugging.            var errCode = result.info && result.info.match(/data is not currently available/i) ? 'refresh' : 'contact';            window.console.error('WhoWroteThat encountered a "' + errCode + '" error:', result);

if (errCode === 'refresh' && retry <= retries) { // Return an intermediate Promise to handle the wait. // The time to wait gets progressively longer for each retry. return new Promise(function (resolve) {               return setTimeout(resolve, 1000 * retry);              }).then(function  {                // Followed by a (recursive) Promise to do the next request.                window.console.log('WhoWroteThat Api::getData retry ' + retry);                retry++;                return _getJsonData;              }); }

return $.Deferred.reject(errCode); } // Report retry count.

if (retry > 1) { window.console.info('WhoWroteThat Api::getData total retries: ' + (retry - 1)); } // Store all results.

_this.results = result; }, function (jqXHR) { // All other errors are likely to be 4xx and 5xx, and the only one that the user // might be able to recover from is 429 Too Many Requests. var errCode = 'contact';

if (jqXHR.status === 429) { errCode = 'later'; }

return $.Deferred.reject(errCode); });     };

this.resultsPromise = _getJsonData; return this.resultsPromise; } }]);

return Api; };

var _default = Api; exports["default"] = _default;

},{}],3:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

var _InfoBarWidget = _interopRequireDefault(require("./InfoBarWidget"));

var _RevisionPopupWidget = _interopRequireDefault(require("./RevisionPopupWidget"));

var _Controller = _interopRequireDefault(require("./Controller"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/** * Application class, responsible for managing the WWT ui * and the actions on the DOM returned by the API * * @class */ var App = /*#__PURE__*/ function { /**  * Only instantiate once, so the initialization doesn't run again * even if this is called on multiple clicks/calls *  * @constructor * @param {App} A class instance */ function App { var _this = this;

_classCallCheck(this, App);

// Instantiate only once if (!App.instance) { App.instance = this; }

this.model = _Controller["default"].getModel; this.revisionPopup = new _RevisionPopupWidget["default"]; this.widget = new _InfoBarWidget["default"]; // Attach widget

if ($('body').hasClass('skin-timeless')) { $('#mw-content-wrapper').prepend(this.widget.$element); } else { $('#content').prepend(this.widget.$element); } // Attach popup

$('body').append(this.revisionPopup.$element); // Attach events

this.model.on('state', function (state) {     if (state === 'ready') {        // TODO: We need to sort something out here where either        // we detach those events on 'dismiss' or we make sure        // that we do not reattach events if they were previously        // already attached.        // This problem existed before the MVC refactoring, but is        // more visible now, and may have more potential challenges        // when we allow for VE to be dismissed without save (in which // case no need to reattach events to the DOM we're launching)       // vs redoing the operation after a VE save without reload (in // which case we need to re-attach those events).       _this.attachContentListeners(_this.model.getContentWrapper);

_this.scrollDown; }   });    return App.instance;  }  /**   * Scroll down to the content on a diff page   * if it is below or in the lower third of the viewport.   */

_createClass(App, [{   key: "scrollDown",    value: function scrollDown {      var viewBottom = window.scrollY + window.innerHeight * (2 / 3),          $diffHead = $('h2.diff-currentversion-title');

if ($diffHead.length === 1) { var contentTop = $diffHead.offset.top, infobarHeight = this.widget.$element.outerHeight(true);

if (contentTop > viewBottom) { // Scroll to below the WWT info bar. Redundant selector is for Safari. $('html, body').animate({           scrollTop: contentTop - infobarHeight          }); }     }    }    /**     * Activate all the spans belonging to the given user. *    * @param {jQuery} $content The content to apply events on     * @param {number} editorId */

}, {   key: "activateSpans", value: function activateSpans($content, editorId) { $content.find('.token-editor-' + editorId).addClass('active'); }   /**     * Deactivate all spans. *    * @param {jQuery} $content The content to apply events on     */

}, {   key: "deactivateSpans", value: function deactivateSpans($content) { $content.find('.mw-parser-output .editor-token').removeClass('active'); }   /**     * Extract token and editor IDs from a WikiWho span element with `id='token-X'` and * `class='token-editor-Y'` attributes. *    * @param {HTMLElement} element * @return {Object} An object with two parameters: tokenId and editorId (string). */

}, {   key: "getIdsFromElement", value: function getIdsFromElement(element) { var out = { tokenId: false, editorId: false },         tokenMatches = element.id.match(/token-(\d+)/), editorMatches = element.className.match(/token-editor-([^\s]+)/);

if (tokenMatches && tokenMatches[1]) { out.tokenId = parseInt(tokenMatches[1]); }

if (editorMatches && editorMatches[1]) { out.editorId = editorMatches[1]; }

return out; }   /**     * Add listeners to: *  - highlight attribution; *  - show the RevisionPopupWidget; and *  - scroll to the right place for fragment links. *    * @param {jQuery} $content The content to apply events on     */

}, {   key: "attachContentListeners", value: function attachContentListeners($content) { var _this2 = this;

$content.find('.mw-parser-output .editor-token').on('mouseenter', function (e) {       if (_this2.revisionPopup.isVisible) {          return;        }

var ids = _this2.getIdsFromElement(e.currentTarget), tokenInfo = _Controller["default"].getApi.getTokenInfo(ids.tokenId);

_this2.activateSpans($content, ids.editorId);

_this2.widget.setUsernameInfo(tokenInfo.username); }).on('mouseleave', function { if (_this2.revisionPopup.isVisible) { return; }

_this2.deactivateSpans($content);

_this2.widget.clearUsernameInfo; });     $content.find('.editor-token').on('click', function (e) { var ids = _this2.getIdsFromElement(e.currentTarget), tokenInfo = _Controller["default"].getApi.getTokenInfo(ids.tokenId);

_this2.activateSpans($content, ids.editorId);

_this2.widget.setUsernameInfo(tokenInfo.username);

_this2.revisionPopup.show(tokenInfo, $(e.currentTarget));

_this2.revisionPopup.once('toggle', function {          _this2.deactivateSpans($content);

_this2.widget.clearUsernameInfo; }); // eslint-disable-next-line one-var

var reqStartTime = Date.now; // Fetch edit summary then re-render the popup.

_Controller["default"].getApi.fetchEditSummary(tokenInfo.revisionId).then(function (successData) {         var delayTime = Date.now - reqStartTime < 250 ? 250 : 0;          Object.assign(tokenInfo, successData);          setTimeout(function  { _this2.revisionPopup.show(tokenInfo, $(e.target)); }, delayTime);       }, function  {          // Silently fail. The revision info provided by WikiWho is still present, which is          // the important part, so we'll just show what we have and throw a console warning.          mw.log.warn("WhoWroteThat failed to fetch the summary for revision ".concat(tokenInfo.revisionId));        }); });     /*       * Modify fragment link scrolling behaviour to take into account the width of the infobar at       * the top of the screen, to prevent the targeted heading or citation from being hidden.       */

$content.find("a[href^='#']").on('click', function (event) {       var targetId, linkOffset, infobarHeight;

if (!_this2.widget.isVisible) { // Use the default if WWT is not active. return; }

targetId = decodeURIComponent(event.currentTarget.hash).replace(/^#/, ''); event.preventDefault; // After preventing the default event from doing it, set the URL bar fragment manually.

window.location.hash = targetId; // After setting that, manually scroll to the correct place.

linkOffset = $(document.getElementById(targetId)).offset.top; infobarHeight = _this2.widget.$element.outerHeight(true); window.scrollTo(0, linkOffset - infobarHeight); });   }  }]);

return App; };

var _default = App; // This should be able to load with 'require'

exports["default"] = _default; module.exports = App;

},{"./Controller":4,"./InfoBarWidget":5,"./RevisionPopupWidget":7}],4:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

var _Api = _interopRequireDefault(require("./Api"));

var _Model = _interopRequireDefault(require("./Model"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var $ = window.$ || require('jquery'); /** * An activation singleton, responsible for activating and attaching the * button that activates the system when it is applicable. * * @class */

var Controller = /*#__PURE__*/ function { /**  * Initialize if it is the first time, and cache the * initialization for the next times. */ function Controller { var _this = this;

_classCallCheck(this, Controller);

if (!Controller.instance) { this.initialized = false; this.link = null; this.namespace = null; this.mainPage = false; this.translations = {}; Controller.instance = this; this.app = null; this.api = null; this.model = new _Model["default"]; // Events

this.model.on('active', function (isActive) {       _this.toggleLinkActiveState(isActive); // Toggle a class for CSS to style links appropriately.

_this.model.getContentWrapper.toggleClass('wwt-active', isActive); }); // Events

this.model.on('enabled', function (isEnabled) {       _this.getButton.toggle(isEnabled);      }); }

return Controller.instance; } /**   * Get the model attached to this controller *  * @return {Model} Current model */

_createClass(Controller, [{   key: "getModel",    value: function getModel {      return this.model;    }    /**     * Get the API class attached to this controller     *     * @return {Api} Current API     */

}, {   key: "getApi", value: function getApi { return this.api; }   /**     * Initialize the process *    * @param {jQuery} $content Page content * @param {Object} config Configuration options * @param {string} [config.lang="en"] User interface language * @param {Object} [config.translations={}] Object with all translations * organized by language key with data of translation key/value pairs. * @param {string} [config.namespace] Page namespace. Falls back to reading * directly from mw.config * @param {string} [config.mainPage] Whether the current page is the main page * of the wiki. Falls back to reading directly from mw.config */

}, {   key: "initialize", value: function initialize($content, config) { if (this.model.isInitialized) { return; }

this.model.initialize($content, config);

if (!this.model.isValidPage) { return; }

this.translations = config.translations || {}; // This validation is for tests, where // mw is not defined. We don't care to test // whether messages are set properly, since that // has its own tests in the mw.messsages bundle

if (window.mw) { this.api = new _Api["default"]({         url: config.wikiWhoUrl,          mwApi: new mw.Api        }); this.api.fetchMessages; // Load all messages

mw.messages.set(Object.assign({}, // Manually create fallback on English this.translations.en, this.translations[this.model.getLang])); // Add a portlet link to 'tools'

this.link = mw.util.addPortletLink('p-tb', '#', mw.msg('whowrotethat-activation-link'), 't-whowrotethat', mw.msg('whowrotethat-activation-link-tooltip')); this.initialized = true; }   }    /**     * Launch WWT application *    * @return {jQuery.Promise} Promise that is resolved when the system * has finished launching, or is rejected if the system has failed to launch */

}, {   key: "launch", value: function launch { var _this2 = this;

if (!this.model.isEnabled) { window.console.log('Who Wrote That: Could not launch. System is disabled.'); return $.Deferred.reject; } // Cache the current version. // We might replace it in a minute, but if the user will // shut the system down while we fetch, we want the // original available.

this.model.cacheOriginal; return this.loadDependencies.then(function {        if (!_this2.app) {          // Only load after dependencies are loaded          // And only load once          // We do this trick because our widgets are dependent          // on OOUI, which is not available at construction time.          // When we launch, we load dependencies and the first run          // should make sure we also initialize the widget app          var App = require('./App');

_this2.app = new App; }

_this2.model.toggleActive(true);

_this2.model.setState('pending');

return _this2.api.getData(window.location.href).then( // Success handler.       function  {          // There could be time that passed between          // activating the promise request and getting the          // answer. During that time, the user may          // have dismissed the system.          // We should only replace the DOM and declare          // ready if the system is actually ready to be          // replaced.          // On subsequent launches, this promise will run          // again (no-op as an already-resolved promise)          // and the operation below will be re-triggered          // with the replacements          if (_this2.model.isActive) {            // Insert modified HTML.            _this2.model.cacheOriginal;

_this2.model.getContentWrapper.html(_this2.api.getReplacementHtml);

_this2.model.setState('ready'); }

return _this2.model.getContentWrapper; }, // Error handler. function (errorCode) { _this2.model.setState('err', errorCode); });     });    }    /**     * Close the WWT application */

}, {   key: "dismiss", value: function dismiss { if (!this.model.isActive) { window.console.log('Who Wrote That: Could not dismiss. System is not active.'); return; }

this.model.toggleActive(false); // Revert back to the original content

this.model.getContentWrapper.html(this.model.getOriginalContent.html); }   /**     * Toggle the system; if already launched, dismiss it, and vise versa. */

}, {   key: "toggle", value: function toggle { if (this.model.isActive) { this.dismiss; } else { this.launch; }   }    /**     * Set the link text and tooltip. * @param {boolean} [active] The state to toggle to. */

}, {   key: "toggleLinkActiveState", value: function toggleLinkActiveState(active) { var anchor = this.link.querySelector('a');

if (active) { anchor.textContent = mw.msg('whowrotethat-deactivation-link'); anchor.title = ''; } else { anchor.textContent = mw.msg('whowrotethat-activation-link'); anchor.title = mw.msg('whowrotethat-activation-link-tooltip'); }   }    /**     * Load the required dependencies for the full script *    * @return {jQuery.Promise} Promise that is resolved when * all dependencies are ready and loaded. */

}, {   key: "loadDependencies", value: function loadDependencies { if (!window.mw) { // This is for test environment only, where mw       // is not defined. return $.Deferred.resolve; }

return $.when($.ready, // jQuery's document.ready     mw.loader.using([// MediaWiki dependencies 'oojs-ui', 'oojs-ui.styles.icons-user', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles', 'mediawiki.special.changeslist', 'mediawiki.widgets.datetime', 'moment'])); }   /**     * Get the jQuery object representing the activation button *    * @return {jQuery} Activation button */

}, {   key: "getButton", value: function getButton { return $(this.link); } }]);

return Controller; }; // Initialize the singleton // eslint-disable-next-line one-var

var wwtController = new Controller; var _default = wwtController; exports["default"] = _default;

},{"./Api":2,"./App":3,"./Model":6,"jquery":undefined}],5:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

var _Tools = _interopRequireDefault(require("./Tools"));

var _Controller = _interopRequireDefault(require("./Controller"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

/** * @class * @param {Object} config Configuration options. * @constructor */ var InfoBarWidget = function InfoBarWidget { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; // Parent constructor OO.ui.ButtonWidget.parent.call(this, config); OO.ui.mixin.IconElement.call(this, config); OO.ui.mixin.LabelElement.call(this, config); OO.ui.mixin.TitledElement.call(this, config); OO.ui.mixin.FlaggedElement.call(this, config); this.closeIcon = new OO.ui.IconWidget({   icon: 'clear',    flags: ['invert'],    classes: ['wwt-infoBarWidget-close']  }); this.userInfoUsernameLabel = new OO.ui.LabelWidget; this.userInfoLabel = new OO.ui.LabelWidget({   label: mw.msg('whowrotethat-ready-general'),    classes: ['wwt-infoBarWidget-info']  }); this.$pendingAnimation = $(' ').addClass('wwt-infoBarWidget-spinner').append($(' ').addClass('wwt-infoBarWidget-spinner-bounce')); // Set properties

this.setState(_Controller["default"].getModel.getState); this.toggle(_Controller["default"].getModel.isActive); // Events

this.closeIcon.$element.on('click', _Controller["default"].dismiss.bind(_Controller["default"]));

_Controller["default"].getModel.on('state', this.setState.bind(this));

_Controller["default"].getModel.on('active', this.toggle.bind(this)); // Initialize

this.$element.addClass('wwt-infoBarWidget').append(this.$pendingAnimation, this.$icon, this.$label, this.userInfoUsernameLabel.$element, this.userInfoLabel.$element, this.closeIcon.$element); }; /* Setup */

OO.inheritClass(InfoBarWidget, OO.ui.Widget); OO.mixinClass(InfoBarWidget, OO.ui.mixin.IconElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.LabelElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.TitledElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.FlaggedElement); /** * Define legal states for the widget * * @type {Array} */

InfoBarWidget["static"].legalFlags = ['pending', 'ready', 'err']; /** * Change the state of the widget * * @param {string} state Widget state; 'pending', 'ready' or 'error' * @param {string} [errorCode] Error code, if applicable */

InfoBarWidget.prototype.setState = function (state) { var errorCode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var flags = {};

if (this.state !== state) { this.constructor["static"].legalFlags.forEach(function (flag) {     flags[flag] = flag === state;    }); flags.invert = true; this.setFlags(flags);

if (state === 'ready') { this.setIcon('userAvatar'); this.setLabel($(' ').append(mw.msg('whowrotethat-ready-title')).contents); this.userInfoLabel.setLabel($(' ').append(mw.msg('whowrotethat-ready-general')).contents); } else if (state === 'pending') { this.setIcon(''); this.setLabel($(' ').append(mw.msg('whowrotethat-state-pending')).contents); } else { this.setIcon('error'); this.setErrorMessage(errorCode); }

this.$pendingAnimation.toggle(state === 'pending'); this.userInfoLabel.toggle(state === 'ready'); this.state = state; } }; /** * Set an error with a specific label * * @param {string} errCode Error code as defined by the Api class. */

InfoBarWidget.prototype.setErrorMessage = function { var errCode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'refresh'; // Messages used here: // whowrotethat-error-refresh // whowrotethat-error-later // whowrotethat-error-contact var errorMessage = mw.msg('whowrotethat-error-' + errCode);

if (errCode === 'contact') { // The contact error message is the only with with a different signature, so we handle it. var link = document.createElement('a'); link.href = 'https://meta.wikimedia.org/wiki/Talk:Community_Tech/Who_Wrote_That_tool'; link.text = mw.msg('whowrotethat-error-contact-link'); errorMessage = mw.message('whowrotethat-error-contact', link).parse; }

this.setLabel(new OO.ui.HtmlSnippet(mw.msg('whowrotethat-state-error', errorMessage))); }; /** * Show the given username information * * @param {string} username */

InfoBarWidget.prototype.setUsernameInfo = function (username) { this.userInfoUsernameLabel.setLabel($(' ').append(_Tools["default"].bidiIsolate(username)).contents); }; // Clear the username information

InfoBarWidget.prototype.clearUsernameInfo = function { this.userInfoUsernameLabel.setLabel(''); };

var _default = InfoBarWidget; exports["default"] = _default;

},{"./Controller":4,"./Tools":8}],6:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super hasn't been called"); } return self; }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

var EventEmitter = require('events'); /** * A class managing and storing the state of the system * * @extends EventEmitter */

var Model = /*#__PURE__*/ function (_EventEmitter) { _inherits(Model, _EventEmitter);

/**  * Construct with the default values */ function Model { var _this;

_classCallCheck(this, Model);

_this = _possibleConstructorReturn(this, _getPrototypeOf(Model).call(this)); _this.initialized = false; _this.enabled = true; _this.active = false; _this.state = 'pending'; _this.$originalContent = null; _this.$contentWrapper = null; _this.lang = 'en'; _this.namespace = ''; _this.mainPage = false; return _this; } /**   * Initialize the model with the given content and configuration * This is done after the class is instantiated, when we are certain * that the DOM loaded with all gadgets ready. *  * @param {jQuery} $content jQuery object representing the content * @param {Object} config Configuration object * @param {string} [config.lang='en'] Interface language used * @param {string} [config.namespace] Namespace of the current article * @param {boolean} [config.mainPage] Whether the current page is the * wiki's main page * @fires Model#initialized */

_createClass(Model, [{   key: "initialize",    value: function initialize($content, config) {      this.$contentWrapper = $content;      this.lang = config.lang || 'en';      this.namespace = config.namespace || '';      this.mainPage = !!config.mainPage;      this.initialized = true;      /**       * Initialization event       *       * @event Model#initialized       */

this.emit('initialized'); }   /**     * Cache the original content we are about to replace *    * @param {jQuery} [$content=this.$contentWrapper] A jQuery * object to cache. If not given the $contentWrapper will * be cached. In most cases this should remain empty, * as $contentWrapper contains the original content before * it is replaced. */

}, {   key: "cacheOriginal", value: function cacheOriginal { var $content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.$contentWrapper; $content = $content || this.$contentWrapper; this.$originalContent = $content.clone(true, true); }   /**     * Get the content wrapper for the current page *    * @return {jQuery} Content wrapper */

}, {   key: "getContentWrapper", value: function getContentWrapper { return this.$contentWrapper; }   /**     * Get the original content of the article *    * @return {jQuery} Original content */

}, {   key: "getOriginalContent", value: function getOriginalContent { return this.$originalContent; }   /**     * Check if the current page is valid * for the system to display. *    * @return {boolean} Page is valid */

}, {   key: "isValidPage", value: function isValidPage { return !!( // Has the needed parser content     this.getContentWrapper.length && // Is in the main namespace      this.namespace === '' && // Is not main page      !this.mainPage); }   /**     * Check whether the model was initialized *    * @return {boolean} Model was initialized */

}, {   key: "isInitialized", value: function isInitialized { return this.initialized; }   /**     * Check whether the system is enabled on this page *    * @return {boolean} System is enabled */

}, {   key: "isEnabled", value: function isEnabled { return this.enabled; }   /**     * Check whether the system is currently active *    * @return {boolean} System is active */

}, {   key: "isActive", value: function isActive { return this.active; }   /**     * Get the current state of the system. * Possible values are 'pending', 'ready' or 'error'. *    * @return {string} State of the system */

}, {   key: "getState", value: function getState { return this.state; }   /**     * Get the interface language used *    * @return {string} Interface language */

}, {   key: "getLang", value: function getLang { return this.lang; }   /**     * Toggle the active state of the system *    * @fires Model#active * @param {boolean} [state] System is active */

}, {   key: "toggleActive", value: function toggleActive(state) { // If state isn't given, use the flipped value of the current state state = state !== undefined ? !!state : !this.active;

if (this.active !== state) { this.active = state; /**        * Activation event *        * @event Model#active * @type {boolean} * @param {boolean} isActive System is currently active */

this.emit('active', this.active); }   }    /**     * Toggle whether the system is currently enabled *    * @fires Model#enabled * @param {boolean} [state] System is enabled */

}, {   key: "toggleEnabled", value: function toggleEnabled(state) { // If state isn't given, use the flipped value of the current state state = state !== undefined ? !!state : !this.active;

if (this.enabled !== state) { this.enabled = state; /**        * Activation event *        * @event Model#enabled * @type {boolean} * @param {boolean} isEnabled System is currently active */

this.emit('enabled', this.enabled); }   }    /**     * Set the state of the system. Legal values are 'pending', 'ready', or 'err' * For 'err' state, an error code is optionally given. *    * @fires Model#state * @param {string} state System state * @param {string} [errorCode=''] Error code type, if the state is an error */

}, {   key: "setState", value: function setState(state) { var errorCode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

if (['pending', 'ready', 'err'].indexOf(state) > -1 && this.state !== state) { this.state = state; /**        * State change event *        * @event Model#state * @type {boolean} * @param {string} System changed its state * @param {string} Error code, if provided */

this.emit('state', this.state, errorCode); }   }  }]);

return Model; }(EventEmitter);

var _default = Model; exports["default"] = _default;

},{"events":1}],7:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

var _Tools = _interopRequireDefault(require("./Tools"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

/** * @class * @constructor */ var RevisionPopupWidget = function RevisionPopupWidget { this.$popupContent = $(' ').addClass('wwt-revisionPopupWidget-content'); // Parent constructor

RevisionPopupWidget.parent.call(this, {   padded: true,    autoClose: true,    position: 'above',    // FIXME: 'force-left' for RTL languages    align: 'force-right',    hideWhenOutOfView: false,    $content: this.$popupContent  }); }; /* Setup */

OO.inheritClass(RevisionPopupWidget, OO.ui.PopupWidget); /** * Get markup for the diff size. * @param {number} size * @return {string} */

function getSizeHtml(size) { var sizeClass;

if (size > 0) { sizeClass = 'mw-plusminus-pos'; } else if (size < 0) { sizeClass = 'mw-plusminus-neg'; } else { sizeClass = 'mw-plusminus-null'; }

return "\n\t\t") + "".concat(size > 0 ? '+' : '').concat(mw.language.convertNumber(size)) + ' '; } /** * Get markup for the edit summary. * @param {Object} data As returned by Api.prototype.getTokenInfo. * @return {string} */

function getCommentHtml(data) { if (data.comment === '') { // No edit summary. return ''; } else if (data.comment === undefined) { // Not yet available. return "\n\t\t\t\n\t\t\t\t \n\t\t\t\t \n\t\t\t "; }

return "\n\t\t\n\t\t\t".concat(_Tools["default"].bidiIsolate(data.comment, true), " \n\t\t\t").concat(getSizeHtml(data.size), "\n\t\t "); } /** * Get jQuery objects for the user links. * @param {Object} data As returned by Api.prototype.getTokenInfo. * @return {jQuery} */

function getUserLinksHtml(data) { var contribsUrl = mw.util.getUrl("Special:Contributions/".concat(data.username)), // We typically link to Special:Contribs for IPs. userPageUrl = data.isIP ? contribsUrl : mw.util.getUrl("User:".concat(data.username));

if (!data.username) { // Username was apparently suppressed. return $(' ').addClass('history-deleted') // Note we can't use the native MediaWiki 'rev-deleted-user' // message because it can include parser functions. .text(mw.msg('whowrotethat-revision-deleted-username')); }

return $([]).add($('').attr('href', userPageUrl).append(_Tools["default"].bidiIsolate(data.username))).add(document.createTextNode(' ' + mw.msg('parentheses-start'))).add( // Talk page $('').attr('href', mw.util.getUrl("User talk:".concat(data.username))).text(mw.msg('talkpagelinktext'))).add(document.createTextNode(' ' + mw.msg('pipe-separator') + ' ')).add($('').attr('href', contribsUrl).text(mw.msg('contribslink'))).add(document.createTextNode(mw.msg('parentheses-end'))); } /** * Show the revision popup based on the given token data, above the given element. * Note that the English namespaces will normalize to the wiki's local namespaces. * @param {Object} data As returned by Api.prototype.getTokenInfo. * @param {jQuery} $target Element the popup should be attached to. */

RevisionPopupWidget.prototype.show = function (data, $target) { var $userLinks = getUserLinksHtml(data), dateStr = moment(data.revisionTime).locale(mw.config.get('wgUserLanguage')).format('LLL'), // Use jQuery to make sure attributes are properly escaped $diffLink = $('').attr('href', mw.util.getUrl("Special:Diff/".concat(data.revisionId))).text(dateStr), addedMsg = mw.message('whowrotethat-revision-added', $userLinks, $diffLink).parse, scoreMsgKey = Number(data.score) >= 1 ? 'whowrotethat-revision-attribution' : 'whowrotethat-revision-attribution-lessthan', attributionMsg = "".concat(mw.message(scoreMsgKey, data.score).parse, " "), html = $.parseHTML("\n\t\t\t".concat(addedMsg.trim, "\n\t\t\t").concat(getCommentHtml(data), "\n\t\t\t").concat(attributionMsg, "\n\t\t")); this.$popupContent.html(html);

if ($target.find('.thumb').length) { $target = $target.find('.thumb'); } // Make sure all links in the popup (including in the edit summary) open in new tabs.

this.$popupContent.find('a').attr('target', '_blank'); this.setFloatableContainer($target); this.toggle(true); // Animate edit summary, if present.

if (data.comment) { $('.wwt-revisionPopupWidget-comment').removeClass('wwt-revisionPopupWidget-comment-transparent'); } };

var _default = RevisionPopupWidget; exports["default"] = _default;

},{"./Tools":8}],8:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var $ = window.$ || require('jquery'); /** * Class to hold some global helper tools */

var Tools = /*#__PURE__*/ function { function Tools { _classCallCheck(this, Tools); }

_createClass(Tools, null, [{   key: "bidiIsolate",

/**    * Encompass strings with BiDi isolation for RTL/LTR support. * This should be used on any string that is content language, * to make sure that if the user uses a different directionality * in the interface language, the output is shown properly * within a bidi isolation block. *    * @param {jQuery|string} $content Content to isolate * @param {boolean} [returnRawHtml] Force the return value to    *  be raw html. If set to false, will return the encompassed * jQuery object. * @return {jQuery|string} BiDi isolated jQuery object or HTML */   value: function bidiIsolate($content) { var returnRawHtml = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var $result = $(' ');

if (typeof $content === 'string') { $content = $($.parseHTML($content)); }

$result.append($content);

if (returnRawHtml) { // `html` sends the inner HTML, so we wrap the node // and send the result to include the new wrap return $(' ').append($result).html; }

return $result; } }]);

return Tools; };

var _default = Tools; exports["default"] = _default;

},{"jquery":undefined}],9:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

/** * A popup meant to display as a welcome message after the * extension is installed for the first time. * @class * @extends OO.ui.PopupWidget * * @constructor * @param {Object} [config={}] Configuration options */ var WelcomePopupWidget = function WelcomePopupWidget { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var $content = $(' ').addClass('wwt-welcomePopupWidget-content'); // Parent constructor

WelcomePopupWidget.parent.call(this, Object.assign({ padded: true, autoClose: true, position: 'after', hideWhenOutOfView: false, $content: $content }, config)); this.dismissButton = new OO.ui.ButtonWidget({   flags: ['primary', 'progressive'],    label: mw.msg('whowrotethat-tour-welcome-dismiss')  }); $content.append($(' ').addClass('wwt-welcomePopupWidget-title').append(mw.msg('whowrotethat-tour-welcome-title')), $(' ').addClass('wwt-welcomePopupWidget-description').append(mw.msg('whowrotethat-tour-welcome-description')), $(' ').addClass('wwt-welcomePopupWidget-button').append(this.dismissButton.$element)); // Events

this.dismissButton.connect(this, {   click: ['emit', 'dismiss']  }); // Initialize

this.$element.addClass('wwt-welcomePopupWidget'); }; /* Setup */

OO.inheritClass(WelcomePopupWidget, OO.ui.PopupWidget); /* Methods */

/** * @inheritdoc */

WelcomePopupWidget.prototype.toggle = function (show) { // Parent method WelcomePopupWidget.parent.prototype.toggle.call(this, show); // HACK: Prevent clipping; we don't want (or need) the // popup to clip itself when it's outside the viewport // or close to the viewport. // Unfortunately, the toggle operation calls toggleClipping // multiple times on and off, so we have to insist on it // being actually off at the end of it.

this.toggleClipping(false); };

module.exports = WelcomePopupWidget; var _default = WelcomePopupWidget; exports["default"] = _default;

},{}],10:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var config = { wikiWhoUrl: 'https://www.wikiwho.net/' }; var _default = config; exports["default"] = _default;

},{}],11:[function(require,module,exports){ "use strict";

var _config = _interopRequireDefault(require("../config"));

var _Controller = _interopRequireDefault(require("../Controller"));

var _languages = _interopRequireDefault(require("../../temp/languages"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

// This is generated during the build process (function {  var onActivateButtonClick = function onActivateButtonClick(e) {    _Controller["default"].toggle;

e.preventDefault; return false; },     loadWhoWroteThat = function loadWhoWroteThat { _Controller["default"].initialize($('.mw-parser-output'), {     lang: $('html').attr('lang'),      translations: _languages["default"],      namespace: mw.config.get('wgCanonicalNamespace'),      mainPage: mw.config.get('wgIsMainPage'),      wikiWhoUrl: _config["default"].wikiWhoUrl    });

_Controller["default"].getButton.on('click', onActivateButtonClick); };

$(document).ready(function {    var welcomeTourSeen = window.localStorage.getItem('WelcomeTourSeen'),        $overlay = $(' ').addClass('wwt-overlay');    loadWhoWroteThat;

if (welcomeTourSeen || !_Controller["default"].getModel.isValidPage) { // Do not show the tour if it was previously dismissed // Or if the page is invalid return; }

$.when($.ready, mw.loader.using(['oojs-ui', 'mediawiki.jqueryMsg'])).then(function {      var WelcomePopupWidget = require('../WelcomePopupWidget'),          welcome = new WelcomePopupWidget({ $floatableContainer: $('#t-whowrotethat'), $overlay: $overlay });

welcome.on('dismiss', function {        // This is a gadget that may also work for        // non logged in users, so we can't trust        // the mw.user.options storage.        // Store the fact that the tour was        // dismissed in localStorage        window.localStorage.setItem('WelcomeTourSeen', true); // Hide the popup

welcome.toggle(false); }); // Show the popup

$('html').addClass('wwt-popup'); $('body').append($overlay); $overlay.append(welcome.$element); welcome.toggle(true); }); }); // For debugging purposes, export methods to the window global

window.wwtDebug = { resetWelcomePopup: function resetWelcomePopup { window.localStorage.removeItem('WelcomeTourSeen'); window.console.log('WhoWroteThat Extension: Welcome tour reset. Please refresh.'); },   launch: _Controller["default"].launch.bind(_Controller["default"]), dismiss: _Controller["default"].dismiss.bind(_Controller["default"]) }; });

},{"../../temp/languages":12,"../Controller":4,"../WelcomePopupWidget":9,"../config":10}],12:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var languageBlob = { "en": { "whowrotethat-activation-link": "Who Wrote That?", "whowrotethat-activation-link-tooltip": "Activate Who Wrote That?", "whowrotethat-deactivation-link": "Close Who Wrote That?", "whowrotethat-state-pending": " Who Wrote That? is loading. This might take a while.", "whowrotethat-state-error": "API Error: $1", "whowrotethat-error-refresh": "Please refresh the page or try again later.", "whowrotethat-error-later": "Please try again later.", "whowrotethat-error-contact": "Please $1 about this issue.", "whowrotethat-error-contact-link": "contact us", "whowrotethat-ready-title": "Who Wrote That?", "whowrotethat-ready-general": "Hover to see contributions by the same author. Click for more details.", "whowrotethat-tour-welcome-title": " Who Wrote That? is enabled!", "whowrotethat-tour-welcome-description": "Activate the the tool from the sidebar. Hover over the text to see the author and their other contributions to this page. Click to get more details.", "whowrotethat-tour-welcome-dismiss": "Got it!", "whowrotethat-revision-added": "$1 added this on $2.", "whowrotethat-revision-attribution": "They have written $1% of the page.", "whowrotethat-revision-attribution-lessthan": "They have written less than 1% of the page.", "whowrotethat-revision-deleted-username": "(username or IP removed)" } }; var _default = languageBlob; exports["default"] = _default;

},{}]},{},[11]);