User:Maxim Masiutin/RefRenamer-core.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:Maxim Masiutin/RefRenamer-core. |
// This is a fork from [[User:Nardog/RefRenamer-core.js]] by [[User:Nardog]]
// The fork adds support the pmid (PubMed ID) attribute.
(function refRenamerCore() {
let messages = Object.assign({
loadingSource: 'Loading the source...',
loadingHtml: 'Loading HTML...',
parsing: 'Parsing wikitext...',
opening: 'Opening the diff...',
continue: 'Continue',
main: 'Main fallback stack:',
pmid: 'pmid',
lastName: 'Last name',
firstName: 'First name',
author: 'Author',
periodical: 'Periodical/website',
publisher: 'Publisher',
article: 'Article',
book: 'Book',
domain: 'Domain',
firstPhrase: 'First phrase',
lowercase: 'Lowercase',
removeDia: 'Remove diacritics',
removePunct: 'Remove punctuation',
replaceSpace: 'Replace space with:',
year: 'Year',
yearFallback: 'Fall back on any 4-digit number',
yearConvert: 'Convert to ASCII',
latinIncrement: 'Use Latin letters for increments',
latinStart: 'Start with:',
increment: 'Numeric increments start at:',
forceIncrement: 'Always insert increments',
delimiter: 'Delimiter:',
delimitConditional: 'Insert delimiters only after numerals',
removeUnreused: 'Remove unreused names',
apply: 'Apply',
reset: 'Reset',
tableName: 'Name',
tableCaption: 'References to rename',
tableRef: 'Reference',
tableNewName: 'New name',
tableAddRemove: '+/−',
keepTooltip: 'Uncheck to remove',
tableRemove: '(Remove)',
removeTooltip: 'Remove from references to rename',
otherTableCaption: 'Other named references',
notReused: '(not reused)',
addTooltip: 'Add to references to rename',
addAll: 'Add all',
resetSelection: 'Reset selection',
noNamesAlert: 'The source does not contain ref names to rename.',
noChangesError: 'No names have been modified.',
duplicatesError: 'The following names are already used or input more than once:',
summary: 'Replaced [[$1|VE ref names]] using [[$2|RefRenamer]]',
genericSummary: 'Renamed references using [[$1|RefRenamer]]'
}, window.refrenamerMessages);
let getMsg = (key, ...args) => (
messages.hasOwnProperty(key) ? mw.format(messages[key], ...args) : key
);
let notify = key => {
mw.notify(getMsg(key), {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
};
let dialog;
let encodedPn = encodeURIComponent(mw.config.get('wgPageName'));
let headers = {
'Api-User-Agent': 'RefRenamer (https://wiki.riteme.site/wiki/User:Maxim_Masiutin/RefRenamer)'
};
window.refRenamer = () => {
let data = {
isEdit: document.getElementById('wpTextbox1') &&
!$('input[name=wpSection]').val(),
refs: []
};
let promise;
let dependencies = [
'oojs-ui-windows', 'oojs-ui-widgets', 'jquery.makeCollapsible',
'oojs-ui.styles.icons-interactions', 'mediawiki.storage'
];
if (data.isEdit) {
dependencies.push('jquery.textSelection');
} else {
dependencies.push('mediawiki.api', 'user.options');
data.started = performance.now();
notify('loadingSource');
promise = $.ajax('/w/rest.php/v1/page/' + encodedPn, { headers }).then(response => {
data.wikitext = response.source;
data.revId = response.latest.id;
data.editTime = response.latest.timestamp.replace(/\D/g, '');
});
}
$.when(mw.loader.using(dependencies), promise).then(() => {
if (data.isEdit) {
data.wikitext = $('#wpTextbox1').textSelection('getContents');
}
let sources = [];
let wikitext = data.wikitext.replace(/<!--[^]*?-->/g, '');
let match;
let re = /<ref\s+name\s*=\s*(?:"\s*([^\n"]+?)\s*"|'?\s*([^\n']+?)\s*'|([^\s>]+?))\s*(?:>([^]+?)<\/ref|\/)>/gi;
while ((match = re.exec(wikitext))) {
let name = match[1] || match[2] || match[3];
let ref = data.refs.find(r => r.name === name);
if (ref) {
ref.reused = true;
} else {
data.refs.push({
name: name,
normalized: normalize(name),
isVe: /^:\d+$/.test(name)
});
}
if (data.isEdit && match[4]) {
sources.push(match[0]);
}
}
if (!data.refs.length) throw 'nonames';
if (data.isEdit) {
notify('parsing');
return $.ajax('/api/rest_v1/transform/wikitext/to/html/' + encodedPn, {
type: 'POST',
data: { wikitext: sources.join(''), body_only: true },
headers: headers
});
} else {
notify('loadingHtml');
return $.ajax('/api/rest_v1/page/html/' + encodedPn, { headers });
}
}).then(response => {
let $page = $($.parseHTML(response));
let $refs = $page.find('.mw-references:not([data-mw-group]) .mw-reference-text');
$refs.each(function (i) {
let match = this.id.match(/^mw-reference-text-cite_note-(.+)-\d+$/);
if (!match) return;
let ref = data.refs.find(r => r.normalized === match[1]);
if (!ref) return;
ref.coins = {};
ref.$ref = $refs.eq(i).clone();
ref.$ref.find('[id], [about]').addBack().removeAttr('id about');
ref.$ref.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', function () {
return mw.format(
mw.config.get('wgArticlePath'),
this.getAttribute('href').slice(2)
);
});
let coinsSpan = this.querySelector('.Z3988');
if (coinsSpan) {
new URLSearchParams(coinsSpan.title).forEach((v, k) => {
if (k.startsWith('rft.')) {
ref.coins[k.slice(4)] = v;
} else if (k === 'rft_id') {
try {
if (v.startsWith('http')) {
ref.coins.domain = new URL(v).hostname;
} else
{
let infore = /^info:([A-Za-z0-9]+)\/(.+)$/;
let infomatch;
if ((infomatch = infore.exec(v)) !== null)
{
let infokey = infomatch[1];
let infovalue = infomatch[2];
if (infokey === 'pmid')
{
ref.coins[infokey] = infokey + infovalue;
}
}
}
} catch (e) {}
}
});
}
let text = this.textContent;
if (ref.coins.date) {
let numbers = ref.coins.date.match(/\p{Nd}+/gu);
if (numbers) {
let converted = numbers.map(n => toAscii(n));
ref.coins.year = numbers[
converted.indexOf(String(Math.max(...converted)))
];
data.hasNonAsciiYear = data.hasNonAsciiYear || isNaN(ref.coins.year);
}
} else {
let yearMarch = text.match(/(?:^|\P{Nd})(\p{Nd}{4})(?!\p{Nd})/u);
if (yearMarch) {
ref.year = yearMarch[1];
data.hasNonAsciiYear = data.hasNonAsciiYear || isNaN(ref.year);
}
}
if (!ref.coins.domain) {
let link = this.querySelector('a.external');
if (link) ref.domain = link.hostname;
}
ref.phrase = (text.match(/[^\s\p{P}].*?(?=\s*(?:\p{P}|$))/u) || [])[0];
});
data.refs = data.refs.filter(ref => ref.$ref);
if (!data.refs.length) throw 'nonames';
let collator;
try {
collator = Intl.Collator(mw.config.get('wgContentLanguage') + '-u-kn-true');
} catch (e) {
collator = Intl.Collator('en-u-kn-true');
}
data.refs.sort((a, b) => collator.compare(a.name, b.name));
if (!dialog) initDialog();
dialog.open(data);
}).catch(e => {
OO.ui.alert(e === 'nonames' ? getMsg('noNamesAlert') : e.message);
}).always(() => {
mw.requestIdleCallback(() => {
let notif = $('.mw-notification-tag-refrenamer').data('mw-notification');
if (notif) notif.close();
});
});
};
let initDialog = () => {
let rtl = document.dir === 'rtl';
let left = rtl ? 'right' : 'left';
let right = rtl ? 'left' : 'right';
mw.loader.addStyleTag(`.refrenamer .oo-ui-window-body{padding:1em} .refrenamer .oo-ui-layout.oo-ui-labelElement.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline{margin:4px 0} .refrenamer-subinput{padding-${left}:2em} .refrenamer-dropdown .oo-ui-fieldLayout-header{flex-grow:0 !important} .refrenamer-dropdown .oo-ui-fieldLayout-field{width:min-content !important} .refrenamer-name, .refrenamer-ref{vertical-align:top} .refrenamer-name{max-width:6em} .refrenamer-name, .refrenamer-ref .mw-reference-text{font-size:90%;overflow-wrap:break-word} .refrenamer-newname{height:3em;position:relative;width:12em} .refrenamer-newname > .oo-ui-textInputWidget{position:absolute;top:0;${left}:0;height:100%} .refrenamer-newname .oo-ui-inputWidget-input{height:100%;resize:none;scrollbar-width:none} .refrenamer-newname .oo-ui-textInputWidget-type-text > .oo-ui-inputWidget-input{font-family:monospace,monospace;font-size:90%} .refrenamer-newname > .oo-ui-checkboxInputWidget{float:${right};margin-${right}:0;z-index:1} .refrenamer-newname .oo-ui-checkboxInputWidget ~ .oo-ui-textInputWidget > .oo-ui-inputWidget-input{padding-${right}:30px} .refrenamer-newname .oo-ui-textInputWidget:not(.oo-ui-element-hidden) + span{display:none} .refrenamer .refrenamer-addremove{padding:0;position:relative;width:32px;height:32px} .refrenamer-addremove > .oo-ui-buttonElement-frameless.oo-ui-iconElement{margin:0;position:absolute;top:0;bottom:0;left:0;right:0} .refrenamer-addremove .oo-ui-buttonElement-button{height:100%} .refrenamer-othername{max-width:32em;overflow-wrap:break-word} .refrenamer-table-wide .refrenamer-othername{max-width:16em}`);
function RefRenamerDialog(config) {
RefRenamerDialog.parent.call(this, config);
this.$element.addClass('refrenamer');
}
OO.inheritClass(RefRenamerDialog, OO.ui.ProcessDialog);
RefRenamerDialog.static.name = 'refRenamerDialog';
RefRenamerDialog.static.title = 'RefRenamer';
RefRenamerDialog.static.size = 'large';
RefRenamerDialog.static.actions = [
{
flags: ['safe', 'close']
},
{
action: 'continue',
label: getMsg('continue'),
flags: ['primary', 'progressive']
}
];
RefRenamerDialog.prototype.initialize = function () {
RefRenamerDialog.parent.prototype.initialize.apply(this, arguments);
this.mainSelect = new OO.ui.MenuTagMultiselectWidget({
options: [
{ data: 'pmid', label: getMsg('pmid') },
{ data: 'aulast', label: getMsg('lastName') },
{ data: 'aufirst', label: getMsg('firstName') },
{ data: 'au', label: getMsg('author') },
{ data: 'jtitle', label: getMsg('periodical') },
{ data: 'pub|inst', label: getMsg('publisher') },
{ data: 'atitle|title', label: getMsg('article') },
{ data: 'btitle', label: getMsg('book') },
{ data: 'domain', label: getMsg('domain') },
{ data: 'phrase', label: getMsg('firstPhrase') }
]
}).connect(this, { change: 'updateSize', reorder: 'updateSize' });
this.lowercaseCheck = new OO.ui.CheckboxInputWidget();
this.removeDiaCheck = new OO.ui.CheckboxInputWidget();
this.removePunctCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceCheck = new OO.ui.CheckboxInputWidget().on('change', selected => {
this.replaceSpaceInput.toggle(selected);
this.updateSize();
});
this.replaceSpaceInput = new OO.ui.TextInputWidget({
classes: ['refrenamer-subinput']
}).toggle();
this.yearCheck = new OO.ui.CheckboxInputWidget().on('change', selected => {
this.yearFallbackLayout.toggle(selected);
this.yearConvertLayout.toggle(selected && this.hasNonAsciiYear);
this.latinIncrementLayout.toggle(selected);
this.updateSize();
});
this.yearFallbackCheck = new OO.ui.CheckboxInputWidget();
this.yearFallbackLayout = new OO.ui.FieldLayout(this.yearFallbackCheck, {
label: getMsg('yearFallback'),
align: 'inline',
classes: ['refrenamer-subinput']
});
this.yearConvertCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertLayout = new OO.ui.FieldLayout(this.yearConvertCheck, {
label: getMsg('yearConvert'),
align: 'inline',
classes: ['refrenamer-subinput']
});
this.latinIncrementCheck = new OO.ui.CheckboxInputWidget().on('change', selected => {
this.latinStartLayout.toggle(selected);
this.updateSize();
});
this.latinIncrementLayout = new OO.ui.FieldLayout(this.latinIncrementCheck, {
label: getMsg('latinIncrement'),
align: 'inline',
classes: ['refrenamer-subinput']
});
this.latinIncrementDropdown = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: 0, label: 'a' }),
new OO.ui.MenuOptionWidget({ data: 1, label: 'b' })
]
}
});
this.latinStartLayout = new OO.ui.FieldLayout(this.latinIncrementDropdown, {
label: getMsg('latinStart'),
classes: ['refrenamer-subinput', 'refrenamer-dropdown']
}).toggle();
this.latinIncrementLayout.$element.append(this.latinStartLayout.$element);
this.incrementDropdown = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: 0, label: '0' }),
new OO.ui.MenuOptionWidget({ data: 1, label: '1' }),
new OO.ui.MenuOptionWidget({ data: 2, label: '2' })
]
}
});
this.forceIncrementCheck = new OO.ui.CheckboxInputWidget();
this.delimiterInput = new OO.ui.TextInputWidget();
this.delimitConditionalCheck = new OO.ui.CheckboxInputWidget();
this.removeUnreusedCheck = new OO.ui.CheckboxInputWidget();
this.applyButton = new OO.ui.ButtonInputWidget({
label: getMsg('apply'),
flags: ['progressive'],
type: 'submit'
}).connect(this, { click: 'applyConfig' });
this.resetButton = new OO.ui.ButtonWidget({
label: getMsg('reset')
}).connect(this, { click: 'setConfig' });
this.form = new OO.ui.FormLayout({
items: [
new OO.ui.FieldLayout(this.mainSelect, {
label: getMsg('main'),
align: 'top'
}),
new OO.ui.FieldLayout(this.lowercaseCheck, {
label: getMsg('lowercase'),
align: 'inline'
}),
new OO.ui.FieldLayout(this.removeDiaCheck, {
label: getMsg('removeDia'),
align: 'inline'
}),
new OO.ui.FieldLayout(this.removePunctCheck, {
label: getMsg('removePunct'),
align: 'inline'
}),
new OO.ui.FieldLayout(this.replaceSpaceCheck, {
label: getMsg('replaceSpace'),
align: 'inline'
}),
this.replaceSpaceInput,
new OO.ui.FieldLayout(this.yearCheck, {
label: getMsg('year'),
align: 'inline'
}),
this.yearFallbackLayout,
this.yearConvertLayout,
this.latinIncrementLayout,
new OO.ui.FieldLayout(this.incrementDropdown, {
label: getMsg('increment'),
classes: ['refrenamer-dropdown']
}),
new OO.ui.FieldLayout(this.forceIncrementCheck, {
label: getMsg('forceIncrement'),
align: 'inline'
}),
new OO.ui.FieldLayout(this.delimiterInput, {
label: getMsg('delimiter'),
align: 'top'
}),
new OO.ui.FieldLayout(this.delimitConditionalCheck, {
label: getMsg('delimitConditional'),
align: 'inline'
}),
new OO.ui.FieldLayout(this.removeUnreusedCheck, {
label: getMsg('removeUnreused'),
align: 'inline'
}),
this.applyButton,
this.resetButton
]
});
this.$tbody = $('<tbody>');
this.$otherTbody = $('<tbody>');
this.$otherTable = $('<table>').addClass('wikitable').append(
$('<caption>').text(getMsg('otherTableCaption')),
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$otherTbody
).on('afterExpand.mw-collapsible', function () {
this.classList.add('refrenamer-table-wide');
}).on('afterCollapse.mw-collapsible', function () {
this.classList.toggle(
'refrenamer-table-wide',
!!this.querySelector('.mw-collapsible:not(.mw-collapsed)')
);
});
this.addAllButton = new OO.ui.ButtonWidget({
flags: ['progressive'],
label: getMsg('addAll')
}).on('click', () => {
this.refs.forEach(ref => {
if (!ref.renamable) {
this.addCandidate(ref, true);
}
});
});
this.resetSelectionButton = new OO.ui.ButtonWidget({
label: getMsg('resetSelection')
}).on('click', () => {
this.refs.forEach(ref => {
if (ref.renamable) {
if (!ref.isVe) this.removeCandidate(ref);
} else {
if (ref.isVe) this.addCandidate(ref, true);
}
});
});
this.$body.append(
this.form.$element,
$('<table>').addClass('wikitable').append(
$('<caption>').text(getMsg('tableCaption')),
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableNewName')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$tbody
),
this.$otherTable,
this.addAllButton.$element,
this.resetSelectionButton.$element
);
this.defaults = {
main: ['aulast', 'aufirst', 'au', 'jtitle', 'pub|inst', 'phrase'],
lowercase: false,
removeDia: false,
removePunct: false,
replaceSpace: false,
year: true,
yearFallback: false,
yearConvert: true,
latinIncrement: false,
increment: 2,
forceIncrement: false,
delimiter: '-',
delimitConditional: false,
removeUnreused: true
};
};
RefRenamerDialog.prototype.getConfig = function () {
return {
main: this.mainSelect.getValue(),
lowercase: this.lowercaseCheck.isSelected(),
removeDia: this.removeDiaCheck.isSelected(),
removePunct: this.removePunctCheck.isSelected(),
replaceSpace: this.replaceSpaceCheck.isSelected() &&
this.replaceSpaceInput.getValue(),
year: this.yearCheck.isSelected(),
yearFallback: this.yearFallbackCheck.isSelected(),
yearConvert: this.yearConvertCheck.isSelected(),
latinIncrement: this.latinIncrementCheck.isSelected() &&
this.latinIncrementDropdown.getMenu().findSelectedItem().getData(),
increment: this.incrementDropdown.getMenu().findSelectedItem().getData(),
forceIncrement: this.forceIncrementCheck.isSelected(),
delimiter: this.delimiterInput.getValue(),
delimitConditional: this.delimitConditionalCheck.isSelected(),
removeUnreused: this.removeUnreusedCheck.isSelected()
};
};
RefRenamerDialog.prototype.setConfig = function (config) {
config = Object.assign({}, this.defaults, config);
this.mainSelect.setValue(config.main);
this.lowercaseCheck.setSelected(config.lowercase);
this.removeDiaCheck.setSelected(config.removeDia);
this.removePunctCheck.setSelected(config.removePunct);
this.replaceSpaceCheck.setSelected(typeof config.replaceSpace === 'string');
this.replaceSpaceInput.setValue(
this.replaceSpaceCheck.isSelected() ? config.replaceSpace : '-'
);
this.yearCheck.setSelected(config.year);
this.yearFallbackCheck.setSelected(config.yearFallback);
this.yearConvertCheck.setSelected(config.yearConvert);
this.latinIncrementCheck.setSelected(typeof config.latinIncrement === 'number');
this.latinIncrementDropdown.getMenu().selectItemByData(
this.latinIncrementCheck.isSelected() ? config.latinIncrement : 0
);
this.incrementDropdown.getMenu().selectItemByData(config.increment);
this.forceIncrementCheck.setSelected(config.forceIncrement);
this.delimiterInput.setValue(config.delimiter);
this.delimitConditionalCheck.setSelected(config.delimitConditional);
this.removeUnreusedCheck.setSelected(config.removeUnreused);
};
RefRenamerDialog.prototype.applyConfig = function () {
let config = this.getConfig();
config.processed = this.refs.filter(ref => !ref.renamable)
.map(ref => ref.normalized);
this.refs.forEach(ref => {
if (ref.renamable) {
this.applyConfigTo(ref, config);
}
});
};
RefRenamerDialog.prototype.applyConfigTo = function (ref, config) {
config = config || this.getConfig();
if (!config.processed) {
config.processed = this.refs.filter(r => r !== ref).map(r => (
r.renamable
? normalize(r.input.getValue()) || r.normalized
: r.normalized
));
}
ref.input.setValue('');
if (!ref.reused) {
ref.keepCheck.setSelected(!config.removeUnreused);
if (config.removeUnreused) return;
}
let s;
config.main.some(key => key.split('|').some(subkey => {
s = ref.coins[subkey] || ref[subkey];
return s;
}));
if (!s) {
config.processed.push(ref.normalized);
return;
}
if (config.lowercase) {
s = s.toLowerCase();
}
if (config.removeDia) {
s = s.normalize('NFD').replace(/\p{Mn}/gu, '');
}
if (config.removePunct) {
s = s.replace(/\p{P}/gu, '');
}
if (typeof config.replaceSpace === 'string') {
s = s.replace(/\s+/g, config.replaceSpace);
}
let useLatin;
let year;
if (!s.startsWith('pmid'))
{
year = config.year &&
(ref.coins.year || config.yearFallback && ref.year);
}
if (year) {
if (config.yearConvert) year = toAscii(year);
let delimiter = config.delimitConditional && /\P{Nd}$/u.test(s)
? ''
: config.delimiter;
s += delimiter + year;
useLatin = typeof config.latinIncrement === 'number';
}
let unsuffixed = s;
let normalized = normalize(s);
let delimiter = useLatin || (
config.delimitConditional && /\P{Nd}$/u.test(unsuffixed)
) ? '' : config.delimiter;
let increment = useLatin ? config.latinIncrement : config.increment;
let incrementStr = useLatin ? toLatin(increment) : increment;
let forceIncrement = config.forceIncrement;
while (forceIncrement || config.processed.includes(normalized)) {
s = unsuffixed + delimiter + incrementStr;
normalized = normalize(s);
increment++;
incrementStr = useLatin ? toLatin(increment) : increment;
forceIncrement = false;
}
config.processed.push(normalized);
ref.input.setValue(s);
};
RefRenamerDialog.prototype.addCandidate = function (ref, isMove) {
ref.renamable = true;
if (this.refs.every(r => r.renamable)) {
this.$otherTable.hide();
this.addAllButton.toggle(false);
} else {
this.$otherTable.show();
this.addAllButton.toggle(true);
}
ref.$otherRow.hide();
if (isMove) {
ref.$otherRow.find('.mw-collapsible')
.data('mw-collapsible').collapse();
}
if (ref.removeButton) {
this.applyConfigTo(ref);
ref.$row.children('.refrenamer-ref').append(ref.$ref);
ref.$row.show();
return;
}
ref.input = new OO.ui.MultilineTextInputWidget({
allowLinebreaks: false,
placeholder: ref.name
}).connect(this, { enter: ['executeAction', 'continue'] });
ref.input.$input.on({ focus: onFocus, blur: onBlur });
if (!ref.reused) {
ref.keepCheck = new OO.ui.CheckboxInputWidget({
selected: true,
title: getMsg('keepTooltip')
}).connect(ref.input, { change: 'toggle' });
ref.keepCheck.$input.on({ focus: onFocus, blur: onBlur });
}
if (isMove) this.applyConfigTo(ref);
ref.removeButton = new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
icon: 'subtract',
title: getMsg('removeTooltip')
}).connect(this, { click: ['removeCandidate', ref] });
ref.$row.append(
$('<td>').addClass('refrenamer-name').text(ref.name),
$('<td>').addClass('refrenamer-ref mw-parser-output').append(ref.$ref),
$('<td>').addClass('refrenamer-newname').append(
ref.keepCheck && ref.keepCheck.$element,
ref.input.$element,
ref.keepCheck && $('<span>').text(getMsg('tableRemove'))
),
$('<td>').addClass('refrenamer-addremove').append(ref.removeButton.$element)
).show();
};
RefRenamerDialog.prototype.removeCandidate = function (ref) {
ref.renamable = false;
this.$otherTable.show();
this.addAllButton.toggle(true);
ref.$row.hide();
if (ref.addButton) {
ref.$otherRow.find('.mw-collapsible-content').append(ref.$ref);
ref.$otherRow.show();
return;
}
ref.addButton = new OO.ui.ButtonWidget({
flags: ['progressive'],
framed: false,
icon: 'add',
title: getMsg('addTooltip')
}).connect(this, { click: ['addCandidate', ref, true] });
ref.$otherRow.append(
$('<td>').addClass('refrenamer-othername').text(ref.name).append(!ref.reused && [
' ',
$('<small>').text(getMsg('notReused'))
]),
$('<td>').addClass('refrenamer-ref mw-parser-output').append(
$('<div>').addClass('mw-collapsible mw-collapsed')
.append(ref.$ref).makeCollapsible()
),
$('<td>').addClass('refrenamer-addremove').append(ref.addButton.$element)
).show();
};
RefRenamerDialog.prototype.getSetupProcess = function (data) {
Object.assign(this, data);
this.$tbody.empty();
this.$otherTbody.empty();
this.refs.forEach(ref => {
ref.$row = $('<tr>').appendTo(this.$tbody);
ref.$otherRow = $('<tr>').appendTo(this.$otherTbody);
this[ref.isVe ? 'addCandidate' : 'removeCandidate'](ref);
});
this.resetSelectionButton.toggle(this.refs.some(ref => !ref.isVe));
this.setConfig(mw.storage.getObject('refrenamer'));
this.applyConfig();
this.$body.scrollTop(0);
return RefRenamerDialog.super.prototype.getSetupProcess.call(this, data);
};
RefRenamerDialog.prototype.getActionProcess = function (action) {
return RefRenamerDialog.super.prototype.getActionProcess.call(this, action).next(function () {
if (action !== 'continue') return;
let removed = [];
let modified = this.refs.filter(ref => {
if (!ref.renamable) return;
if (ref.keepCheck && !ref.keepCheck.isSelected()) {
removed.push(ref);
return;
}
ref.newName = ref.input.getValue().replace(/^[\s_]+|[\s_]+$/g, '');
ref.newNormalized = normalize(ref.newName);
return ref.newNormalized;
});
if (!modified.length && !removed.length) {
return new OO.ui.Error(getMsg('noChangesError'), { recoverable: false });
}
let duplicates = new Set(
this.refs.map(ref => (
ref.renamable
? ref.newNormalized || ref.normalized
: ref.normalized
)).filter((name, i, arr) => arr.indexOf(name) !== i)
);
if (duplicates.size) {
return new OO.ui.Error($([
document.createTextNode(getMsg('duplicatesError')),
$('<ul>').append([...duplicates].map(n => $('<li>').text(n)))[0]
]), { recoverable: false });
}
this.close();
let subs = {};
[...modified, ...removed].forEach(ref => {
subs[ref.name] = ref.newName;
});
let newText = replace(this.wikitext, subs);
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
let summary = [...modified, ...removed].every(ref => ref.isVe) ? getMsg(
'summary',
iw + 'Wikipedia:VisualEditor/Named references',
iw + 'User:Maxim Masiutin/RefRenamer'
) : getMsg('genericSummary', iw + 'User:Maxim Masiutin/RefRenamer');
if (this.isEdit) {
$('#wpTextbox1').textSelection('setContents', newText);
if (document.documentElement.classList.contains('ve-active')) {
ve.init.target.once('showChanges', () => {
ve.init.target.saveDialog.reviewModeButtonSelect.selectItemByData('source');
ve.init.target.saveDialog.setEditSummary(summary);
});
ve.init.target.showSaveDialog('review');
} else {
$('#wpSummary').textSelection('setContents', summary);
$('#wpDiff').trigger('click');
}
} else {
new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
inprop: 'watched',
curtimestamp: 1,
formatversion: 2
}).always(response => {
let formData = [];
let timestamp = response && response.curtimestamp;
if (timestamp) {
let elapsed = performance.now() - this.started;
let time = new Date(new Date(timestamp).getTime() - elapsed)
.toISOString().slice(0, -5).replace(/\D/g, '');
formData.push(['wpStarttime', time]);
}
formData.push(
['wpEdittime', this.editTime],
['editRevId', this.revId],
['wpTextbox1', newText],
['wpSummary', summary],
['wpMinoredit', '']
);
let page = (((response || {}).query || {}).pages || [])[0] || {};
if (page.watched ||
Number(mw.user.options.get('watchdefault')) === 1
) {
formData.push(['wpWatchthis', '']);
if (page.watchlistexpiry) {
formData.push(['wpWatchlistExpiry', page.watchlistexpiry]);
}
}
formData.push(['wpDiff', ''], ['wpUltimateParam', 1]);
$('<form>').attr({
method: 'post',
action: mw.util.getUrl(null, { action: 'submit' }),
enctype: 'multipart/form-data'
}).append(
formData.map(([n, v]) => $('<input>').attr({
name: n,
type: 'hidden'
}).val(v))
).appendTo(document.body).trigger('submit').remove();
});
notify('opening');
}
mw.requestIdleCallback(() => {
let customized = Object.entries(this.getConfig())
.filter(([k, v]) => String(v) !== String(this.defaults[k]));
if (customized.length) {
mw.storage.setObject('refrenamer', Object.fromEntries(customized), 7776000);
} else {
mw.storage.remove('refrenamer');
}
});
}, this);
};
RefRenamerDialog.prototype.hideErrors = function () {
RefRenamerDialog.super.prototype.hideErrors.call(this);
this.actions.setAbilities({ continue: true });
};
dialog = new RefRenamerDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
};
let replace = (text, subs) => text.replace(
/<(ref)\s+(name)\s*=\s*(?:"\s*([^\n"]+?)\s*"|'\s*([^\n']+?)\s*'|([^\s>]+?))(\s*\/?)>/gi,
(s, ref, attr, name1, name2, name3, slash) => {
let name = name1 || name2 || name3;
return subs.hasOwnProperty(name)
? subs[name]
? `<${ref} ${attr}="${subs[name]}"${slash}>`
: `<${ref}>`
: s;
}
);
let normalize = s => s.replace(/[\s_]+/g, '_').replace(/^_+|_+$/g, '');
let toAscii = s => s.replace(/\D/g, c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
});
let toLatin = n => {
let s = '';
do {
s = String.fromCharCode(97 + (n % 26)) + s;
n = Math.floor(n / 26) - 1;
} while (n >= 0);
return s;
};
let onFocus = () => {
dialog.refs.forEach(ref => {
if (ref.renamable) {
ref.input.setTabIndex(1);
if (ref.keepCheck) ref.keepCheck.setTabIndex(1);
}
});
};
let onBlur = () => {
dialog.refs.forEach(ref => {
if (ref.renamable) {
ref.input.setTabIndex(0);
if (ref.keepCheck) ref.keepCheck.setTabIndex(0);
}
});
};
window.refRenamer();
}());