// Version : 2.6.7// Last-modified : August 07, 2023// Author : Alexander Davronov// Description : Toolbar for copying diff entries from revision/contributions// pages history on Wikipedia/*********************************************************************************** *********************************************************************************** ** HistoryHelper (Wikipedia script) ** ** Copyright (C) 2021- Alex A. Davronov ** ** ** ** 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. ** *********************************************************************************** ***********************************************************************************/$(function() {"use strict";// -----------------------------------------------------------------------------// #BROWSER POLYFILLS// -----------------------------------------------------------------------------sif (!Object.assign) { Object.assign = jQuery.extend; }/** * @param {string} message * @param {string} indent */var InvalidArgumentTypeError = class extends TypeError {constructor(message, indent) {indent = indent instanceof String ? indent : "";message = indent + "Invalid Argument: " + message;super(message);}};// -----------------------------------------------------------------------------// #UTILS// -----------------------------------------------------------------------------/* * Makes clipboard (temporary buffer) managment easier * @example: new ClipboardBuffer().copy('foo') // copies 'foo' string to the clipboard * Borrowed from Collect tracks v.2.js **/let ClipboardBuffer = class {static version = "1.0.0";constructor(container) {this.container = container || document.body;this.id = "clipboard-area";this.el = this.container.querySelector("#" + this.id);if (!this.el) {this.el = document.createElement("textarea");this.container.appendChild(this.el);}this.el.style.position = "absolute";this.el.style.top = "-9999px";this.el.contentEditable = true;this.el.id = this.id;}copy(text) {this.el.value = text;this.el.select();var result = document.execCommand("copy");this.el.blur();return result;}};/** * Toolbar for buttons. * This class is tasked with book keeping of buttons. * It can retrieve buttons to assing listeners for both pointer and keyboard. * element which you can style. * @since 2.6.0 * @example * let toolbar = new Wiki.Toolbar(document.getElementById(`some-panel`)) * toolbar.addMany([ ...htmlElements or oo.UI.ButtonWidgets ]) */// -----------------------------------------------------------------------------// #WIKI TEXT SYNTAX// -----------------------------------------------------------------------------// Wikipedia Classes NameSpacevar Wiki = {};/** * @since 2.6.0 */Wiki.Text = class extends String {static options = {}constructor(rawWikitext, options, C) {super(rawWikitext)this.C = Object.assign({}, C || {});this.options = Object.assign({}, this.constructor.options, options || {});}/** * https://www.mediawiki.org/wiki/ResourceLoader/Core_modules#mediawiki.api * https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.Api * https://www.mediawiki.org/wiki/Special:ApiSandbox#action=parse&text=%7B%7BProject:Sandbox%7D%7D&contentmodel=wikitext * @example render().done((data) => …) * @returns {mw.API} */render() {// Get rendered wikitext with no miscelanious thingsvar api = new mw.Api();return api.post({action: `parse`,format: `json`,text: this,contentmodel: `wikitext`,prop: {langlinks: false,categories: false,categorieshtml: false,links: false,parsetree: false,properties: false},preview: true})}};/** Wikipedia's Template markup as string in the form of {{}} * https://www.search.com.vn/wiki/en/Wikipedia:Anatomy_of_a_template * @return {TemplateTag} */Wiki.Text.Tag = class {static IATE = InvalidArgumentTypeError;/** Basic Tokens */static B = "{{";static D = "|";static E = "}}";/** * @param {String} name - Tag name e.g. diff, oldid2 * @param {params} params - Params of the template: {diff|param1|paramX|….} */constructor(name, params) {if (new Object(name).constructor !== String) {throw new this.constructor.IATE(`Invalid arg: string expected`);}if (!(params instanceof Array)) {throw new this.constructor.IATE("params have to be an array");}let isParamString;// Replace non-string by "" (empty) stringparams = params.map((param) => {isParamString = new Object(param) instanceof String;return isParamString ? param.toString() : "";});this.name = name;this.params = params;}valueOf() {return this.toString();}toString() {// Create `{{name|param0|param1|paramN}}`let B = this.constructor.B; // Tag tokenlet D = this.constructor.D; // Tag tokenlet E = this.constructor.E; // Tag tokenlet val = "";val += B;val += this.name;for (var param of this.params) {if (param) val += D + param;}val += E;return val;}};/** * A container for Rows. Renders them into a string via toStirng() * @summary Wikipedia table wikitext wrapper */Wiki.Text.Table = class extends String {static IATE = InvalidArgumentTypeError;constructor({ cssClasses, rows }, options) {super();this.options = Object.assign({caption: `Diffs`,},options || {});if (!(rows instanceof Array)) {throw new this.constructor.IATE("rows have to be an array");}this.cssClasses = cssClasses || ``;this.rows = rows;}valueOf() {return this.toString();}toString() {let rowsStr = this.rows.join("\r\n");let classAttr = this.cssClasses ? `class="${this.cssClasses}"` : ``;return `{|${classAttr}\n|+${this.options.caption}\n${rowsStr}\n|}`;}};Wiki.Text.Table.Row = class extends String {constructor({ arr, value }, options, C) {if (value) {throw new Error(`Provide array instead`);}let rows = arr.join(`||`);super(`|-\n|${rows}`);this.C = C || {};}};Wiki.Text.Table.Header = class extends String {constructor({ arr, value }, options, C) {if (value) {throw new Error(`Provide array instead`);}let rows = arr.join(`!!`);super(`!${rows}`);this.C = C || {};}};Wiki.Text.Table.Def = class extends String {constructor(value) {if (new Object(value).value != null) {value = obj.value}super(`${value}`);}};// -----------------------------------------------------------------------------// #Wikidate// -----------------------------------------------------------------------------// @summary I convert Wikidate into Date and help to format itWiki.Date = class extends Date {constructor(dateStr) {let wdate = dateStr.split(`, `);super(wdate.slice(1).concat(wdate[0]).join(`,`));wdate = null;}// Defaultstatic dateFormat = {dateStyle: `medium`, timeStyle: "short", hour12: false};// @para {object} dateFormat - Format object, see MDN: Intl/DateTimeFormatformat(dateFormat) {return Intl.DateTimeFormat(undefined, dateFormat || this.constructor.dateFormat).format(this);}}// -----------------------------------------------------------------------------// #REVISIONS ENTRIES WRAPPER// -----------------------------------------------------------------------------/** * @summary Container for elements of Entry class * @class */Wiki.Revisions = class extends Array {static IATE = InvalidArgumentTypeError;/** * @param {Array<Wiki.Entry>} entries * @param {HTMLElement} parentEl * @param {Object} options * @param {Object} C */constructor(entries, parentEl, options, C) {super();// Contextthis.C = Object.assign({}, C || {});this.options = Object.assign({}, options || {});this.parentEl = parentEl;this.el = parentEl;if (entries instanceof Array) {// throw new this.constructor.IATE(`Array is expected`);// Sieve only Entry-based instancesthis.init(entries);}}/** * @summary Clean up checkboxes left by previous script run * @description Use after revisions.fromEl() call * @param {HTMLElement} rootElement * @returns {Revisions} */static checkboxesCleanUp(rootElement) {// Clean up previously created checkboxesif (rootElement.querySelector(`input[name="select-diff"]`)) {$(rootElement).find(`input[name="select-diff"]`).parent().remove();}return this} // checkboxesCleanUp end/** * Helper to map HTMLElement children of revisions into Entries * @param {HTMLElement} rawRevisions - An element whose children are going to be wrapped by Entry * @param {Object} opt - Options for Revisions * @param {Object} C - Context for Revisions * @param {Wiki.Revisions.Entry} Entry - Entry constructor * @param {Object} Eopt - For Entry - Options for Entry * @param {Object} EC - Wiki UI native checkbox widget * @returns Revisions */static fromEl(rawRevisions, opt, C, Entry, Eopt, EC) {if (!(rawRevisions&& rawRevisions.constructor == Array&& rawRevisions.length > 0)) {throw this.IATE(`${Wiki.HH.NAME}: fromEl() expects an array with elements`);}if (rawRevisions[0].constructor != HTMLLIElement) {throw this.IATE(`${Wiki.HH.NAME}: fromEl() expects an array of li elements`);}EC = Object.assign({ CheckboxInputWidget: OO.ui.CheckboxInputWidget }, EC || {});let entries = rawRevisions.map((el) => new Entry(el, Eopt, EC));// Invoking this(…) make this portablereturn new this(entries, rawRevisions, opt, C);}init(entries) {for (let i = 0; i < entries.length; i++) {const entry = entries[i];if (entry instanceof this.constructor.Entry) {this[i] = entry;entry.parent = this;}}}// Return array of checked entrieschecked() {let checked = [];for (let i = 0; i < this.length; i++) {if (this[i].isChecked()) {checked.push(this[i]);}}// BUG: This uncessarily registered new controls listeners if called via// built-in Array methodsreturn new this.constructor(checked, this.parentEl, this.options, this.C);}};// A single revision entry line containerWiki.Revisions.Entry = class extends Object {static IATE = InvalidArgumentTypeError;constructor(el, options, C) {super();this.C = Object.assign({}, C || {});if (!(el instanceof HTMLLIElement)) {throw new this.constructor.IATE(`<li> element expected`);}this.el = el;this.init(el);}init(el) {// Revision linklet href = el.querySelector(`.mw-changeslist-links > span:nth-child(2) > a`);if (href == null) {console.warn(`${Wiki.HH.NAME}: Entry()..init(): history 'prev' link isn't found, falling back to default values`);this.title = "";this.diff = "";this.oldid = "";} else {// TODO: BUG ON MAIN PAGElet urlParams = new URL(href).searchParams;this.title = urlParams.get(`title`);this.diff = urlParams.get(`amp;diff`) || urlParams.get(`diff`);this.oldid = urlParams.get(`amp;oldid`) || urlParams.get(`oldid`);}// Datelet date = el.querySelector(`li > a`);if (date && date.textContent) {this.date = new Wiki.Date(date.textContent);}this.user = el.querySelector("bdi") && (el.querySelector("bdi").textContent ?? "");let comment = el.querySelector(".comment") ?? "";// Strip comments from backslashif (comment && comment.textContent) {this.comment = el.querySelector(".comment").textContent.replace(/[\(\→]/g, "");} else {this.comment = ``;}}/** * Insert a given el element before entry's first element * @param {HTMLElement} el - element to be inserted before the first child */insertBefore(el) {this.el.insertBefore(el, this.el.firstChild);}};/** * @summary Container for elements of EntryCB class * @class * @since 2.6.0 */Wiki.Revisions2 = class extends Wiki.Revisions {/** * @param {Array<Wiki.Revisions2.EntryCB>} entries * @param {HTMLElement} parentEl * @param {Object} options * @param {Object} C */constructor(entries, parentEl, options, C) {super(entries, parentEl, options, C);this.i = 0;}init(entries) {/** @property {Boolean} - Whether any checkbox is checked*/this.checkboxes = [];this.checkboxes.parent = this;this.checkboxes.lastClicked = [];for (let i = 0; i < entries.length; i++) {const entry = entries[i];if (entry instanceof this.constructor.Entry) {this[i] = entry;// entry.parent = this;entry.parent = this;entry.checkbox.parent = this.checkboxes;this.checkboxes.push(entry.checkbox);}}}isAnyChecked() {return this.some(entry => entry.checkbox.isSelected())}checked() {return this.filter(entry => entry.isChecked());}};/** * The Entry extended with a checkboxk * @class * @since 2.6.0 */Wiki.Revisions2.EntryCB = class extends Wiki.Revisions.Entry {constructor(el, options, C) {super(el, options, C);if (this.C.CheckboxInputWidget == null) {throw new this.constructor.IATE(`CheckboxInputWidget is missing`);}// The value is expected to be assigned by external entitythis.parentthis.init(el);this.initCheckBox();}initCheckBox() {this.checkbox = new this.C.CheckboxInputWidget({name: `select-diff`,value: this.el.getAttribute(`data-mw-revid`),selected: false,});this.checkbox.$element[0].style.width = `15px`;this.checkbox.$element[0].style.height = `15px`;this.checkbox.$element.mouseleave(function(e) {if (e.buttons === 1) {this.setSelected(!this.isSelected());}}.bind(this.checkbox));this.insertBefore(this.checkbox.$element[0]);}/** * @returns {Boolean} - True if checked */isChecked() {return this.checkbox.isSelected();}};Wiki.Contributions = class extends Wiki.Revisions2 { };Wiki.Contributions.EntryCB = class extends Wiki.Revisions2.EntryCB {static IATE = InvalidArgumentTypeError;static UserName = mw.config.get(`wgRelevantUserName`);constructor(el, options, C) {super(el, options, C);if (!(el instanceof HTMLLIElement)) {throw new this.constructor.IATE(`<li> element expected`);}let context = {}; // The context here stands for imported objectthis.C = Object.assign(context, C || {});this.options = Object.assign({}, options || {});this.el = el;this.init(el);}init(el) {// Revision linkslet diffEl = el.querySelector(`a.mw-changeslist-diff`) || el.querySelector(`a.mw-changeslist-history`);if (diffEl == null) {throw new Error(`${Wiki.HH.NAME}: can't find diff element on collaboration page.`);}let href = diffEl.href;if (href == null) {throw new Error(`${Wiki.HH.NAME}: Entry()..init(): history 'prev' link isn't found, falling back to default values`)this.title = "";this.diff = "";} else {let urlParams = new URL(href).searchParams;this.title = urlParams.get(`title`);this.diff = this.el.dataset["mwRevid"];}this.oldid = `prev`;this.user = this.constructor.UserName;// this.user = mw.config.get(`wgRelevantUserName`);// Datelet date = el.querySelector(`li > a`);if (date && date.textContent) {el.querySelector(`li > a`).textContent;this.date = new Wiki.Date(date.textContent);} else {this.date = new Wiki.Date(date.textContent);}this.comment = ``;let commentEl = this.el.querySelector(`.comment`);if (commentEl) {this.comment = commentEl.textContent.replace(/[\(\→]/g, "")}}};Wiki.Toolbar = class extends Map {static IATE = InvalidArgumentTypeError;static config = {id: `toolbar-default`}static buttons = {[`info`]: {type: `Popup`,disabled: true,title: `Click buttons on the right`,label: `COPY AS`,icon: `doubleChevronEnd`,},[`as.diffs`]: {title: `Copy selected as {{diff|…}} wikitext`,id: `as.diffs`,label: `{{diff}}`,icon: `code`,template: `{{tqb|\n%\n}}`},[`as.table`]: {title: `Copy selected as table wikitext`,id: `as.table`,label: `<Table/>`,icon: `table`,template: ``},[`as.links`]: {title: `Copy selected as raw [1]..[n] links (can be pasted into summary)`,id: `as.links`,label: `Links`,icon: `wikiText`,template: ``},};static notice = {type: 'info',label: 'Nothing to preview. Select checkboxes!',title: 'Info',inline: true}/** * * @param {HTMLElement} toolbarEl - Container * @param {Array<Object>} buttons - Arrays of buttons widgets. See add() for supported ones * @param {Object} options - * @param {Object} C - Context */constructor(buttons, options, C) {super();// Options.this.arguments = arguments;this.arguments[1] = Object.assign({}, options || this.constructor.config);this.arguments[2] = Object.assign({}, OO.ui, C || {});// Toolbar widgetthis.buttonsGroup = new this.arguments[2].ButtonGroupWidget({ id: this.arguments[1].id });this.$element = this.buttonsGroup.$element;this.$element.css(`z-index`, 2);if (buttons) {this.addMany(buttons);}}/** * @typedef {Object} OO.ui.ButtonWidget - * @property {string} id - * @method addItems *//** * Add every button to the group, associate buttons with IDs * @example new Toolbar(); * @param {HTMLElement | OO.ui.ButtonGroupWidget | OO.ui.PopupButtonWidget} el * @returns {Wiki.Toolbar} */add(el) {if (el == null) {throw new this.constructor.IATE(`first argument is expected`);}switch (el.constructor) {case HTMLElement:this.buttonsGroup.$element[0].appendChild(el);el.id && this.set(el.id, el);break;case this.arguments[2].ButtonWidget:case this.arguments[2].PopupButtonWidget:el.$element[0].id && this.set(el.$element[0].id, el);this.buttonsGroup.addItems([el]);break;break;default:console.warn(`toolbar.add(e): unknown e.constructor.`)}return this;}/** * * @param {Array<HTMLElement | OO.ui.ButtonGroupWidget>} elements * @returns */addMany(elements) {for (let i = 0; i < elements.length; i++) {this.add(elements[i]);}return this;}toArray() {return Array.from(this.values())}};/** * The HistoryHelper main class used as nameSpace. * It binds provided UI elements (toolbar/revisions) and binds * Pointer (mouse) and Keyboard strokes to actionsM * (e.g. copy revisions to clipboard) */Wiki.HH = class extends Object {static NAME = `HistoryHelper`;static IATE = InvalidArgumentTypeError;// TODO: Deprecate in favor of preview copy text fieldstatic shortcuts = {[`ctrl+alt+d`]: `revisions.as.diffs.to.clipboard`,[`ctrl+alt+c`]: `revisions.as.links.to.clipboard`}static options = {fetchLimit: 64}/** * Overview of basic HistoryHelper workflows * ##Clipboard workflow * revisions.keyboard -> revisionsTo…(revisions) -> clipboard.copy() * buttons.pointer.click -> entries.to.markup -> clipboard.copy() * buttons.pointer.hover -> buttons.popup.showPreview(revisions.as.XYZ) * ##UX workflow * buttons.popup.pointer -> preview.modify() * revisions.pointer -> entries.select * revisions.checkboxes.pointer + keyboard.shift -> entries.select * @param {Wiki.Toolbar} revisions - Data (revisions container) * @param {Wiki.Revisions} toolbar - Input (butttons panel) * @param {ClipboardBuffer} clipboard - Output (clipboard buffer) * @param {Object} options - Configuration object * @param {Object} options.shortcuts - Shortcuts to Action map * @param {Object} C - Namespace for default class constructors * @param {Object} C.Revisions - Revisions entries container constructor * @param {Object} C.Toolbar - * @param {Object} C.Clipboard - * @param {Object} C.Text - WikiText renderer * used to build output strings */constructor(toolbar, revisions, clipboard, options, C) {super();this.C = {};this.C.Revisions = Wiki.Revisions;this.C.Toolbar = Wiki.Toolbar; // Containersthis.C.Clipboard = ClipboardBuffer;this.C.Text = Wiki.Text;this.C = Object.assign(this.C, (C || {}));this.options = Object.assign({}, this.constructor.options, options || {});if (!(toolbar instanceof this.C.Toolbar)) throw new this.constructor.IATE(`toolbar instance of Toolbar is expected`);if (!(revisions instanceof this.C.Revisions)) throw new this.constructor.IATE(`revisions instance of Revisions is expected`);if (!(clipboard instanceof this.C.Clipboard)) throw new this.constructor.IATE(`clipboard instance of Clipboard is expected`);this.toolbar = toolbar;this.revisions = revisions;this.clipboard = clipboard;//#ACTIONS MAP//------------------------------------------// These are intended to be invoked on some user// actions such as click or keypress// These callbacks are called from multiple places// DPRCT: [August 07, 2023] Remove clipboard functionalitythis[`revisions.as.diffs.to.clipboard`] = function() {this.clipboard.copy(this.constructor.revisionsToDIFFS(this.revisions.checked(), undefined, options))}.bind(this);this[`revisions.as.table.to.clipboard`] = function() {this.clipboard.copy(this.constructor.revisionsToTABLE(this.revisions.checked()))}.bind(this);this[`revisions.as.links.to.clipboard`] = function() {this.clipboard.copy(this.constructor.revisionsToLINKS(this.revisions.checked()))}.bind(this);this[`revisions.as.diffs.rendered`] = function(cb) {let selected = this.revisions.checked().slice(0, this.options.fetchLimit);let wikitext = this.constructor.revisionsToDIFFS(selected, undefined, options);wikitext? new this.C.Text(wikitext).render().done(cb): cb({});}.bind(this);this[`revisions.as.table.rendered`] = function(cb) {let selected = this.revisions.checked().slice(0, this.options.fetchLimit);let wikitext = this.constructor.revisionsToTABLE(selected);wikitext? new this.C.Text(wikitext).render().done(cb): cb({});}.bind(this);this[`revisions.as.links.rendered`] = function(cb) {let selected = this.revisions.checked().slice(0, this.options.fetchLimit);let wikitext = this.constructor.revisionsToLINKS(selected);wikitext? new this.C.Text(wikitext).render().done(cb): cb({});}.bind(this);this.buttons = this.toolbar.toArray();this.initButtons();this.initRevisionsListeners();this.initRevisionsSpecialListneners();} // CONSTRUCTOR END// Associate button clicks with actionsinitButtons() {//#POINTER CONTROL - BUTTONS//------------------------------------------for (let button of this.buttons) {button.$element.click(this[`revisions.${button.elementId}.to.clipboard`]);// Show preview of the selected entriesbutton.$element.mouseenter(function(button, e) {// Hide all popupsfor (let nextButton of this.buttons) {nextButton.popup.toggle(false);}button.popup.toggle(true);let d0 = button.popup.$lable.isVisible();if (this.revisions.isAnyChecked()) {button.popup.$lable.toggle(false);setTimeout(() => {this[`revisions.${button.elementId}.rendered`]((response) => {if (response.parse) {button.popup.html(`${response.parse.text[`*`]}`)} else {button.popup.html(``);}});}, 300);} else {button.popup.$lable.toggle(true);}}.bind(this, button)); // bindEventEnd}}// Associate keyboard hotkeys with actions// Only works when pointer is in area of a revisions list elementinitRevisionsListeners() {//#KEYBOARD CONTROL//------------------------------------------if (this.options.shortcuts) {const ctrlKey = `ctrl`;const shiftKey = `shift`;const altKey = `alt`;this.revisions.parentEl.tabIndex = 1;$(this.revisions.parentEl).bind(`keyup`, (e) => {let pressedKeys = ``;pressedKeys += e.ctrlKey ? ctrlKey + `+` : ``;pressedKeys += e.shiftKey ? shiftKey + `+` : ``;pressedKeys += e.altKey ? altKey + `+` : ``;pressedKeys += e.key;// Match the keystroke into a an action declared abovelet action = this[this.options.shortcuts[pressedKeys]];if (action) action();});}}// Associate keyboard + pointer hotkeys behavior// Allows selecting checkboxes range by using shift + checkbox clickinitRevisionsSpecialListneners() {//#CHECKBOXES CONTROL//------------------------------------------this.revisions.checkboxes.lastClicked[1] = this.revisions.checkboxes[0];$(this.revisions.el).click((e) => {// Clear up preview datafor (let button of this.buttons) {button.popup.html(``);}// We need to focuse only on widget's span elementlet focusedCheckbox;if (e.target instanceof HTMLInputElement) {focusedCheckbox = e.target.parentElement;}if (e.target instanceof HTMLSpanElement&& /oo-ui-checkboxInputWidget/.test(e.target.className)) {focusedCheckbox = e.target;}/**@type Array<CheckboxInputWidgets> */let checkboxes = this.revisions.checkboxes;if (checkboxes.lastClicked[1] !== focusedCheckbox) {checkboxes.lastClicked[0] = checkboxes.lastClicked[1];checkboxes.lastClicked[1] = focusedCheckbox;}if (e.shiftKey &&checkboxes.lastClicked[0] &&checkboxes.lastClicked[1]) {let from = checkboxes.findIndex((widget) => {return checkboxes.lastClicked[0] === widget.$element[0]});let to = checkboxes.findIndex((widget) => {return checkboxes.lastClicked[1] === widget.$element[0]});if (from > to) {let mid = to;to = from;from = mid;}from++;for (; from < to; from++) {checkboxes[from].setSelected(!checkboxes[from].isSelected())}}});}// Words to higlightstatic highlights = /competen(t|cy)|IR|bitch|illiterate|fuck(er)?|asshole(ery)?|troll|idiot|dumbass|stupid|blank|subhuman|autis[tm]|(edit)? warring|inept/g;/** Convert revisions entries into a Wikitext (diffs) * @since 2.6.0 * @param {Wiki.Revisions} revisions - Array that contains Entry instances * @param {Wiki.Text.Tag} Tag * @returns {String} */static revisionsToDIFFS(revisions, Tag, config) {Tag = Tag || Wiki.Text.Tag;if (!(revisions)) { throw new this.IATE(`Revisions are missing`) }if (!revisions.length) { return `` }let entry, tag, wikitext = ``;let comment;let users = new Set();// Walk over every entryfor (let i = 0; i < revisions.length; i++) {entry = revisions[i];if (entry && new Object(entry.user).constructor == String) {if (entry.user !== mw.config.get(`wgUserName`)) {users.add(entry.user);}}if (entry && new Object(entry.date).constructor == Wiki.Date) {entry.date = entry.date.format();}tag = new Tag(`diff`, [entry.diff,entry.oldid,entry.date,]);// Highlight specified by config words and phrases// Highlight incivilitycomment = entry.comment.replace(this.highlights, `{{highlight|$&}}`);let highlights = config && new Object(config.highlights);if (highlights&& highlights.constructor === Array&& highlights.length) {for (let i = 0, reg; i < highlights.length; i++) {reg = highlights[i];comment = comment.replace(reg, `{{highlight|$&}}`);}}comment = comment ? `- ''«${comment}»''` : ``;wikitext += `${tag.toString()} ${comment}<br/>\n`;}if (users.size) {wikitext += ':';wikitext += new Tag(`re`, Array.from(users));}return wikitext}/** Convert revisions entries into a Wikitext (Special:Diff/… links) * @since 2.6.0 * @param {Wiki.Revisions} revisions - Array that contains Entry instances * @returns {String} */static revisionsToLINKS(revisions) {if (!(revisions)) { throw new this.IATE(`Revisions are missing`) }if (!revisions.length) { return `No revisions selected` }let entry, wikitext = ``;for (let i = 0; i < revisions.length; i++) {entry = revisions[i];if (entry && new Object(entry.date).constructor == Wiki.Date) {entry.date = entry.date.format();}// Omit prevlet diff = entry.oldid;if(diff == "prev") {diff = entry.diff}wikitext += `[[Special:Diff/${diff}|[${entry.date}]]]`}return wikitext}/** Convert revisions entries into a Wikitext (tables ) * @since 2.6.0 * @param {Wiki.Revisions} revisions - Array that contains Entry instances * @param {Wiki.Text.Tag} Tag * @param {Wiki.Text.Table} Table * @returns {String} */static revisionsToTABLE(revisions, Tag, Table) {if (!(revisions)) { throw new this.IATE(`Revisions are missing`) }if (!revisions.length) { return `` }Table = Table || Wiki.Text.Table;Tag = Tag || Wiki.Text.Tag;// Every entry wrapped into a wiki tag// Group of tags into table definitions (colums)let entry;let anchor, anchLink, diff, oldid, user, tags, entries;let defintions = [];for (let i = 0; i < revisions.length; i++) {entry = revisions[i];anchLink = `hist-${i}-${entry.diff}`;anchor = new Tag(`anchor`, [anchLink]);diff = new Tag(`diff`, [entry.oldid, entry.date]);oldid = new Tag(`oldid2`, [1, entry.oldid, entry.date]);user = new Tag(`u`, [entry.user]);tags = [anchor + `[[#${anchLink}|${i}]]`,diff,oldid,user,entry.comment ? `''${entry.comment}''` : ``]defintions.push(tags.map(tag => new Table.Def(tag)));}// Wrap ever column into a row// First row is the headlet columns;let rows = [new Table.Header({arr: [`#`, `DIFF`, `CURRENT`, `USER`, `SUMMARY`],})];for (let i = 0; i < defintions.length; i++) {columns = defintions[i];rows.push(new Table.Row({ arr: columns }))}let wikitext = new Table({cssClasses: "wikitable sortable",rows: rows,}).toString();return wikitext;}};//#USER CONFIG//------------------------------------------// Convert legacy (prior 2.6.0) config version into a 2.6.0 if (window.HistoryHelper && window.HistoryHelper.shortcuts) {let shortcuts = window.HistoryHelper.shortcuts;// 1/2 For every shortcutfor (const key in shortcuts) {if (Object.hasOwnProperty.call(shortcuts, key)) {const actionName = shortcuts[key];// 2/2 if an old action match, replace by a new oneif (actionName === `copyAsdiffs`) {shortcuts[key] = `revisions.as.links.to.clipboard`;console.warn(`${Wiki.HH.NAME}: copyAsdiffs action is deprecated after v2.6.0, update your config`)}}}}let config = Object.assign({}// Turn off default shortcuts// ,{ shortcuts: Wiki.HH.shortcuts},, window.HistoryHelper || {});// ---------------------------------------------------------------------------// #MAIN// ---------------------------------------------------------------------------let main = function main() {let contribPageRe = /Special:Contributions/let isContributionsPage = contribPageRe.test(window.location.href);let isHistoryPage = new URL(window.location).searchParams.get("action") == "history";if (!(isContributionsPage || isHistoryPage)) {return}// Initialize toolbar & buttonslet buttons = Object.values(Wiki.Toolbar.buttons).map((data) => {let $lable = new OO.ui.MessageWidget(Wiki.Toolbar.notice);$lable.$element.css(`min-width`, `478px`)let $content = $(`<div></div>`)// .append($notice.$element);let popup = new OO.ui.PopupWidget({width: null,head: true,label: $lable.$element,$content: $content,padded: true,autoClose: true,autoFlip: false});popup.$element.css(`z-index`, 32);popup.$element.css(`min-width`, `330px`);popup.$element.css(`min-height`, `127px`);popup.$content = $content;popup.$lable = $lable;popup.html = function(str) {return this.$content.html(str)}let button = new OO.ui.ButtonWidget({ ...data, content: [popup] });button.popup = popup;return button})// New toolbarwindow[Wiki.Toolbar.config.id] && window[Wiki.Toolbar.config.id].remove();let toolbar = new Wiki.Toolbar(buttons);// Initialize revisions containerlet pagehistory = document.getElementById(`pagehistory`)|| document.querySelector(`#mw-content-text section.mw-pager-body`);if (!(pagehistory)) {throw new Error(`${Wiki.HH.NAME}: can't find revisions html element. \n\tThis is probably due to Wikipedia changing its HTML ids. \n\tContact the script author for help: \n\thttps:https://www.search.com.vn/wiki/index.php?lang=en&q=User_talk:Alexander_Davronov&action=edit§ion=new`);return}// Remove old checkboxesWiki.Revisions2.checkboxesCleanUp(pagehistory);let clipboard = new ClipboardBuffer();// Article or User history page// https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.configif (isHistoryPage) {let rawRevisions = Array.from(pagehistory.querySelectorAll(`ul > li`));let revisions = Wiki.Revisions2.fromEl(rawRevisions, {}, {}, Wiki.Revisions2.EntryCB);// Adding toolslet revCompareForm = document.getElementById(`mw-history-compare`);let toolbarContainerTarget =revCompareForm&& revCompareForm.querySelector(`.mw-history-compareselectedversions`);$(toolbarContainerTarget).append(toolbar.$element);if (toolbar.$element[0] && !toolbar.$element[0].children.length) {throw new Error(`${Wiki.HH.NAME}: Toolbar has no buttons, please fill a bug report!`);}// Init HistoryHelper controls (button press handlers)// over toolbar and revisionsnew Wiki.HH(toolbar, revisions, clipboard, config);return}// User contributions pagelet isViewing = mw.config.get(`wgAction`) === `view`;if (isViewing) {let rawRevisions = Array.from(pagehistory.querySelectorAll(`ul > li`));let revisions = Wiki.Contributions.fromEl(rawRevisions, {}, {}, Wiki.Contributions.EntryCB, { user: mw.config.get(`wgRelevantUserName`) });let toolbarContainerTarget = document.getElementById(`mw-content-text`).firstChild;toolbar.$element.insertAfter(toolbarContainerTarget);new Wiki.HH(toolbar, revisions, clipboard, config);return}}mw.loader.using([`oojs-ui.styles.icons-editing-advanced`, `oojs-ui.styles.icons-alerts`], main);// From the End comes The Beginning!// Something ends, something begins!});
🔥 Top keywords: Main PageSpecial:SearchPage 3Wikipedia:Featured picturesHouse of the DragonUEFA Euro 2024Bryson DeChambeauJuneteenthInside Out 2Eid al-AdhaCleopatraDeaths in 2024Merrily We Roll Along (musical)Jonathan GroffJude Bellingham.xxx77th Tony AwardsBridgertonGary PlauchéKylian MbappéDaniel RadcliffeUEFA European Championship2024 ICC Men's T20 World CupUnit 731The Boys (TV series)Rory McIlroyN'Golo KantéUEFA Euro 2020YouTubeRomelu LukakuOpinion polling for the 2024 United Kingdom general electionThe Boys season 4Romania national football teamNicola CoughlanStereophonic (play)Gene WilderErin DarkeAntoine GriezmannProject 2025