User:DannyS712 test/Global watchlist.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:DannyS712 test/Global watchlist. This user script seems to have an accompanying .css page at User:DannyS712 test/Global watchlist.css. |
// <nowiki>
/**
* Global Watchlist script
*
* Client-side user script to create a functional "global" watchlist
*
* @author DannyS712
* @version 9.1.0
*/
/* jshint maxerr: 999, esversion: 6 */
/* eslint array-bracket-spacing: ["error", "always"]
array-element-newline: ["error", "consistent"]
comma-dangle: ["error", {"arrays": "always-multiline", "objects": "always-multiline"}]
dot-location: ["error", "property"]
function-call-argument-newline: ["error", "consistent"]
max-lines-per-function: ["error", 65],
multiline-ternary: ["error", "always-multiline"]
object-property-newline: ["error", { "allowAllPropertiesOnSameLine": true }]
quotes: ["error", "single"] */
/* globals GlobalWatchlist, $, mw, OO, Set, Promise */
$(() => { // eslint-disable-line max-lines-per-function
const GlobalWatchlist = {};
window.GlobalWatchlist = GlobalWatchlist;
GlobalWatchlist.cfg = {
debugMode: 100,
name: '[[:m:User:DannyS712/Global watchlist|Global Watchlist]]',
version: '9.1.0',
versionCSS: '6.3',
versionDoc: '1.1',
versionI18n: '1.1',
versionTests: '2.3.1',
};
GlobalWatchlist.init = function (mode) {
GlobalWatchlist.debug('Starting...', mode);
mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist.css&action=raw&ctype=text/css', 'text/css');
const defaultCfg = {
configMode: 0,
confirmAllSites: true,
fastMode: false,
flags: {anon: 0, bot: 0, minor: 0, totalStr: 'unread'},
groupPage: true,
siteList: [ 'en.wikipedia', 'meta.wikimedia', 'commons.wikimedia', 'www.wikidata' ],
testNoActions: false,
types: {edits: true, logEntries: true, newPages: true, totalStr: 'edit|log|new'},
wlStr: 'ids|title|flags|loginfo|parsedcomment|user|tags',
},
metaCfg = {
liveCounter: 0,
mode: -1,
scriptImports: {
/* eslint-disable quotes */
prod: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist.js&action=raw&ctype=text/javascript');",
prod2: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global_watchlist.js&action=raw&ctype=text/javascript');",
prodStable: "mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:DannyS712/Global watchlist/stable.js&action=raw&ctype=text/javascript');",
test: "mw.loader.load('//en.wikipedia.org/w/index.php?title=User:DannyS712 test/Global watchlist.js&action=raw&ctype=text/javascript');",
testStable: "mw.loader.load('//en.wikipedia.org/w/index.php?title=User:DannyS712 test/Global watchlist/stable.js&action=raw&ctype=text/javascript');",
/* eslint-enable quotes */
usingDev: true,
},
},
mwCfg = mw.config.get([ 'skin', 'wgUserName', 'wgUserLanguage' ]),
userSettings = GlobalWatchlist.help.getUserSettings(mwCfg);
GlobalWatchlist.cfg = $.extend({}, GlobalWatchlist.cfg, metaCfg, defaultCfg, userSettings);
new mw.Api().getMessages(
Object.values(GlobalWatchlist.i18n.mwMsgs),
{amenableparser: true, amtitle: 'Special:Watchlist'}
).then((mwUserLocalMsgs) => {
const localMsgs = {},
translations = $.extend({}, GlobalWatchlist.i18n.en, GlobalWatchlist.i18n[mwCfg.wgUserLanguage] || {});
Object.keys(GlobalWatchlist.i18n.mwMsgs).forEach((k) => {
localMsgs[`gw-msg-${k}`] = mwUserLocalMsgs[GlobalWatchlist.i18n.mwMsgs[k]];
});
if (mwCfg.wgUserLanguage !== 'qqx') {
Object.keys(translations).forEach((k) => {
localMsgs[`gw-msg-${k}`] = translations[k];
});
}
mw.messages.set(localMsgs);
GlobalWatchlist.start(mode);
});
};
GlobalWatchlist.mode = function (num) {
GlobalWatchlist.debug('mode', num);
GlobalWatchlist.cfg.mode = num;
switch (num) {
// Loading global watchlist
case 10:
GlobalWatchlist.elements.live.setDisabled(true);
$('#globalWatch-watchlistsLoading').show();
$('#globalWatch-markSeen-all').hide();
$('#globalWatch-watchlistsFeed').hide();
$('#globalWatch-asOf')[0].innerText = '';
break;
// Showing global watchlist
case 11:
GlobalWatchlist.elements.live.setDisabled(GlobalWatchlist.cfg.fastMode);
GlobalWatchlist.elements.refresh.setDisabled(false);
GlobalWatchlist.elements.groupPage.setDisabled(GlobalWatchlist.cfg.fastMode);
GlobalWatchlist.elements.live.setIcon('play');
$('#globalWatch-watchlistsLoading').hide();
$('#globalWatch-watchlistsFeed').show();
GlobalWatchlist.watchlists.checkChangesShown(false);
break;
// Marking all sites as seen, primarily used for status
case 12:
$('span.globalWatch-feed-markSeen > button > span.oo-ui-labelElement-label').each(function () {
this.click();
});
break;
// Live updates running
case 13:
GlobalWatchlist.elements.refresh.setDisabled(true);
GlobalWatchlist.elements.groupPage.setDisabled(true);
GlobalWatchlist.elements.live.setIcon('pause');
GlobalWatchlist.watchlists.runLive();
break;
// Anything else (not supported)
default:
GlobalWatchlist.error('Unsupported mode', num);
}
};
GlobalWatchlist.start = function (mode) {
GlobalWatchlist.cfg.mode = mode;
switch (mode) {
// Start global watchlist
case 1:
GlobalWatchlist.help.setUp(1, 'globalWatchlist');
$('#mw-content-text').empty().append(GlobalWatchlist.watchlists.create());
GlobalWatchlist.watchlists.feed();
GlobalWatchlist.informExtensionLaunched();
break;
// Start global watchlist config
case 2:
GlobalWatchlist.help.setUp(2, 'globalWatchlistSettings');
$('#mw-content-text').empty().append(GlobalWatchlist.settings.create());
GlobalWatchlist.settings.prefill();
GlobalWatchlist.informExtensionLaunched();
break;
// Viewing normal watchlist, add a link
case 3: {
const target = '/wiki/Special:BlankPage/GlobalWatchlist',
text = mw.msg('gw-msg-globalWatchlistLink');
switch (GlobalWatchlist.cfg.skin) {
case 'cologneblue':
case 'minerva':
$('.mw-watchlist-toollinks')[0].childNodes[7].after($('.mw-watchlist-toollinks')[0].childNodes[6].textContent);
$('.mw-watchlist-toollinks')[0].childNodes[8].after(
$('<a>')
.text(text)
.attr('href', target)
.attr('title', text)[0]
);
break;
case 'modern':
case 'monobook':
mw.util.addPortletLink('p-personal', target, text, '', text);
break;
default:
// Vector, timeless
mw.util.addPortletLink('p-views', target, text, '', text);
break;
}
break;
}
// Anything else (tests, qqq documentation, i18n)
default:
GlobalWatchlist.cfg.testNoActions = true;
mw.hook('GlobalWatchlistInternal').fire();
break;
}
};
GlobalWatchlist.informExtensionLaunched = function () {
// Notification that the extension is now available on meta
// No translation support because this will be the last thing added to the script...
OO.ui.alert(
$( '<span>')
.append(
'The ',
$( '<a>' )
.attr( 'href', 'https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:GlobalWatchlist' )
.attr( 'target', '_blank' )
.text( 'GlobalWatchlist extension' ),
' is now available on Meta. Please use that instead. Visit ',
$( '<a>' )
.attr( 'href', 'https://meta.wikimedia.org/wiki/Special:GlobalWatchlistSettings' )
.attr( 'target', '_blank' )
.text( 'Special:GlobalWatchlistSettings' ),
' on Meta to get started.'
),
{ size: 'large' }
);
};
GlobalWatchlist.debugLog = [];
GlobalWatchlist.debug = function (key, msg, level = 1) {
if (GlobalWatchlist.cfg.debugMode >= level) {
console.log(`GlobalWatchlist@${key}:`); // eslint-disable-line no-console
console.log(msg); // eslint-disable-line no-console
}
const logMessage = `${GlobalWatchlist.debugLog.length}: ${key}\t${JSON.stringify(msg)}`;
GlobalWatchlist.debugLog.push(logMessage);
};
GlobalWatchlist.error = function (info, error) {
GlobalWatchlist.debug(`ERROR: ${info}`, error, 0);
alert('GlobalWatchlist error, please check the console!'); // eslint-disable-line no-alert
throw new Error(`Error: ${info} - ${error}`);
};
GlobalWatchlist.Site = function (urlFragment) { // eslint-disable-line max-lines-per-function
this.site = urlFragment.replace(/_/g, '.');
this.siteID = urlFragment.replace(/\./g, '_');
this.divID = `globalWatch-feed-site-${this.siteID}`;
this.$feedDiv = '';
this.isEmpty = false;
this.tags = {};
this.api = function (func, content, name) {
const skipWhenTesting = [ 'updateWatched', 'actuallyMarkSiteAsSeen' ].indexOf(name) > -1;
return new Promise((resolve) => {
this.debug(`API.${name} (called); func & content`, [ func, content ], 1);
if (skipWhenTesting && GlobalWatchlist.cfg.testNoActions) {
this.debug(`API.${name} (skipping, testNoActions); func & content`, [ func, content ], 3);
resolve();
} else {
GlobalWatchlist.help.api(this.site)[func](content).then((response) => {
this.debug(`API.${name} (result); func, content & response`, [ func, content, response ], 2);
resolve(response);
}).catch((error) => {
GlobalWatchlist.error(`API.${this.site}@${name}`, error);
});
}
});
};
this.debug = function (key, msg, level = 1) {
GlobalWatchlist.debug(`${this.site}:${key}`, msg, level);
};
this.actuallyGetWatchlist = function (iter, cnt) {
const that = this;
return new Promise((resolve) => {
const getter = {
action: 'query',
formatversion: 2,
list: 'watchlist',
wllimit: 'max',
wlprop: GlobalWatchlist.cfg.wlStr,
wlshow: GlobalWatchlist.cfg.flags.totalStr,
wltype: GlobalWatchlist.cfg.types.totalStr,
};
if (iter > 1) {
getter.wlcontinue = cnt;
}
if (!GlobalWatchlist.cfg.fastMode) {
getter.wlallrev = true;
}
that.api('get', getter, `actuallyGetWatchlist ${iter}`).then((response) => {
const wlraw = response.query.watchlist;
if (response.continue && response.continue.wlcontinue) {
that.actuallyGetWatchlist(iter + 1, response.continue.wlcontinue).then((innerResponse) => {
resolve(wlraw.concat(innerResponse));
});
} else {
resolve(wlraw);
}
});
});
};
this.changeWatched = function (pageTitle, func) {
this.debug('changeWatched', `Going to ${func}: ${pageTitle}`);
const that = this,
titleReal = pageTitle.replace(/DOUBLEQUOTE/g, '"');
that.api(func, titleReal, 'updateWatched');
that.processUpdateWatched(pageTitle, func === 'unwatch');
if (!GlobalWatchlist.cfg.fastMode) {
that.getAssociatedPageTitle(titleReal).then((associatedTitle) => {
that.processUpdateWatched(associatedTitle, func === 'unwatch');
if (func === 'unwatch') {
GlobalWatchlist.watchlists.checkChangesShown(true);
}
});
}
};
this.getAssociatedPageTitle = function (pageTitle) {
return new Promise((resolve) => {
const getter = {
action: 'parse',
contentmodel: 'wikitext',
formatversion: 2,
onlypst: true,
text: `{{subst:TALKPAGENAME:${pageTitle}}}\n{{subst:SUBJECTPAGENAME:${pageTitle}}}`,
};
this.api('get', getter, 'parseOnlyPST').then((response) => {
const titles = response.parse.text.split('\n');
resolve(titles[1] === pageTitle ? titles[0] : titles[1]);
});
});
};
this.getTagList = function () {
const that = this;
return new Promise((resolve) => {
if (GlobalWatchlist.cfg.fastMode || Object.keys(that.tags).length > 0) {
resolve(true);
} else {
const getter = {
action: 'query',
list: 'tags',
tglimit: 'max',
tgprop: 'displayname',
};
that.api('get', getter, 'getTags').then((response) => {
const asObject = {};
response.query.tags.forEach((t) => {
asObject[t.name] = t.displayname || false ? t.displayname.replace(/<a href="\/wiki\//g, `<a href="https://${that.site}.org/wiki/`) : t.name;
});
that.debug('getTagList', asObject, 3);
that.tags = asObject;
resolve(true);
});
}
});
};
this.getWatchlist = function () {
const that = this;
return new Promise((resolve) => {
that.actuallyGetWatchlist(1, 0).then((wlraw) => {
if (!(wlraw && wlraw[0])) {
that.debug('getWatchlist', 'empty');
that.isEmpty = true;
resolve();
}
that.debug('getWatchlist wlraw', wlraw);
const prelimSummary = GlobalWatchlist.help.rawToSummary(wlraw, this.site);
that.debug('getWatchlist prelimSummary', prelimSummary);
that.makeWikidataList(prelimSummary).then((summary) => {
that.debug('getWatchlist summary', summary);
that.getTagList().then(() => {
const $ul = $('<ul>');
summary.forEach((e) => $ul.append(that.makePageLink(e)));
that.$feedDiv = $('<div>')
.attr('id', that.divID)
.addClass('globalWatch-feed-site')
.append(
$('<h3>')
.append(
$('<a>')
.attr('href', `https://${that.site}.org/wiki/Special:Watchlist`)
.attr('target', '_blank')
.text(that.site),
' (',
$('<a>')
.attr('href', `https://${that.site}.org/wiki/Special:EditWatchlist`)
.attr('target', '_blank')
.text(mw.msg('gw-msg-editWatchlist')),
')'
),
$('<div>')
.addClass('globalWatch-site')
.append(
GlobalWatchlist.OOUI.buttonInput('markSeen', `${that.divID}-seen`, [ 'destructive' ], () => { GlobalWatchlist.help.markSiteSeen(that.site); }, 'check', 'globalWatch-feed-markSeen').$element,
$ul
)
.makeCollapsible()
);
resolve();
});
});
});
});
};
this.makePageLink = function (entry) { // eslint-disable-line max-lines-per-function, complexity
/* eslint-disable one-var */
let $before = false,
$comment = '',
$extraLink = false,
$tags = false,
$user = '';
const pageTitle = encodeURIComponent(entry.title).replace(/'/g, '%27');
const $pageLink = $('<a>')
.attr('href', `https://${this.site}.org/w/index.php?title=${pageTitle}&redirect=no`)
.attr('target', '_blank')
.text(entry.titleMsg || entry.title);
const $historyLink = $('<a>')
.attr('href', `https://${this.site}.org/w/index.php?title=${pageTitle}&action=history`)
.attr('target', '_blank')
.text(mw.msg('gw-msg-history'));
const that = this;
const $unwatchLink = $('<a>')
.addClass('globalWatch-watchunwatch')
.text(mw.msg('gw-msg-unwatch'))
.click(() => {
that.changeWatched(entry.title, 'unwatch');
});
/* eslint-enable one-var */
if (entry.editsbyuser || false) {
$user = entry.editsbyuser.replace('%SITE%', this.site);
} else if (!GlobalWatchlist.cfg.fastMode) {
if (entry.user === false) {
$user = $('<span>')
.addClass('history-deleted')
.text(mw.msg('gw-msg-deleted-user'));
} else if (entry.anon) {
$user = $('<a>')
.attr('href', `https://${this.site}.org/wiki/Special:Contributions/${entry.user}`)
.attr('target', '_blank')
.text(entry.user);
} else {
$user = $('<a>')
.attr('href', `https://${this.site}.org/wiki/User:${entry.user}`)
.attr('target', '_blank')
.text(entry.user);
}
}
if (entry.comment && entry.comment !== '') {
// Need to process links in the parsed comments as raw HTML
// eslint-disable-next-line prefer-template
$comment = $('<span>').html(': ' + entry.comment.replace(/<a href="\/wiki\//g, `<a href="https://${this.site}.org/wiki/`));
}
if (entry.entryType === 'edit') {
$extraLink = $('<a>')
.attr('href', `https://${this.site}.org/w/index.php?diff=${entry.toRev}&oldid=${entry.fromRev}`)
.attr('target', '_blank')
.addClass('globalWatchlist-diff')
.text(entry.editCount === 1 ? mw.msg('gw-msg-diff') : mw.msg('gw-msg-changes', entry.editCount));
} else if (entry.entryType === 'log') {
$extraLink = $('<a>')
.attr('href', `https://${this.site}.org/w/index.php?title=Special:Log&page=${pageTitle}`)
.attr('target', '_blank')
.text(mw.msg('gw-msg-pagelogs'));
}
if (entry.entryType === 'log') {
$before = $('<i>')
.text(`Log: ${entry.logtype}/${entry.logaction}: `);
} else if (entry.minor || entry.bot || entry.entryType === 'new') {
let letters = '';
if (entry.entryType === 'new') {
letters += mw.msg('gw-msg-newPageN');
}
if (entry.minor) {
letters += mw.msg('gw-msg-minorEditM');
}
if (entry.bot) {
letters += mw.msg('gw-msg-botEditB');
}
$before = $('<b>').text(letters);
}
if (entry.tags && entry.tags.length > 0) {
// Need to process links in the parsed description as raw HTML
// eslint-disable-next-line prefer-template
$tags = $('<i>').html('(Tags: ' + entry.tags.map((t) => this.tags[t]).join(', ') + ')');
}
// Actually set up the $row to be returned
// eslint-disable-next-line one-var
const $row = $('<li>');
$row.attr('siteAndPage', `${this.siteID}_${pageTitle}`);
if ($before !== false) {
$row.append($before)
.append(' ');
}
$row.append($pageLink)
.append(' (')
.append($historyLink)
.append(', ');
if ($extraLink !== false) {
$row.append($extraLink)
.append(', ');
}
$row.append($unwatchLink)
.append(') (')
.append($user)
.append($comment)
.append(')');
if ($tags !== false) {
$row.append(' ')
.append($tags);
}
this.debug('makePageLink for entry', [ entry, $row ], 3);
return $row;
};
this.makeWikidataList = function (summary) {
const that = this;
return new Promise((resolve) => {
if (that.site !== 'www.wikidata' || GlobalWatchlist.cfg.fastMode) {
resolve(summary);
} else {
const ids = [],
wdns = [ 0, 120, 146 ];
summary.forEach((e) => {
if (wdns.indexOf(e.ns) > -1) {
e.titleMsg = e.title.replace(/^(?:Property|Lexeme):/, '');
if (ids.indexOf(e.titleMsg) === -1) {
ids.push(e.titleMsg);
}
}
});
that.debug('makeWikidataList - summary, ids', [ summary, ids ]);
if (ids.length === 0) {
resolve(summary);
}
that.getWikidataLabels(ids).then((wdlabels) => {
const lang = GlobalWatchlist.cfg.lang;
summary.forEach((e) => {
if (wdns.indexOf(e.ns) > -1 && wdlabels[e.titleMsg]) {
that.debug('makeWikidataList - have entry', [ e, wdlabels[e.titleMsg] ], 3);
const entryWithLabel = wdlabels[e.titleMsg][e.ns === 146 ? 'lemmas' : 'labels'];
if (entryWithLabel && entryWithLabel[lang] && entryWithLabel[lang].value) {
e.titleMsg += ` (${entryWithLabel[lang].value})`;
}
}
});
resolve(summary);
});
}
});
};
this.getWikidataLabels = function (ids) {
const that = this;
return new Promise((resolve) => {
that.debug('getWikidataLabels ids', ids);
const lang = GlobalWatchlist.cfg.lang,
wdgetter = {
action: 'wbgetentities',
formatversion: 2,
ids: ids.slice(0, 50),
languages: lang,
props: 'labels',
};
that.api('get', wdgetter, 'getWikidataLabels').then((response) => {
const wdlabels = response.entities;
that.debug('getWikidataLabels wdlabels', wdlabels);
if (ids.length > 50) {
that.getWikidataLabels(ids.slice(50)).then((extraLabels) => {
const bothLabels = $.extend({}, wdlabels, extraLabels);
that.debug('getWikidataLabels bothLabels', bothLabels, 3);
resolve(bothLabels);
});
} else {
resolve(wdlabels);
}
});
});
};
this.markAsSeen = function () {
this.debug('markSiteAsSeen', 'marking');
const setter = {
action: 'setnotificationtimestamp',
entirewatchlist: true,
timestamp: GlobalWatchlist.cfg.time.toISOString(),
};
this.api('postWithEditToken', setter, 'actuallyMarkSiteAsSeen');
if (GlobalWatchlist.cfg.mode === 11 || GlobalWatchlist.cfg.mode === 13) {
this.debug('markSiteAsSeen', 'hiding');
$(`#${this.divID} > *:not(h3)`).hide();
}
GlobalWatchlist.watchlists.checkChangesShown(true);
};
this.processUpdateWatched = function (pageTitle, unwatched) {
this.debug('processUpdateWatched', `Proccessing after ${unwatched ? 'unwatching' : 'rewatching'}: ${pageTitle}`);
const encodedTitle = encodeURIComponent(pageTitle).replace(/'/g, '%27').replace(/DOUBLEQUOTE/g, '%22'),
msg = mw.msg(`gw-msg-${unwatched ? 'rewatch' : 'unwatch'}`),
that = this,
$links = $(`li[siteAndPage='${this.siteID}_${encodedTitle}'] > a.globalWatch-watchunwatch`); // eslint-disable-line sort-vars
$(`li[siteAndPage='${this.siteID}_${encodedTitle}']`)[unwatched ? 'addClass' : 'removeClass']('globalWatch-strike');
$links.each(function () {
$(this).off('click');
$(this).on('click', () => {
that.changeWatched(pageTitle, unwatched ? 'watch' : 'unwatch');
});
$(this).text(msg);
});
};
};
GlobalWatchlist.settings = {
actuallySaveOptions: function (newOptions) {
new mw.Api().edit(GlobalWatchlist.cfg.globalJS, (revision) => {
const imports = GlobalWatchlist.cfg.scriptImports;
let content = revision.content.replace(imports.prod2, imports.prod);
if (GlobalWatchlist.cfg.configMode === 2) {
content = content.replace(/window\.GlobalWatchlistConfig\s*=\s*{[^}]*\};\n/, newOptions);
} else {
content = content.replace(imports.prod, newOptions + imports.prod);
}
return {
summary: `Updating global watchlist settings (${GlobalWatchlist.cfg.name} v. ${GlobalWatchlist.cfg.version})`,
text: content,
};
}).done(() => {
GlobalWatchlist.debug('Settings.actuallySaveOptions', 'should be done');
GlobalWatchlist.help.notify('settingsSaved');
GlobalWatchlist.help.notify('redirecting');
if (GlobalWatchlist.cfg.debugMode < 2) {
setTimeout(() => {
window.location.href = window.location.href.replace('GlobalWatchlistConfig', 'GlobalWatchlist');
}, 2000);
}
}).fail(() => {
GlobalWatchlist.debug('Settings.actuallySaveOptions', 'something went wrong');
GlobalWatchlist.help.notify('savingFailed');
});
},
addRow: function (start = '') {
const num = GlobalWatchlist.elements.sites.length,
row = new OO.ui.ActionFieldLayout(
new OO.ui.TextInputWidget({
classes: [ 'globalWatch-site-text' ],
value: start,
}),
GlobalWatchlist.OOUI.buttonInput('remove', '', [ 'destructive' ], () => { GlobalWatchlist.elements.sites[num].toggle(); })
);
GlobalWatchlist.elements.sitelist.addItems([ row ]);
GlobalWatchlist.elements.sites.push(row);
return row;
},
create: function () { return [
GlobalWatchlist.OOUI.buttonInputSimpleE('resetChanges', GlobalWatchlist.settings.prefill, 'history'),
GlobalWatchlist.OOUI.buttonE('Back', 'GlobalWatchlist', 'previous'),
GlobalWatchlist.OOUI.buttonInput(GlobalWatchlist.cfg.scriptImports.usingDev ? 'switch-stable' : 'switch-dev', 'globalWatch-switch-import', [ 'destructive' ], GlobalWatchlist.settings.switchBranch).$element,
$('<hr>'),
$('<br>'),
$('<div>')
.attr('id', 'globalWatch-sites')
.append(
GlobalWatchlist.OOUI.labelE('siteList'),
$('<div>')
.attr('id', 'globalWatch-sites-list'),
GlobalWatchlist.elements.sitelist.$element,
GlobalWatchlist.OOUI.buttonInputSimpleE('add', GlobalWatchlist.settings.addRow, 'add'),
GlobalWatchlist.OOUI.buttonInputSimpleE('save', GlobalWatchlist.settings.saveChanges, 'bookmark')
),
$('<div>')
.attr('id', 'globalWatch-filters')
.append(
$('<div>')
.attr('id', 'globalWatch-filters-label')
.append(GlobalWatchlist.OOUI.labelE('filters')),
$('<hr>'),
$('<div>')
.attr('id', 'globalWatch-filters-list')
.append(
GlobalWatchlist.elements.anon.$element,
GlobalWatchlist.elements.bot.$element,
GlobalWatchlist.elements.minor.$element
),
$('<hr>'),
$('<div>')
.attr('id', 'globalWatch-settings-other')
.append(
GlobalWatchlist.OOUI.optionsE('changetypes', [ 'edits', 'logEntries', 'newPages' ]),
GlobalWatchlist.OOUI.optionsE('otherOptions', [ 'groupPage', 'confirmAllSites', 'fastMode' ])
)
),
]; },
prefill: function () {
const c = $.extend({}, GlobalWatchlist.cfg, GlobalWatchlist.cfg.types, GlobalWatchlist.cfg.flags);
[ 'anon', 'bot', 'minor' ].forEach((s) => {
GlobalWatchlist.elements[s].selectItemByData(c[s]);
});
[ 'edits', 'logEntries', 'newPages', 'groupPage', 'confirmAllSites', 'fastMode' ].forEach((s) => {
GlobalWatchlist.elements[s].setSelected(c[s]);
});
GlobalWatchlist.elements.sitelist.clearItems();
GlobalWatchlist.cfg.siteList.forEach((s, i) => {
GlobalWatchlist.elements.sites[i].toggle(true);
GlobalWatchlist.elements.sitelist.addItems([ GlobalWatchlist.elements.sites[i] ]);
$('.globalWatch-site-text:last > input')[0].value = s;
});
GlobalWatchlist.elements.sites[GlobalWatchlist.cfg.siteList.length].toggle(true);
GlobalWatchlist.elements.sitelist.addItems([ GlobalWatchlist.elements.sites[GlobalWatchlist.cfg.siteList.length] ]);
$('.globalWatch-site-text:last > input')[0].value = '';
},
saveChanges: function () {
const e = GlobalWatchlist.elements,
settings = {
anonFilter: e.anon.findSelectedItem().data,
botFilter: e.bot.findSelectedItem().data,
confirmAllSites: e.confirmAllSites.isSelected(),
fastMode: e.fastMode.isSelected(),
groupPage: e.groupPage.isSelected(),
minorFilter: e.minor.findSelectedItem().data,
showEdits: e.edits.isSelected(),
showLogEntries: e.logEntries.isSelected(),
showNewPages: e.newPages.isSelected(),
sites: [ ...new Set($('div.oo-ui-actionFieldLayout:visible .globalWatch-site-text > input').filter(function () { return this.value && this.value !== ''; }).map((s, f) => f.value).toArray()) ],
};
GlobalWatchlist.settings.validate(settings).then((validation) => {
GlobalWatchlist.debug('Settings.saveChanges - chosen and settingsValidation', [ settings, validation ]);
if (validation.length > 0) {
OO.ui.alert(new OO.ui.HtmlSnippet(`<ul><li>${validation.join('</li><li>')}</li></ul>`), {title: 'Invalid settings'});
} else if (GlobalWatchlist.cfg.testNoActions) {
GlobalWatchlist.debug('Settings.saveChanges - skipping, testNoActions', settings);
} else {
// eslint-disable-next-line prefer-template, prefer-named-capture-group
GlobalWatchlist.settings.actuallySaveOptions('window.GlobalWatchlistConfig = ' + JSON.stringify(settings, null, ' ').replace(/\n/g, '').replace(/(\S)}/, '$1 }') + ';\n');
}
});
},
switchBranch: function () {
new mw.Api().edit(GlobalWatchlist.cfg.globalJS, (revision) => {
const allimports = GlobalWatchlist.cfg.scriptImports,
currentImport = allimports[allimports.usingDev ? 'prod' : 'prodStable'],
nextImport = allimports[allimports.usingDev ? 'prodStable' : 'prod'];
return {
summary: `Switching global watchlist branch (switched from ${GlobalWatchlist.cfg.name} v. ${GlobalWatchlist.cfg.version})`,
text: revision.content.replace(currentImport, nextImport),
};
}).then(() => {
GlobalWatchlist.debug('Settings.switchBranch', 'should be done');
if (GlobalWatchlist.cfg.debugMode < 2) {
setTimeout(() => {
window.location.reload();
}, 1000);
}
});
},
validate: function (settings) {
return new Promise((resolve) => {
new mw.Api().get({
action: 'query',
meta: 'globaluserinfo',
guiprop: 'merged', // eslint-disable-line sort-keys
}).done((response) => {
const accounts = response.query.globaluserinfo.merged.map((s) => s.url.replace(/https:\/\/|\.org/g, '')),
badSites = settings.sites.filter((s) => accounts.indexOf(s) === -1);
let errors = [];
GlobalWatchlist.debug('Settings.validate - bad sites', badSites);
if (settings.sites.length === 0) {
errors.push('nosites');
}
if (!settings.showEdits && !settings.showNewPages && !settings.showLogEntries) {
errors.push('nochanges');
}
if (settings.anonFilter === 1 && settings.botFilter === 1) {
errors.push('anonbot');
}
if (settings.anonFilter === 1 && settings.minorFilter === 1) {
errors.push('anonminor');
}
errors = errors.map((e) => mw.msg(`gw-msg-settings-error-${e}`));
if (badSites.length > 0) {
errors.push(mw.msg('gw-msg-settings-error-badsites', badSites.join(', ')));
}
resolve(errors);
});
});
},
};
GlobalWatchlist.watchlists = {
checkChangesShown: function (shouldReload) {
const sitesWithChangesShown = $('#globalWatch-feedCollector > div.globalWatch-feed-site > div.globalWatch-site ul:has(li:visible:not(.globalWatch-strike))').length;
if (sitesWithChangesShown > 1 && GlobalWatchlist.cfg.mode !== 12) {
$('#globalWatch-markSeen-all').show();
} else if (sitesWithChangesShown === 1) {
$('#globalWatch-markSeen-all').hide();
} else if (shouldReload) {
setTimeout(GlobalWatchlist.watchlists.feed, 1000);
}
},
create: function () { return [
$('<div>')
.attr('id', 'globalWatch-toolbar')
.append(
GlobalWatchlist.elements.live.$element,
GlobalWatchlist.elements.groupPage.$element,
GlobalWatchlist.elements.refresh.$element,
GlobalWatchlist.OOUI.buttonE('Settings', 'GlobalWatchlistConfig', 'settings'),
GlobalWatchlist.OOUI.buttonInput('markSeen-all', 'globalWatch-markSeen-all', [ 'primary', 'destructive' ], GlobalWatchlist.watchlists.markAllAsSeen, 'checkAll').$element
),
$('<div>')
.attr('id', 'globalWatch-asOf'),
new OO.ui.ProgressBarWidget({id: 'globalWatch-watchlistsLoading'}).$element,
$('<div>')
.attr('id', 'globalWatch-watchlistsFeed'),
]; },
feed: function () {
if (GlobalWatchlist.cfg.mode === 13) {
return;
}
GlobalWatchlist.mode(10);
GlobalWatchlist.watchlists.refresh().then(() => GlobalWatchlist.mode(11));
},
markAllAsSeen: function () {
if (GlobalWatchlist.cfg.confirmAllSites === false) {
GlobalWatchlist.debug('watchlists.markSeen-all', 'Marking all sites as seen without confirmation');
GlobalWatchlist.mode(12);
} else {
OO.ui.confirm(mw.msg('gw-msg-markSeen-allConfirm')).done((confirmed) => {
GlobalWatchlist.debug('watchlists.markAllAsSeen', confirmed ? 'Marking all sites as seen' : 'Aborting...');
if (confirmed) {
GlobalWatchlist.mode(12);
}
});
}
},
refresh: function () {
GlobalWatchlist.debug('watchlists.refresh', 'starting refresh');
GlobalWatchlist.cfg.time = new Date();
return new Promise((resolve) => {
Promise.all(GlobalWatchlist.sites.map((s) => s.getWatchlist())).then(() => {
const $div = $('<div>').attr('id', 'globalWatch-feedCollector'),
emptySites = [];
let showChangesLabel = false;
GlobalWatchlist.sites.forEach((s) => {
GlobalWatchlist.debug('watchlists.refrsh site loop, a site', s, 3);
if (s.isEmpty) {
emptySites.push(s.site);
} else {
showChangesLabel = true;
$div.append(s.$feedDiv);
}
});
if (showChangesLabel) {
$div
.prepend(GlobalWatchlist.OOUI.labelE('changesFeed'))
.append($('<hr>'));
}
if (emptySites[0]) {
const $ul = $('<ul>');
[ ...new Set(emptySites) ].forEach((s) => {
$ul.append(
$('<li>')
.addClass('globalWatch-emptyWatchlist')
.append(
$('<a>')
.attr('href', `https://${s}.org/wiki/Special:Watchlist`)
.text(s),
' (',
$('<a>')
.attr('href', `https://${s}.org/wiki/Special:EditWatchlist`)
.text(mw.msg('gw-msg-editWatchlist')),
')'
)
);
});
$div.append(
GlobalWatchlist.OOUI.labelE('emptyFeed'),
$('<div>')
.addClass('globalWatch-emptySites')
.addClass('mw-collapsed')
.append(
$('<div>')
.addClass('globalWatch-col')
.append($ul)
)
.makeCollapsible()
);
}
$('#globalWatch-watchlistsFeed')
.empty()
.append($div);
GlobalWatchlist.watchlists.runLive();
$('#globalWatch-asOf')[0].innerText = mw.msg('gw-msg-asOf', GlobalWatchlist.cfg.time.toUTCString());
resolve();
}).catch((error) => {
GlobalWatchlist.debug('watchlists.refresh ERROR', error);
});
});
},
runLive: function () {
if (GlobalWatchlist.cfg.mode === 13) {
GlobalWatchlist.debug('watchlists.runLive - counter', GlobalWatchlist.cfg.liveCounter++);
setTimeout(GlobalWatchlist.watchlists.refresh, 7500);
}
},
};
GlobalWatchlist.help = {
api: function (site) {
return new mw.ForeignApi(`//${site}.org/w/api.php`);
},
convertEdits: function (edits, site) {
const finalEdits = [];
Object.values(edits).forEach((e) => {
const pagebase = {
entryType: 'edit',
ns: e.ns,
title: e.title,
};
if (!GlobalWatchlist.cfg.groupPage || e.each.length === 1) {
e.each.forEach((f) => {
finalEdits.push($.extend({}, pagebase, {
anon: f.anon || false,
bot: f.bot,
comment: f.parsedcomment || '',
editCount: 1,
fromRev: f.old_revid,
minor: f.minor,
tags: f.tags,
toRev: f.revid,
user: f.user,
}));
});
} else { /* eslint-disable no-extra-parens */
const distinctUsers = [ ...new Set(e.each.map((f) => f.user)) ],
userEntries = [];
distinctUsers.forEach((u) => {
const userEdits = e.each.filter((f) => (f.user === u)),
userLink = (userEdits[0].user === false) ? `<span class="history-deleted">${mw.msg('gw-msg-deleted-user')}</span>` : `<a href="https://${site}.org/wiki/${userEdits[0].anon || false ? 'Special:Contributions/' : 'User:'}${u}" target="_blank">${u}</a>`;
userEntries.push(`${userLink}${userEdits.length > 1 ? ` ${mw.msg('gw-msg-editCount', userEdits.length)}` : ''}`);
});
finalEdits.push($.extend({}, pagebase, {
bot: e.each.map((f) => f.bot).reduce((a, b) => (a && b)),
editCount: e.each.length,
editsbyuser: userEntries.join(', '),
fromRev: e.each.map((f) => f.old_revid).reduce((a, b) => (a > b ? b : a)),
minor: e.each.map((f) => f.minor).reduce((a, b) => (a && b)),
tags: [],
toRev: e.each.map((f) => f.revid).reduce((a, b) => (a > b ? a : b)),
}));
} /* eslint-enable no-extra-parens */
});
return finalEdits;
},
flag: function (setting, msg) {
switch (setting) {
case 1:
return `|${msg}`;
case 2:
return `|!${msg}`;
default:
return '';
}
},
getUserSettings: function (mwCfg) {
const cfg = {
debugMode: mwCfg.wgUserName === 'DannyS712 test' ? 2 : 1,
globalJS: `User:${mwCfg.wgUserName}/global.js`,
lang: mwCfg.wgUserLanguage,
skin: mwCfg.skin,
};
if (typeof window.GlobalWatchlistConfig !== 'undefined') {
GlobalWatchlist.debug('help.getUserSettings GlobalWatchlistConfig', window.GlobalWatchlistConfig);
const WGWC = window.GlobalWatchlistConfig,
flags = {anon: WGWC.anonFilter || 0, bot: WGWC.botFilter || 0, minor: WGWC.minorFilter || 0},
types = {edits: WGWC.showEdits !== false, logEntries: WGWC.showLogEntries !== false, newPages: WGWC.showNewPages !== false};
flags.totalStr = [ 'unread', GlobalWatchlist.help.flag(flags.bot, 'bot'), GlobalWatchlist.help.flag(flags.anon, 'anon'), GlobalWatchlist.help.flag(flags.minor, 'minor') ].join('');
types.totalStr = ((types.edits ? 'edit|' : '') + (types.newPages ? 'new|' : '') + (types.logEntries ? 'log|' : '')).replace(/\|+$/, '');
if (WGWC.fastMode) {
cfg.fastMode = true;
cfg.wlStr = 'ids|title|flags|loginfo';
}
cfg.configMode = 2;
cfg.siteList = WGWC.sites;
cfg.groupPage = WGWC.groupPage;
cfg.confirmAllSites = WGWC.confirmAllSites;
cfg.flags = flags;
cfg.types = types;
}
return cfg;
},
markSiteSeen: function (site) {
new GlobalWatchlist.Site(site).markAsSeen();
},
notify: function (msg) {
mw.notify(mw.msg(`gw-msg-notify-${msg}`), {title: 'Global Watchlist'});
},
rawToSummary: function (entries, site) {
const edits = {},
logEntries = [],
newPages = [];
entries.forEach((e) => {
if (e.userhidden) {
e.user = false;
}
if (e.type === 'edit') {
if (typeof edits[e.pageid] === 'undefined') {
edits[e.pageid] = {each: [ e ], ns: e.ns, title: e.title};
} else {
edits[e.pageid].each.push(e);
}
} else {
const entryBase = {
anon: e.anon || false,
comment: e.parsedcomment || '',
entryType: e.type,
ns: e.ns,
tags: e.tags,
title: e.title,
user: e.user,
};
if (e.type === 'new') {
newPages.push(entryBase);
} else if (e.type === 'log') {
logEntries.push($.extend(entryBase, {
logaction: e.logaction,
logtype: e.logtype,
}));
}
}
});
return [ ...newPages, ...GlobalWatchlist.help.convertEdits(edits, site), ...logEntries ];
},
setUp: function (mode, page) {
window.document.title = mw.msg(`gw-msg-title-${page}`);
$(`#${GlobalWatchlist.cfg.skin === 'minerva' ? 'section_0' : 'firstHeading'}`)[0].innerText = mw.msg(`gw-msg-heading-${page}`);
if (mode === 1) {
GlobalWatchlist.elements = {
groupPage: GlobalWatchlist.OOUI.toggleButton('groupPage', GlobalWatchlist.cfg.groupPage && !GlobalWatchlist.cfg.fastMode, () => {
GlobalWatchlist.cfg.groupPage = GlobalWatchlist.elements.groupPage.value;
GlobalWatchlist.watchlists.feed();
}),
live: GlobalWatchlist.OOUI.toggleButton('live', false, () => {
GlobalWatchlist.mode(GlobalWatchlist.elements.live.value ? 13 : 11);
}),
refresh: GlobalWatchlist.OOUI.buttonInput('refresh', 'globalWatch-refresh', [ 'primary', 'progressive' ], GlobalWatchlist.watchlists.feed, 'reload'),
};
GlobalWatchlist.sites = GlobalWatchlist.cfg.siteList.map((s) => new GlobalWatchlist.Site(s));
} else if (mode === 2) {
GlobalWatchlist.elements = {
anon: GlobalWatchlist.OOUI.filter('anon'),
bot: GlobalWatchlist.OOUI.filter('bot'),
confirmAllSites: GlobalWatchlist.OOUI.checkBox('option-confirmAllSites'),
edits: GlobalWatchlist.OOUI.checkBox('show-edits'),
fastMode: GlobalWatchlist.OOUI.checkBox('option-fastMode'),
groupPage: GlobalWatchlist.OOUI.checkBox('option-groupPage'),
logEntries: GlobalWatchlist.OOUI.checkBox('show-logEntries'),
minor: GlobalWatchlist.OOUI.filter('minor'),
newPages: GlobalWatchlist.OOUI.checkBox('show-newPages'),
sitelist: new OO.ui.FieldsetLayout(),
sites: [],
};
GlobalWatchlist.cfg.siteList.forEach((s) => GlobalWatchlist.settings.addRow(s));
GlobalWatchlist.settings.addRow();
}
$('#content div.mw-indicators').append(
$('<div>')
.addClass('mw-indicator')
.append(
$('<a>')
.addClass('mw-helplink')
.attr('href', 'https://meta.wikimedia.org/wiki/User:DannyS712/Global_watchlist')
.attr('target', '_blank')
.text(mw.msg('gw-msg-help'))
)
);
},
};
GlobalWatchlist.OOUI = {
buttonE: function (msg, target, icon) {
return new OO.ui.ButtonWidget({
flags: [ 'progressive' ],
href: `/wiki/:m:Special:BlankPage/${target}`,
icon: icon,
label: mw.msg(`gw-msg-globalWatchlist${msg}Link`),
}).$element;
},
buttonInput: function (msg, id, flags, onClick, icon, classes) { // eslint-disable-line max-params
return new OO.ui.ButtonInputWidget({
classes: [ classes ],
flags: flags,
icon: icon,
id: id,
label: mw.msg(`gw-msg-${msg}`),
}).on('click', onClick);
},
buttonInputSimpleE: function (msg, onClick, icon) {
return GlobalWatchlist.OOUI.buttonInput(msg, `globalWatch-${msg}`, [], onClick, icon).$element;
},
checkBox: function (label) {
return new OO.ui.CheckboxMultioptionWidget({label: mw.msg(`gw-msg-${label}`)});
},
filter: function (filter) {
return new OO.ui.RadioSelectWidget({
items: [ 'either', `only-${filter}`, `not-${filter}` ].map((m, i) => new OO.ui.RadioOptionWidget({data: i, label: mw.msg(`gw-msg-filter-${m}`)})),
text: mw.msg(`gw-msg-filter-${filter}`),
});
},
labelE: function (msg) {
return new OO.ui.LabelWidget({label: mw.msg(`gw-msg-${msg}`)}).$element;
},
optionsE: function (msg, items) {
return [
GlobalWatchlist.OOUI.labelE(msg),
new OO.ui.CheckboxMultiselectWidget({items: items.map((i) => GlobalWatchlist.elements[i])}).$element,
];
},
toggleButton: function (msg, value, onClick) {
return new OO.ui.ToggleButtonWidget({
disabled: GlobalWatchlist.cfg.fastMode,
label: mw.msg(`gw-msg-option-${msg}`),
value: value,
}).on('click', onClick);
},
};
GlobalWatchlist.i18n = {
en: {
'asOf': 'As of $1',
'changesFeed': 'Sites with changes',
'emptyFeed': 'Sites with no changes',
'filter-anon': 'Anonymous edits',
'filter-bot': 'Bot edits',
'filter-either': 'Either',
'filter-not-anon': 'Only edits made by logged-in users',
'filter-not-bot': 'Only non-bot edits',
'filter-not-minor': 'Only non-minor edits',
'filter-only-anon': 'Only edits made anonymously',
'filter-only-bot': 'Only bot edits',
'filter-only-minor': 'Only minor edits',
'globalWatchlistBackLink': 'Back to Global watchlist',
'globalWatchlistLink': 'Global watchlist',
'globalWatchlistSettingsLink': 'Settings',
'heading-globalWatchlist': 'Global watchlist',
'heading-globalWatchlistSettings': 'Global watchlist settings',
'markSeen': 'Mark as seen',
'markSeen-all': 'Mark all sites as seen',
'markSeen-allConfirm': 'Are you sure you want to mark sites as seen?',
'notify-redirecting': 'Redirecting back to global watchlist...',
'notify-savingFailed': 'Failed to save settings. Please try again',
'option-confirmAllSites': 'Require confirmation before marking all sites as seen',
'option-fastMode': 'Load faster, at the expense of less detail',
'otherOptions': 'Other settings',
'refresh': 'Refresh',
'resetChanges': 'Reset changes made',
'rewatch': 'rewatch',
'settings-error-anonbot': 'Anonymous users cannot make bot edits',
'settings-error-anonminor': 'Anonymous users cannot make minor edits',
'settings-error-badsites': 'Invalid site(s): $1',
'settings-error-nochanges': 'No change types selected',
'settings-error-nosites': 'No sites selected',
'show-edits': 'Show page edits',
'show-logEntries': 'Show log entries',
'show-newPages': 'Show new page creations',
'siteList': 'User defined site list',
'switch-dev': 'Switch to development version',
'switch-stable': 'Switch to stable version',
'title-globalWatchlist': 'Global watchlist',
'title-globalWatchlistSettings': 'Global watchlist settings',
'unwatch': 'unwatch',
},
mwMsgs: {
'add': 'htmlform-cloner-create',
'botEditB': 'boteditletter',
'changes': 'nchanges',
'changetypes': 'rcfilters-filtergroup-changetype',
'deleted-user': 'rev-deleted-user',
'diff': 'diff',
'editCount': 'ntimes',
'editWatchlist': 'watchlistedit-normal-title',
'filter-minor': 'rcfilters-filter-minor-label',
'filters': 'whatlinkshere-filters',
'help': 'helppage-top-gethelp',
'history': 'history_small',
'minorEditM': 'minoreditletter',
'newPageN': 'newpageletter',
'notify-settingsSaved': 'savedprefs',
'option-groupPage': 'rcfilters-group-results-by-page',
'option-live': 'rcfilters-liveupdates-button',
'pagelogs': 'sp-contributions-logs',
'remove': 'htmlform-cloner-delete',
'save': 'saveprefs',
},
qqq: {
'asOf': 'When the feed was last refreshed; $1 = timestamp',
'changesFeed': 'Label for list of sites with changes to show',
'emptyFeed': 'Label for list of sites with no changes to show',
'filter-anon': 'Label for the anonymous edits filter',
'filter-bot': 'Label for the bot edits filter',
'filter-either': 'Option to not filter based on anonymous/bot/minor edits',
'filter-not-anon': 'Option to only show edits not made by anonymous users',
'filter-not-bot': 'Option to only show edits not made by bots',
'filter-not-minor': 'Option to only show edits not marked as minor',
'filter-only-anon': 'Option to only show edits made by anonymous users',
'filter-only-bot': 'Option to only show edits made by bots',
'filter-only-minor': 'Option to only show edits marked as minor',
'globalWatchlistBackLink': 'Label for the link to the global watchlist from the settings page',
'globalWatchlistLink': 'Label for the link to the global watchlist from the normal watchlist page',
'globalWatchlistSettingsLink': 'Label for the link to the settings page from the global watchlist',
'heading-globalWatchlist': '"Global watchlist" header',
'heading-globalWatchlistSettings': 'Settings page header',
'markSeen': 'Button to mark a specific site as seen',
'markSeen-all': 'Button to mark all sites as seen',
'markSeen-allConfirm': 'Confirmation message for marking all sites as seen',
'notify-redirecting': 'Notification of redirecting to global watchlist after settings saved',
'notify-savingFailed': 'Notification that settings were not saved successfully',
'option-confirmAllSites': 'Label for toggle option to opt-out of confirming before marking all sites as seen',
'option-fastMode': 'Label for toggle option to activate fast mode',
'otherOptions': 'Label for other script configuration options',
'refresh': 'Label for the refresh button',
'resetChanges': 'Label for the settings button to reset changes',
'rewatch': 'Label for button to rewatch a page',
'settings-error-anonbot': 'Error message for invalid settings',
'settings-error-anonminor': 'Error message for invalid settings',
'settings-error-badsites': 'Error message for invalid settings; $1 = bad sites',
'settings-error-nochanges': 'Error message for invalid settings',
'settings-error-nosites': 'Error message for invalid settings',
'show-edits': 'Label for the option to show edits',
'show-logEntries': 'Label for the option to show log entries',
'show-newPages': 'Label for the option to show new pages',
'siteList': 'Label for user defined site list',
'switch-dev': 'Label for switching to the development version of the script',
'switch-stable': 'Label for switching to the stable version of the script',
'title-globalWatchlist': '"Global watchlist" page title',
'title-globalWatchlistSettings': 'Settings page title',
'unwatch': 'Label for button to unwatch a page',
},
units: {
'asOf': 36,
'changesFeed': 19,
'emptyFeed': 20,
'filter-anon': 9,
'filter-bot': 8,
'filter-either': 11,
'filter-not-anon': 15,
'filter-not-bot': 13,
'filter-not-minor': 17,
'filter-only-anon': 14,
'filter-only-bot': 12,
'filter-only-minor': 16,
'globalWatchlistBackLink': 40,
'globalWatchlistLink': 1,
'globalWatchlistSettingsLink': 4,
'heading-globalWatchlist': 3,
'heading-globalWatchlistSettings': 6,
'markSeen': 24,
'markSeen-all': 28,
'markSeen-allConfirm': 37,
'notify-redirecting': 46,
'notify-savingFailed': 41,
'option-confirmAllSites': 43,
'option-fastMode': 44,
'otherOptions': 47,
'refresh': 23,
'resetChanges': 18,
'rewatch': 35,
'settings-error-anonbot': 48,
'settings-error-anonminor': 49,
'settings-error-badsites': 50,
'settings-error-nochanges': 51,
'settings-error-nosites': 52,
'show-edits': 32,
'show-logEntries': 34,
'show-newPages': 33,
'siteList': 21,
'switch-dev': 39,
'switch-stable': 38,
'title-globalWatchlist': 2,
'title-globalWatchlistSettings': 5,
'unwatch': 31,
},
bn: { // eslint-disable-line sort-keys
'asOf': '$1 অনুযায়ী',
'changesFeed': 'পরিবর্তনসহ সাইটগুলি',
'emptyFeed': 'অপরিবর্তিত সাইটগুলি',
'filter-anon': 'বেনামীর সম্পাদনাগুলি',
'filter-bot': 'বটের সম্পাদনাগুলি',
'filter-either': 'উভয়',
'filter-not-anon': 'শুধুমাত্র প্রবেশকৃত ব্যবহারকারীর দ্বারা সম্পাদনা',
'filter-not-bot': 'শুধুমাত্র বট-নয় সম্পাদনা',
'filter-not-minor': 'শুধুমাত্র অ-অনুল্লেখ্য সম্পাদনা',
'filter-only-anon': 'শুধুমাত্র বেনামে করা সম্পাদনা',
'filter-only-bot': 'শুধুমাত্র বটের সম্পাদনা',
'filter-only-minor': 'শুধুমাত্র অনুল্লেখ্য সম্পাদনা',
'globalWatchlistBackLink': 'বৈশ্বিক নজরতালিকায় ফেরত যান',
'globalWatchlistLink': 'বৈশ্বিক নজরতালিকা',
'globalWatchlistSettingsLink': 'সেটিংস',
'heading-globalWatchlist': 'বৈশ্বিক নজরতালিকা',
'heading-globalWatchlistSettings': 'বৈশ্বিক নজরতালিকার সেটিংস',
'markSeen': 'দেখা হিসাবে চিহ্নিত করুন',
'markSeen-all': 'সব সাইট দেখা হয়েছে হিসাবে চিহ্নিত করুন',
'markSeen-allConfirm': 'আপনি কি নিশ্চিত যে সব সাইটগুলিকে দেখা হয়েছে হিসাবে চিহ্নিত করতে চান?',
'notify-redirecting': 'বৈশ্বিক নজরতালিকায় পুনর্নির্দেশ করা হচ্ছে...',
'notify-savingFailed': 'সেটিংস সংরক্ষণ করতে ব্যর্থ। আবার চেষ্টা করুন',
'option-confirmAllSites': 'সমস্ত সাইট দেখা হয়েছে হিসেবে চিহ্নিত করার আগে নিশ্চিতকরণ প্রয়োজন',
'option-fastMode': 'কম বিশদসহ দ্রুত লোড হয়',
'otherOptions': 'অন্যান্য সেটিংস',
'refresh': 'পুনঃসতেজ করুন',
'resetChanges': 'পরিবর্তনগুলিকে পুনঃস্থাপিত করুন',
'rewatch': 'পুনরায় নজর রাখুন',
'settings-error-anonbot': 'বেনামী ব্যবহারকারীরা বট সম্পাদনা করতে পারবে না',
'settings-error-anonminor': 'বেনামী ব্যবহারকারীরা অনুল্লেখ্য সম্পাদনা করতে পারবে না',
'settings-error-badsites': 'অবৈধ সাইট(গুলি): $1',
'settings-error-nochanges': 'কোনও পরিবর্তনের ধরন নির্বাচন করা হয়নি',
'settings-error-nosites': 'কোনও সাইট নির্বাচন করা হয়নি',
'show-edits': 'পৃষ্ঠা সম্পাদনাগুলি দেখান',
'show-logEntries': 'লগ ভুক্তিগুলি দেখান',
'show-newPages': 'নতুন পৃষ্ঠাগুলি দেখান',
'siteList': 'ব্যবহারকারীর সংজ্ঞায়িত সাইটতালিকা',
'switch-dev': 'বিকাশ সংস্করণ ব্যবহার করুন',
'switch-stable': 'স্থিতিশীল সংস্করণ ব্যবহার করুন',
'title-globalWatchlist': 'বৈশ্বিক নজরতালিকা',
'title-globalWatchlistSettings': 'বৈশ্বিক নজরতালিকার সেটিংস',
'unwatch': 'নজর সরিয়ে নিন',
},
da: {
'globalWatchlistSettingsLink': 'Indstillinger',
'markSeen': 'Marker som set',
'markSeen-allConfirm': 'Er du sikker på, du vil markere sider som set?',
'otherOptions': 'Andre indstillinger',
'refresh': 'Genindlæs',
'rewatch': 'Gense',
'settings-error-anonbot': 'Anonyme brugere kan ikke lave bot redigeringer',
'settings-error-anonminor': 'Anonyme brugere kan ikke lave mindre redigeringer',
'settings-error-badsites': 'Ugyldig(e) side(r): $1',
'show-newPages': 'Vis nye sider',
},
de: {
'asOf': 'Stand $1',
'filter-anon': 'Anonyme Bearbeitungen',
'filter-bot': 'Bot-Bearbeitungen',
'filter-not-anon': 'Nur Bearbeitungen von angemeldeten Benutzern',
'filter-not-bot': 'Nur Nicht-Bot-Bearbeitungen',
'filter-not-minor': 'Nur geringfügige Bearbeitungen',
'filter-only-anon': 'Nur anonym gemachte Bearbeitungen',
'filter-only-bot': 'Nur Nicht-Bot-Änderungen',
'filter-only-minor': 'Nur geringfügige Bearbeitungen',
'globalWatchlistLink': 'Globale Beobachtungsliste',
'globalWatchlistSettingsLink': 'Einstellungen',
'heading-globalWatchlist': 'Globale Beobachtungsliste',
'heading-globalWatchlistSettings': 'Globale Beobachtungslisteneinstellungen',
'markSeen': 'Als gesehen markieren',
'option-fastMode': 'Lade schneller, zulasten des Detailgrads',
'otherOptions': 'Andere Einstellungen',
'refresh': 'Aktualisieren',
'resetChanges': 'Gemachte Änderungen zurücksetzen',
'settings-error-anonbot': 'Anonyme Benutzer können keine Bot-Bearbeitungen machen',
'settings-error-anonminor': 'Anonyme Benutzer können keine geringfügigen Bearbeitungen machen',
'settings-error-nochanges': 'Keine Änderungstypen ausgewählt',
'show-edits': 'Zeige Seitenbearbeitungen',
'show-logEntries': 'Zeige Log-Einträge',
'show-newPages': 'Zeige neue Seiten',
'switch-dev': 'Zur Entwicklungsversion wechseln',
'switch-stable': 'Zu stabiler Version wechseln',
'title-globalWatchlist': 'Globale Beobachtungsliste',
'title-globalWatchlistSettings': 'Globale Beobachtungslisteneinstellungen',
},
es: {
'settings-error-anonminor': 'Usuarios anónimos no pueden hacer ediciones menores',
},
fr: {
'asOf': 'Le $1',
'changesFeed': 'Sites modifiés',
'emptyFeed': 'Sites inchangés',
'filter-anon': 'Contributions anonymes',
'filter-bot': 'Modifications de robot',
'filter-either': 'Toutes',
'filter-not-anon': 'Contributions d\'utilisateurs connectés',
'filter-not-bot': 'Modifications faites par des contributeurs',
'filter-not-minor': 'Modifications non mineures',
'filter-only-anon': 'Contributions anonymes',
'filter-only-bot': 'Modifications par des outils automatisés',
'filter-only-minor': 'Modifications mineures',
'globalWatchlistBackLink': 'Retour à la liste de suivi globale',
'globalWatchlistLink': 'Liste de suivi globale',
'globalWatchlistSettingsLink': 'Réglages',
'heading-globalWatchlist': 'Liste de suivi globale',
'heading-globalWatchlistSettings': 'Réglages liste de suivi globale',
'markSeen': 'Marquer comme vu',
'markSeen-all': 'Marquer tous les sites comme vus',
'markSeen-allConfirm': 'Êtes-vous certain(e) de vouloir marquer les sites comme vus ?',
'notify-redirecting': 'Retour vers la liste de suivi globale en cours ...',
'notify-savingFailed': 'Échec de l\'enregistrement des paramètres. Merci de réessayer',
'option-confirmAllSites': 'Demande une confirmation avant de marquer tous les sites comme vus',
'option-fastMode': 'Chargement plus rapide, avec moins de détails',
'otherOptions': 'Autres options',
'refresh': 'Rafraîchir',
'resetChanges': 'Réinitialiser',
'settings-error-anonbot': 'Les contributeurs anonymes ne peuvent pas faire d\'éditions automatiques',
'settings-error-anonminor': 'Les contributeurs anonymes ne peuvent pas faire d\'éditions mineures',
'settings-error-badsites': 'Site(s) non valide(s) : $1',
'settings-error-nosites': 'Aucun site sélectionné',
'show-newPages': 'Affiche les nouvelles pages',
'siteList': 'Liste personnalisée',
'switch-dev': 'Passer à la version de développement',
'switch-stable': 'Passer à la version stable',
'title-globalWatchlist': 'Liste de suivi globale',
'title-globalWatchlistSettings': 'Réglages liste de suivi globale',
'unwatch': 'Ne plus suivre',
},
gu: {
'filter-only-minor': 'ખાલી નાનાં સંપાદનો',
'globalWatchlistSettingsLink': 'સેટિંગ્સ',
'resetChanges': 'સેટિંગ્સ ફરીથી સેટ કરો',
'unwatch': 'ચોકી રાખવાનું બંધ કરો',
},
he: {
'asOf': 'נכון ל־$1',
'changesFeed': 'אתרים עם שינויים',
'emptyFeed': 'אתרים ללא שינויים',
'filter-anon': 'עריכות אלמונים',
'filter-bot': 'עריכות בוט',
'filter-either': 'שניהם',
'filter-not-anon': 'רק עריכות של משתמשים מחיברים',
'filter-not-bot': 'רק עריכות של לא בוטים',
'filter-not-minor': 'רק עריכות לא משניות',
'filter-only-anon': 'רק עריכות של אלמונים',
'filter-only-bot': 'רק עריכות של בוטים',
'filter-only-minor': 'רק עריכות משניות',
'globalWatchlistBackLink': 'חזרה לרשימת מעקב גלובלית',
'globalWatchlistLink': 'רשימת מעקב גלובלית',
'globalWatchlistSettingsLink': 'הגדרות',
'heading-globalWatchlist': 'רשימת מעקב גלובלית',
'heading-globalWatchlistSettings': 'הגדרות רשימת מעקב גלובלית',
'markSeen': 'לסמן בתור אתרים שנצפו',
'markSeen-all': 'לסמן את כל האתרים כאתרים שנצפו',
'markSeen-allConfirm': 'אתם בטוחים כי ברצונכם לסמן אותם בתור אתרים שנצפו?',
'notify-redirecting': 'הפניה חזרה לרשימת מעקב גלובלית...',
'notify-savingFailed': 'שמירת ההגדרות נכשלה. נא לנסות שוב',
'option-confirmAllSites': 'לדרוש אישור לפני סימון כל האתרים כאתרים שנצפו',
'option-fastMode': 'לטעון מהר יותר ולוותר על חלק מהפרטים',
'otherOptions': 'הגדרות אחרות',
'refresh': 'רענון',
'resetChanges': 'איפוס השינויים שנעשו',
'rewatch': 'לעקוב שוב',
'settings-error-anonbot': 'משתמשים אלמוניים אינם יכולים לערוך כבוטים',
'settings-error-anonminor': 'משתמשים אלמוניים אינם יכולים לעשות עריכות משניות',
'settings-error-badsites': 'אתרים בלתי־תקינים: $1',
'settings-error-nochanges': 'לא נבחרו סוגי עריכות',
'settings-error-nosites': 'לא נבחרו אתרים',
'show-edits': 'הצגת עריכות דף',
'show-logEntries': 'הצגת רשומות יומן',
'show-newPages': 'הצגת דפים חדשים',
'siteList': 'רשימת האתרים להצגה',
'switch-dev': 'מעבר לגרסת פיתוח',
'switch-stable': 'מעבר לגרסה יציבה',
'title-globalWatchlist': 'רשימת מעקב גלובלית',
'title-globalWatchlistSettings': 'הגדרות רשימת מעקב גלובלית',
'unwatch': 'להפסיק לעקוב',
},
hi: {
'filter-anon': 'बेनाम संपादन',
'filter-bot': 'बाट संपादन',
'filter-either': 'या तो',
'filter-not-bot': 'केवल नॉन-बाट संपादन',
'filter-only-anon': 'केवल बेनाम किए गए संपादन',
'filter-only-bot': 'केवल बाट संपादन',
'globalWatchlistLink': 'वैश्विक ध्यानसूची',
'globalWatchlistSettingsLink': 'सेटिंग्स',
'heading-globalWatchlist': 'वैश्विक ध्यानसूची',
'heading-globalWatchlistSettings': 'वैश्विक ध्यानसूची सेटिंग्स',
'title-globalWatchlist': 'वैश्विक ध्यानसूची',
'title-globalWatchlistSettings': 'वैश्विक ध्यानसूची सेटिंग्स',
},
it: {
'changesFeed': 'Siti con modifiche',
'emptyFeed': 'Siti senza modifiche',
'filter-anon': 'Modifiche anonime',
'filter-bot': 'Modifiche di bot',
'filter-either': 'Entrambi',
'filter-not-anon': 'Solo modifiche fatte da utenti registrati',
'filter-not-bot': 'Solo modifiche non bot',
'filter-not-minor': 'Solo modifiche non minori',
'filter-only-anon': 'Solo modifiche fatte da anonimi',
'filter-only-bot': 'Solo modifiche bot',
'filter-only-minor': 'Solo modifiche minori',
'globalWatchlistSettingsLink': 'Impostazioni',
'otherOptions': 'Altre impostazioni',
'refresh': 'Aggiorna',
},
ja: {
'settings-error-anonbot': '匿名利用者はボットを操作する編集が認められていません',
'settings-error-badsites': '無効なサイト: $1',
'settings-error-nochanges': '変更の種類を選択していません',
'settings-error-nosites': 'サイトを選択していません',
},
lt: {
'otherOptions': 'Kitos nuostatos',
},
mk: {
'otherOptions': 'Други поставки',
},
ml: {
'asOf': '$1 പ്രകാരം',
'changesFeed': 'മാറ്റങ്ങളുള്ള സൈറ്റുകൾ',
'emptyFeed': 'മാറ്റങ്ങളിലാത്ത സൈറ്റുകൾ',
'filter-bot': 'ബോട്ട് തിരുത്തലുകൾ',
'filter-not-anon': 'പ്രവേശിച്ച ഉപയോക്താക്കൾ മാത്രം നടത്തിയ തിരുത്തലുകൾ',
'filter-not-bot': 'ബോട്ട് ഉപയോഗിക്കാതെ നടത്തിയ തിരുത്തലുകൾ',
'filter-only-bot': 'ബോട്ട് തിരുത്തലുകൾ മാത്രം',
'globalWatchlistSettingsLink': 'സജ്ജീകരണങ്ങൾ',
'markSeen-all': 'എല്ലാ സൈറ്റുകളും കണ്ടതായി അടയാളപ്പെടുത്തുക',
'markSeen-allConfirm': 'സൈറ്റുകൾ കണ്ടതായി അടയാളപ്പെടുത്താൻ തയ്യാറാണോ?',
'otherOptions': 'മറ്റ് സജ്ജീകരണങ്ങൾ',
'refresh': 'പുതുക്കുക',
'settings-error-badsites': 'അസാധുവായ സൈറ്റു(കൾ):$1',
'settings-error-nosites': 'ഒരു സൈറ്റും തിരഞ്ഞെടുത്തിട്ടില്ല',
'show-edits': 'താളിലെ തിരുത്തലുകൾ കാണിക്കുക',
'show-newPages': 'പുതിയ താളുകൾ കാണിക്കുക',
},
nl: {
'asOf': 'Sinds $1',
'changesFeed': 'Veranderde projecten',
'emptyFeed': 'Ongewijzigde projecten',
'filter-anon': 'Anonieme bewerkingen',
'filter-bot': 'Botbijdragen',
'filter-either': 'Een van beide',
'filter-not-anon': 'Slechts bewerkingen door aangemelde gebruikers',
'filter-not-bot': 'Alleen niet-botbewerkingen',
'filter-not-minor': 'Geen kleine bewerkingen',
'filter-only-anon': 'Alleen anonieme bijdragen',
'filter-only-bot': 'Alleen botbewerkingen',
'filter-only-minor': 'Alleen kleine bewerkingen',
'globalWatchlistBackLink': 'Terug naar de wereldwijde volglijst',
'globalWatchlistLink': 'Wereldwijde volglijst',
'globalWatchlistSettingsLink': 'Instellingen',
'heading-globalWatchlist': 'Wereldwijde volglijst',
'heading-globalWatchlistSettings': 'Instellingen wereldwijde volglijst',
'markSeen': 'Markeren als gezien',
'markSeen-all': 'Markeer alle projecten als gezien',
'markSeen-allConfirm': 'Weet u zeker dat u de projecten als gezien wilt markeren?',
'notify-redirecting': 'U wordt terugverwezen naar de wereldwijde volglijst...',
'notify-savingFailed': 'Instellingen niet opgeslagen. Probeer alstublieft opnieuw',
'option-confirmAllSites': 'Eis bevestiging alvorens alle projecten als gezien te markeren',
'option-fastMode': 'Snel laden met als nadeel minder gedetailleerd',
'otherOptions': 'Andere instellingen',
'refresh': 'Ververs',
'resetChanges': 'Herstel gedane veranderingen',
'rewatch': 'Bekijk opieuw',
'settings-error-anonbot': 'Anoniemen kunnen geen botbewerkingen doen',
'settings-error-anonminor': 'Anoniemen kunnen geen kleine bewerkingen doen',
'settings-error-badsites': 'Ongeldig(e) project(en): $1',
'settings-error-nochanges': 'Geen soorten verandering geselecteerd',
'settings-error-nosites': 'Geen projecten geselecteerd',
'show-edits': 'Toon paginabewerkingen',
'show-logEntries': 'Toon logboekregels',
'show-newPages': 'Toon nieuwe pagina\'s',
'siteList': 'Door gebruiker samengestelde projectenlijst',
'switch-dev': 'Overschakelen op ontwikkelversie',
'switch-stable': 'Overschakelen op productieversie',
'title-globalWatchlist': 'Wereldwijde volglijst',
'title-globalWatchlistSettings': 'Instellingen wereldwijde volglijst',
'unwatch': 'Niet meer volgen',
},
pt: {
'notify-redirecting': 'A redirecionar da lista global de \'a vigiar\'...',
'show-edits': 'Mostrar edições de página',
'show-logEntries': 'Mostrar entradas do registo',
'show-newPages': 'Mostrar novas páginas',
},
ru: {
'asOf': 'По состоянию на $1',
'changesFeed': 'Проекты с новыми изменениями',
'emptyFeed': 'Проекты без изменений',
'filter-anon': 'Анонимные правки',
'filter-bot': 'Правки ботов',
'filter-either': 'Ни то ни то',
'filter-not-anon': 'Исключить анонимные правки',
'filter-not-bot': 'Исключить правки ботов',
'filter-not-minor': 'Исключить малые правки',
'filter-only-anon': 'Только анонимные правки',
'filter-only-bot': 'Только правки ботов',
'filter-only-minor': 'Только малые правки',
'globalWatchlistBackLink': 'Назад к глобальному списку наблюдения',
'globalWatchlistLink': 'Глобальный список наблюдения',
'globalWatchlistSettingsLink': 'Настройки',
'heading-globalWatchlist': 'Глобальный список наблюдения',
'heading-globalWatchlistSettings': 'Настройки глобального СН',
'markSeen': 'Пометить прочитанным',
'markSeen-all': 'Пометить всё прочитанным',
'markSeen-allConfirm': 'Вы уверены, что хотите пометить все проекты как прочитанные?',
'notify-redirecting': 'Возврат к глобальному списку наблюдения…',
'notify-savingFailed': 'Не удалось сохранить настройки. Попробуйте ещё раз',
'option-confirmAllSites': 'Требовать подтверждения перед тем, как помечать все проекты прочитанными',
'option-fastMode': 'Загружаться быстрее, но выдавать менее детализованные результаты',
'otherOptions': 'Другие настройки',
'refresh': 'Обновить',
'resetChanges': 'Сбросить изменения',
'rewatch': 'Следить',
'settings-error-anonbot': 'Нельзя править из-под аккаунта бота анонимно',
'settings-error-anonminor': 'Нельзя помечать правки как малые анонимно',
'settings-error-badsites': 'Проекты с ошибками в названии: $1',
'settings-error-nochanges': 'Выберите хотя бы один вид изменений',
'settings-error-nosites': 'Выберите хотя бы один проект',
'show-edits': 'Показать правки на странице',
'show-logEntries': 'Показать журнал',
'show-newPages': 'Показать новые страницы',
'siteList': 'Пользовательский список проектов',
'switch-dev': 'Переключиться в меню разработчика',
'switch-stable': 'Переключиться в стабильную версию',
'title-globalWatchlist': 'Глобальный список наблюдения',
'title-globalWatchlistSettings': 'Настройки глобального СН',
'unwatch': 'Не следить',
},
zh: {
'asOf': '从$1起',
'changesFeed': '有变化的网站',
'emptyFeed': '没有变化的网站',
'filter-anon': '匿名编辑',
'filter-bot': '机器人编辑',
'filter-either': '两者任一',
'filter-not-anon': '仅由登录用户进行的编辑',
'filter-not-bot': '仅列出非机器人编辑',
}
};
});
mw.loader.using([ 'jquery.makeCollapsible', 'mediawiki.util', 'mediawiki.api', 'mediawiki.ForeignApi', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-content', 'oojs-ui.styles.icons-media', 'oojs-ui.styles.icons-moderation' ], () => {
$(document).ready(() => {
if (mw.config.get('wgNamespaceNumber') === -1) {
const page = mw.config.get('wgCanonicalSpecialPageName');
if (page === 'Blankpage') {
const page2 = mw.config.get('wgTitle').split('/');
if (page2[1]) {
if (page2[1] === 'GlobalWatchlist') {
GlobalWatchlist.init(1);
}
else if (page2[1] === 'GlobalWatchlistConfig' && mw.config.get('wgDBname') === 'metawiki') {
GlobalWatchlist.init(2);
}
}
} else if (page === 'Watchlist') {
GlobalWatchlist.init(3);
}
}
});
});
// </nowiki>