User:Nardog/RefRenamer-core.js

(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:', 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: 'Append Latin letters on collision', increment: 'Collision resolution:', incrementExample: 'Example', incrementExamples: '$1, $2...', 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: '±', reapplyTooltip: 'Reapply current options', propsTooltip: 'View/insert properties', keepTooltip: 'Uncheck to remove', tableRemove: '(Remove)', removeTooltip: 'Remove from references to rename', otherTableCaption: 'Other named references', notReused: '(not reused)', expand: 'Expand', collapse: 'Collapse', 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.', numericError: 'The following names are invalid as they consist only of numerals:', duplicatesError: 'The following names are already used or input more than once:', templatesWarn: 'Ref names in the following templates will not be replaced:', invalidWarn: 'The following names have been ignored because it could not be determined which references correspond to them:', summary: 'Replaced VE ref names using RefRenamer', genericSummary: 'Renamed references using RefRenamer' }, window.refrenamerMessages);	let getMsg = (key, ...args) => ( messages.hasOwnProperty(key) ? mw.format(messages[key], ...args) : key );	let notif;	let notify = key => {		mw.notify(getMsg(key), { autoHideSeconds: 'long', tag: 'refrenamer' }).then(n => { notif = n;		});	};	let dialog;	class Ref {		constructor(name, normalized) {			this.name = name;			this.names = new Set([name]);			this.normalized = normalized;			this.isVe = /^:\d+$/.test(name);			this.isAuto = this.isVe || /^auto(?:generated)?\d*$/.test(name);		}		initProps {			this.props = {};			let coinsSpan = this.$ref[0].querySelector('.Z3988');			if (coinsSpan) {				new URLSearchParams(coinsSpan.title).forEach((v, k) => { if (k.startsWith('rft.')) { this.props[k.slice(4)] = v;					} else if (k === 'rft_id') { if (/^https?:/.test(v)) { if (this.props.domain) return; try { let url = new URL(v); this.props.domain = url.hostname; } catch (e) {} } else { let match = v.match(/^info:([^\/]+)\/(.+)$/); if (match) { this.props[match[1]] = match[2]; }						}					}				});			}			let text = this.$ref.text;			if (this.props.date) {				let numbers = this.props.date.match(/\p{Nd}+/gu);				if (numbers) {					let converted = numbers.map(n => toAscii(n));					let year = String(Math.max(...converted));					let original = numbers[converted.indexOf(year)];					this.props.year = original;					if (original !== year) {						this.props.yearAscii = year;					}				}			} else {				let match = text.match(/(?:^|\P{Nd})(\p{Nd}{4})(?!\p{Nd})/u);				if (match) {					this.props.textYear = match[1];					let ascii = toAscii(match[1]);					if (ascii !== match[1]) {						this.props.textYearAscii = ascii;					}				}			}			let link = this.$ref[0].querySelector('a.external');			if (link && link.hostname !== this.props.domain) {				this.props.linkDomain = link.hostname;			}			let match = text.match(/[^\s\p{P}].*?(?=\s*(?:\p{P}|$))/u);			if (match) {				this.props.phrase = match[0]; }			Object.freeze(this.props); }		initRows { let rowClass = !this.reused && 'refrenamer-unreused'; this.$row = $(' ').addClass(rowClass).appendTo(dialog.$tbody); this.$otherRow = $(' ').addClass(rowClass).appendTo(dialog.$otherTbody); this.nameCell = $(' ').addClass('refrenamer-name') .append($(' ').text(this.name))[0]; this.refCell = $(' ').addClass('refrenamer-ref mw-parser-output') .append(this.$ref)[0]; this.moveButton = new OO.ui.ButtonWidget({				framed: false,				invisibleLabel: true			}).connect(dialog, { click: ['toggleActive', this] }); this.moveCell = $(' ').addClass('refrenamer-addremove') .append(this.moveButton.$element)[0]; }		setActive(active) { if (active === this.active) return; this.active = active; this[active ? '$otherRow' : '$row'].addClass('refrenamer-hidden'); let tooltip = getMsg(active ? 'removeTooltip' : 'addTooltip'); this.moveButton .setFlags({ destructive: active, progressive: !active }) .setIcon(active ? 'subtract' : 'add') .setLabel(tooltip).setTitle(tooltip); this[active ? 'initInput' : 'initToggle']; this[active ? '$row' : '$otherRow'] .prepend(this.nameCell, this.refCell) .append(this.moveCell) .removeClass('refrenamer-hidden'); }		initInput { if (this.input) return; this.input = new OO.ui.MultilineTextInputWidget({				allowLinebreaks: false,				placeholder: this.name,				spellcheck: false			}).connect(dialog, { enter: ['executeAction', 'continue'] }); this.reapplyButton = new OO.ui.ButtonWidget({				classes: ['refrenamer-reapplybutton'],				framed: false,				icon: 'undo',				invisibleLabel: true,				label: getMsg('reapplyTooltip')			}).connect(dialog, {				click: ['applyConfig', [this], true]			}).connect(this.input, { click: 'focus' }); this.propsButton = new OO.ui.ButtonMenuSelectWidget({				classes: ['refrenamer-propsbutton'],				clearOnSelect: true,				framed: false,				icon: 'downTriangle',				invisibleLabel: true,				label: getMsg('propsTooltip'),				menu: {					$floatableContainer: this.input.$element,					horizontalPosition: 'end',					width: '16em'				}			}); this.propsButton.getMenu.addItems(				Object.entries(this.props).map(([k, v]) => { let option = new OO.ui.MenuOptionWidget({						label: v,						title: v					}); option.$label.attr('data-refrenamer', k); return option; })			).connect(this, { choose: 'onPropChoose' }); if (!this.reused) { this.keepCheck = new OO.ui.CheckboxInputWidget({					classes: ['refrenamer-keepcheck'],					invisibleLabel: true,					label: getMsg('keepTooltip')				}).connect(this, { change: 'onKeepChange' }); }			$(' ').addClass('refrenamer-newname').append(				this.keepCheck && this.keepCheck.$element,				this.input.$element,				this.reapplyButton.$element,				this.propsButton.$element			).appendTo(this.$row); }		initToggle { if (this.$toggle) return; this.$toggle = $(' ').attr({				class: 'refrenamer-toggle oo-ui-icon-expand',				title: getMsg('expand')			}).on('click', onToggle).prependTo(this.refCell); }		onPropChoose(item) { this.input.insertContent(item.getTitle); }		onKeepChange(selected) { this.$row.toggleClass('refrenamer-kept', selected); if (selected && !this.input.getValue) { dialog.applyConfig([this], true); }		}		setupCollapsible(width) { if (this.collapsible && Math.abs(width - this.refWidth) < 10) return; this.refCell.classList.remove('refrenamer-collapsible'); this.collapsible = this.$ref[0].scrollHeight > this.$ref[0].clientHeight; this.refCell.classList.toggle('refrenamer-collapsible', this.collapsible); this.refWidth = width; }	}	window.refRenamer = => { let encodedPn = encodeURIComponent(mw.config.get('wgPageName')); let headers = { 'Api-User-Agent': 'RefRenamer (https://en.wikipedia.org/wiki/User:Nardog/RefRenamer)' };		let dependencies = [ 'mediawiki.storage', 'mediawiki.Title', 'oojs-ui-windows', 'oojs-ui-widgets', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content', 'oojs-ui.styles.icons-editing-core' ];		let data = { isEdit: !!document.getElementById('wpTextbox1') && !$('input[name=wpSection]').val, refs: [], templates: new Set, invalid: new Set };		let promise; 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 wikitext = data.wikitext.replace(//g, '');			let match;			let re = /]+?))\s*\/?>/gi;			while ((match = re.exec(wikitext))) {				let name = match[1] || match[2] || match[3];				let normalized = normalize(name);				let ref = data.refs.find(r => r.normalized === normalized);				if (ref) {					ref.reused = true;					ref.names.add(name);				} else {					data.refs.push(new Ref(name, normalized));				}			}			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: data.wikitext, body_only: true },					headers: headers				}); } else { notify('loadingHtml'); return $.ajax('/api/rest_v1/page/html/' + encodedPn, { headers }); }		}).then(response => { let numbers = new Set; let $page = $($.parseHTML(response)); $page.find(				'.mw-references:not([data-mw-group]) .mw-reference-text'			).each(function {				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;				if (ref.$ref) {					data.invalid.add(ref.name);					ref.invalid = true;					return;				}				ref.$ref = $(this);				ref.reused = ref.reused || ( ref.$ref.prev('span[rel="mw:referencedBy"]').children.length > 1 );				ref.$ref.remove.find('[id], [about]').addBack.removeAttr('id about');				ref.$ref.find('a').attr('target', '_blank')					.filter('[href^="./"]').attr('href', (_, href) => (						mw.format(mw.config.get('wgArticlePath'), href.slice(2))					));				numbers.add(match[2]);			}); $page.find(				'.mw-ref[typeof~="mw:Transclusion"], [typeof~="mw:Transclusion"] .mw-ref'			).filter(function {				if (!/^\[\d+\]$/.test(this.textContent)) return;				let match = this.id.match(/_(\d+)-\d+$/);				return match && numbers.has(match[1]);			}).closest('[typeof~="mw:Transclusion"]').each(function  {				try {					data.templates.add( JSON.parse(this.dataset.mw).parts[0].template.target.href .replace(/^.\/Template:/, '') );				} catch (e) {}			}); data.refs = data.refs.filter(ref => ref.$ref && !ref.invalid); 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( => { if (notif) { notif.close; notif = null; }		});	};	let initDialog = => {		let rtl = document.dir === 'rtl';		let left = rtl ? 'right' : 'left';		let right = rtl ? 'left' : 'right';		mw.loader.addStyleTag(`.refrenamer .oo-ui-tabOptionWidget > .oo-ui-labelElement-label::after{content:" (" attr(data-refrenamer) ")"} .refrenamer-split{columns:2} .refrenamer-split .oo-ui-fieldLayout{break-inside:avoid} .refrenamer-split, .refrenamer .oo-ui-layout.oo-ui-labelElement:nth-child(n+2), .refrenamer .oo-ui-fieldLayout-align-inline .oo-ui-labelElement-label + *{margin:4px 0} .refrenamer .oo-ui-textInputWidget > .oo-ui-inputWidget-input{font-family:monospace,monospace} .refrenamer .wikitable{margin-bottom:0;overflow-wrap:break-word;width:100%} .refrenamer-maintable .refrenamer-name, .refrenamer .mw-reference-text, .refrenamer-othertable .refrenamer-name::after{font-size:90%} .refrenamer-unreused > .refrenamer-name::after{content:"${getMsg('notReused')}";display:inline-block} .refrenamer-unreused > .refrenamer-name > span::after{content:" "} .refrenamer-hidden, .refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname > :not(.refrenamer-keepcheck), .refrenamer-maintable .refrenamer-toggle, :not(.refrenamer-collapsible) > .refrenamer-toggle{display:none} .refrenamer-name, .refrenamer-ref{vertical-align:top} .refrenamer-maintable .refrenamer-name{max-width:6em} .refrenamer-ref{line-height:normal} .refrenamer-newname{height:3em;position:relative;width:12em} .refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname::after{content:"${getMsg('tableRemove')}"} .refrenamer-newname > .oo-ui-textInputWidget, .refrenamer-addremove > .oo-ui-buttonElement-frameless.oo-ui-iconElement, .refrenamer-toggle{margin:0;position:absolute;top:0;bottom:0;left:0;right:0} .refrenamer-newname textarea{height:100%;resize:none} .refrenamer-keepcheck{position:absolute;${right}:4px;top:50%;transform:translateY(-50%);margin:0;z-index:1} .refrenamer-keepcheck + .oo-ui-textInputWidget > textarea{padding-${right}:28px} .refrenamer-newname .oo-ui-buttonElement-frameless{position:absolute;${right}:0;bottom:0;margin:0} .refrenamer .refrenamer-reapplybutton{${right}:24px} .refrenamer-newname:not(:hover):not(:focus-within) > .oo-ui-buttonElement-frameless{opacity:0} .refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button{min-width:24px;min-height:24px;padding:0} .refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon{background-size:16px 16px;left:0;right:0;margin:auto} .refrenamer-propsbutton .oo-ui-menuOptionWidget{font-size:85%;padding:2px 8px} .refrenamer-propsbutton .oo-ui-menuOptionWidget > .oo-ui-labelElement-label::before{content:attr(data-refrenamer);color:var(--color-subtle,#54595d);float:${right}} .refrenamer .refrenamer-addremove{padding:0;position:relative;width:32px;height:32px} .refrenamer-addremove .oo-ui-buttonElement-button{height:100%} .refrenamer-othertable{margin:0} .refrenamer-othertable .refrenamer-name{max-width:16em} .refrenamer-othertable .refrenamer-ref{position:relative} .refrenamer-othertable .refrenamer-collapsible{padding-right:20px} .refrenamer-toggle{width:100%;background-position:center ${right} 4px;background-repeat:no-repeat;background-size:12px 12px;background-color:transparent;border:none;cursor:pointer;z-index:1} .refrenamer-expanded > .refrenamer-toggle{width:20px;${left}:auto} .refrenamer-toggle:hover{background-color:var(--background-color-button-quiet--hover,rgba(0,24,73,0.027))} .refrenamer-othertable .mw-reference-text{overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical} .refrenamer-othertable .refrenamer-collapsible.refrenamer-expanded > .mw-reference-text{-webkit-line-clamp:unset}`); 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'], modes: ['main', 'mainReset', 'other', 'otherReset'] },			{				action: 'continue', flags: ['primary', 'progressive'], label: getMsg('continue'), modes: ['main', 'mainReset', 'other', 'otherReset'] },			{				action: 'addAll', flags: ['progressive'], label: getMsg('addAll'), modes: ['other', 'otherReset'] },			{				action: 'resetSelection', label: getMsg('resetSelection'), modes: ['main', 'other'] }		];		RefRenamerDialog.prototype.initialize = function { RefRenamerDialog.parent.prototype.initialize.apply(this, arguments); this.index = new OO.ui.IndexLayout({				autoFocus: false			}).addTabPanels([				new OO.ui.TabPanelLayout('main', { label: getMsg('tableCaption') }),				new OO.ui.TabPanelLayout('other', { label: getMsg('otherTableCaption') })			]).on('set', => {				let mode = this.index.getCurrentTabPanelName;				if (this.refs.every(ref => ref.active === ref.isAuto)) {					mode += 'Reset';				}				this.actions.setMode(mode);				this.updateSize;				this.setupCollapsibles;			}); this.warning = new OO.ui.MessageWidget({				showClose: true,				type: 'warning'			}).toggle.connect(this, { close: 'updateSize' }); this.mainSelect = new OO.ui.MenuTagMultiselectWidget({				input: { autocomplete: false },				options: [					{ 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|linkDomain', 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; this.replaceSpaceLayout = new OO.ui.FieldLayout(this.replaceSpaceCheck, {				align: 'inline',				label: getMsg('replaceSpace')			}); this.replaceSpaceInput = new OO.ui.TextInputWidget({				autocomplete: false			}).toggle.connect(this, { toggle: 'updateSize' }); this.replaceSpaceCheck.connect(this.replaceSpaceInput, { change: 'toggle' }); this.replaceSpaceLayout.$header.append(this.replaceSpaceInput.$element); this.yearCheck = new OO.ui.CheckboxInputWidget; this.yearLayout = new OO.ui.FieldLayout(this.yearCheck, {				align: 'inline',				label: getMsg('year')			}); this.yearFallbackCheck = new OO.ui.CheckboxInputWidget; this.yearConvertCheck = new OO.ui.CheckboxInputWidget; this.yearConvertLayout = new OO.ui.FieldLayout(this.yearConvertCheck, {				align: 'inline',				label: getMsg('yearConvert')			}); this.latinIncrementCheck = new OO.ui.CheckboxInputWidget; this.yearSubLayout = new OO.ui.FieldsetLayout({				items: [					new OO.ui.FieldLayout(this.yearFallbackCheck, { align: 'inline', label: getMsg('yearFallback') }),					this.yearConvertLayout,					new OO.ui.FieldLayout(this.latinIncrementCheck, { align: 'inline', label: getMsg('latinIncrement') })				]			}).toggle.connect(this, { toggle: 'updateSize' }); this.yearCheck.connect(this.yearSubLayout, { change: 'toggle' }); this.yearLayout.$header.append(this.yearSubLayout.$element); this.incrementDropdown = new OO.ui.DropdownWidget({				menu: {					items: [						new OO.ui.MenuOptionWidget({ data: [1, false] }),						new OO.ui.MenuOptionWidget({ data: [2, false] }),						new OO.ui.MenuOptionWidget({ data: [0, true] }),						new OO.ui.MenuOptionWidget({ data: [1, true] })					]				}			}); this.incrementDropdown.updateLabels = => { let example = getMsg('incrementExample'); let delimiter = this.delimitConditionalCheck.isSelected ? ''					: this.delimiterInput.getValue; this.incrementDropdown.getMenu.getItems.forEach(item => {					let [start, incrementAll] = item.getData;					let label = getMsg( 'incrementExamples', example + (incrementAll ? delimiter + start : ''), example + delimiter + (start + Number(incrementAll)) );					item.setLabel(label);					if (item.isSelected) {						this.incrementDropdown.setLabel(label);					}				}); };			this.delimiterInput = new OO.ui.TextInputWidget({				autocomplete: false			}).connect(this.incrementDropdown, { change: 'updateLabels' }); this.delimitConditionalCheck = new OO.ui.CheckboxInputWidget .connect(this.incrementDropdown, { change: 'updateLabels' }); this.removeUnreusedCheck = new OO.ui.CheckboxInputWidget; this.applyButton = new OO.ui.ButtonInputWidget({				flags: ['primary', 'progressive'],				label: getMsg('apply'),				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: [					this.warning,					new OO.ui.FieldLayout(this.mainSelect, { align: 'top', label: getMsg('main') }),					new OO.ui.FieldsetLayout({ classes: ['refrenamer-split'], items: [ new OO.ui.FieldLayout(this.lowercaseCheck, {								align: 'inline',								label: getMsg('lowercase')							}), new OO.ui.FieldLayout(this.removeDiaCheck, {								align: 'inline',								label: getMsg('removeDia')							}), new OO.ui.FieldLayout(this.removePunctCheck, {								align: 'inline',								label: getMsg('removePunct')							}), this.replaceSpaceLayout, this.yearLayout, new OO.ui.FieldLayout(this.incrementDropdown, {								align: 'top',								label: getMsg('increment')							}), new OO.ui.FieldLayout(this.delimiterInput, {								align: 'top',								label: getMsg('delimiter')							}), new OO.ui.FieldLayout(this.delimitConditionalCheck, {								align: 'inline',								label: getMsg('delimitConditional')							}) ]					}),					new OO.ui.FieldLayout(this.removeUnreusedCheck, { align: 'inline', label: getMsg('removeUnreused') }),					this.applyButton,					this.resetButton				]			}); this.$tbody = $(' '); this.$table = $(' ').addClass('wikitable refrenamer-maintable').append(				$(' ').append( $(' ').append(						$(' ').text(getMsg('tableName')),						$(' ').text(getMsg('tableRef')),						$(' ').text(getMsg('tableNewName')),						$(' ').text(getMsg('tableAddRemove'))					) ),				this.$tbody			); this.index.getTabPanel('main').$element.append(				this.form.$element, this.$table			); this.$otherTbody = $(' '); this.index.getTabPanel('other').$element.append(				$(' ').addClass('wikitable refrenamer-othertable').append( $(' ').append(						$(' ').append( $(' ').text(getMsg('tableName')), $(' ').text(getMsg('tableRef')), $(' ').text(getMsg('tableAddRemove')) )					),					this.$otherTbody )			);			this.$body.append(this.index.$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: true, increment: 2, incrementAll: false, delimiter: '-', delimitConditional: false, removeUnreused: true };		};		RefRenamerDialog.prototype.getConfig = function { let incrementData = this.incrementDropdown.getMenu .findSelectedItem.getData; 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, increment: incrementData[0], incrementAll: incrementData[1], 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); let replaceSpace = typeof config.replaceSpace === 'string'; this.replaceSpaceCheck.setSelected(replaceSpace); this.replaceSpaceInput .setValue(replaceSpace ? config.replaceSpace : '-'); this.yearCheck.setSelected(config.year); this.yearFallbackCheck.setSelected(config.yearFallback); this.yearConvertCheck.setSelected(config.yearConvert); this.latinIncrementCheck.setSelected(				// compatibility				typeof config.latinIncrement === 'number' || config.latinIncrement			); let incrementMenu = this.incrementDropdown.getMenu; let incrementItem = incrementMenu .findItemFromData([config.increment, config.incrementAll]); if (incrementItem) { incrementMenu.selectItem(incrementItem); } else { incrementMenu.selectItemByData([2, false]); }			this.delimiterInput.setValue(config.delimiter); this.delimitConditionalCheck.setSelected(config.delimitConditional); this.removeUnreusedCheck.setSelected(config.removeUnreused); };		RefRenamerDialog.prototype.applyConfig = function (refs, forceKeep) { refs = (refs || this.refs).filter(ref => ref.active); if (!refs.length) return; let config = this.getConfig, withYear = new Set; let stack = config.main.flatMap(k => k.split('|')); let names = refs.map(ref => {				if (!forceKeep && ref.keepCheck) {					ref.keepCheck.setSelected(!config.removeUnreused);					if (config.removeUnreused) return;				}				let exports = {					name: ref.name,					props: ref.props,					getElement: => ref.$ref.clone[0]				};				mw.hook('refrenamer.rename').fire(exports);				if (typeof exports.newName === 'string') {					return exports.newName;				}				let s;				stack.some(k => (s = ref.props[k]));				if (!s) 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 year = config.year && ( config.yearConvert && ref.props.yearAscii || ref.props.year || config.yearFallback && (						config.yearConvert && ref.props.textYearAscii ||						ref.props.textYear					) );				if (year) {					let delimiter = config.delimitConditional && /\P{Nd}$/u.test(s)						? ''						: config.delimiter;					s += delimiter + year;					withYear.add(ref);				}				return s;			}); let comps = names.map(s => s && normalize(s)); let hardComps = new Set; this.refs.forEach(ref => {				if (refs.includes(ref)) return;				hardComps.add( ref.active && normalize(ref.input.getValue) || ref.normalized );			});			refs.forEach((ref, i) => {				let s = names[i];				if (!s) {					ref.input.setValue('');					return;				}				let normalized = comps[i];				let useLatin = config.latinIncrement && withYear.has(ref);				let needsIncrement = hardComps.has(normalized) ||					comps.indexOf(normalized) !== i ||					(useLatin || config.incrementAll) &&					comps.slice(i + 1).includes(normalized);				if (needsIncrement) {					let unsuffixed = s;					let delimiter = useLatin || ( config.delimitConditional && /\P{Nd}$/u.test(s) ) ? '' : config.delimiter;					let increment = useLatin ? 0 : config.increment;					do {						s = unsuffixed + delimiter +							(useLatin ? toLatin(increment) : increment);						normalized = normalize(s);						increment++;					} while (hardComps.has(normalized) || comps.includes(normalized));					comps.push(normalized);				}				ref.input.setValue(s);			}); };		RefRenamerDialog.prototype.setActive = function (refs, active) { refs.forEach(ref => {				ref.setActive(active === undefined ? ref.isAuto : active);			}); this.applyConfig(refs); let activeCount = this.refs.filter(ref => ref.active).length; let inactiveCount = this.refs.length - activeCount; let tabs = this.index.getTabs.getItems; tabs[0].$label.attr('data-refrenamer', activeCount); tabs[1].setDisabled(!inactiveCount) .$label.attr('data-refrenamer', inactiveCount); this.$table.toggleClass('refrenamer-hidden', !activeCount); if (!activeCount) { this.index.setTabPanel('other'); } else if (!inactiveCount) { this.index.setTabPanel('main'); } else { this.index.emit('set'); }		};		RefRenamerDialog.prototype.toggleActive = function (ref) { this.setActive([ref], !ref.active); };		RefRenamerDialog.prototype.getSetupProcess = function (data) { Object.assign(this, data); let warnings = []; if (this.templates.size) { warnings.push(					document.createTextNode(getMsg('templatesWarn')),					$('').append( [...this.templates].map(s => $('').append( ''						))					)[0]				);			}			if (this.invalid.size) { warnings.push(					document.createTextNode(getMsg('invalidWarn')),					$('').append( [...this.invalid].map(s => $('').text(s)) )[0]				);			}			this.warning.setLabel(warnings.length ? $(warnings) : '') .toggle(warnings.length); this.setConfig(mw.storage.getObject('refrenamer')); this.$tbody.empty; this.$otherTbody.empty; this.refs.forEach(ref => {				ref.initProps;				ref.initRows;			}); this.setActive(this.refs); let hasNonAsciiYear = this.refs.some(ref => ( ref.props.yearAscii || ref.props.textYearAscii ));			this.yearConvertLayout.toggle(hasNonAsciiYear); this.index.getCurrentTabPanel.$element.scrollTop(0); return RefRenamerDialog.super.prototype.getSetupProcess.call(this, data).next(function {				this.index.emit('set');			}, this); };		RefRenamerDialog.prototype.setupCollapsibles = mw.util.debounce(function {			if (this.index.getCurrentTabPanelName !== 'other') return;			let refs = this.refs.filter(ref => !ref.active && ref.$toggle);			let width = refs[0].refCell.clientWidth;			refs.reverse.forEach(ref => { ref.setupCollapsible(width); });			this.updateSize;		}, 200); RefRenamerDialog.prototype.getActionProcess = function (action) { if (action === 'addAll') { this.setActive(this.refs, true); } else if (action === 'resetSelection') { this.setActive(this.refs); }			return RefRenamerDialog.super.prototype.getActionProcess.call(this, action).next(function {				if (action !== 'continue') return;				let subs = {}, integers = [], dupes = [];				let comps = this.refs.filter(ref => !ref.active).map(ref => ref.normalized);				let hasNonVe;				this.refs.forEach(ref => { if (!ref.active) return; if (ref.keepCheck && !ref.keepCheck.isSelected) { subs[ref.normalized] = null; hasNonVe = hasNonVe || !ref.isVe; return; }					let newName = ref.input.getValue.replace(/^[\s_]+|[\s_]+$/g, ''); let normalized = normalize(newName); if (!normalized || newName === ref.name && ref.names.size === 1) { comps.push(ref.normalized); } else if (!/\D/.test(normalized)) { integers.push(newName); } else if (comps.includes(normalized)) { dupes.push(newName); } else { subs[ref.normalized] = newName; hasNonVe = hasNonVe || !ref.isVe; comps.push(normalized); }				});				if (integers.length || dupes.length) {					return new OO.ui.Error($([						[integers, 'numericError'],						[dupes, 'duplicatesError']					].flatMap(([names, msgKey]) => names.length ? [						document.createTextNode(getMsg(msgKey)), $('').append(names.map(n => $('').text(n)))[0] ] : [])), { recoverable: false });				}				if (!Object.keys(subs).length) {					return new OO.ui.Error( getMsg('noChangesError'), { recoverable: false } );				}				this.close;				let newText = this.wikitext.replace( /<(ref)\s+(name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))(\s*\/?)>/gi,					(s, tag, attr, name1, name2, name3, slash) => {						let normalized = normalize(name1 || name2 || name3);						return subs.hasOwnProperty(normalized)							? subs[normalized]								? `<${tag} ${attr}="${subs[normalized]}"${slash}>`								: `<${tag}>`							: s;					}				);				let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';				let summary = hasNonVe					? getMsg('genericSummary', iw + 'User:Nardog/RefRenamer')					: getMsg(						'summary',						iw + 'Wikipedia:VisualEditor/Named references',						iw + 'User:Nardog/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(Date.parse(timestamp) - elapsed) .toISOString.slice(0, -5).replace(/\D/g, ''); formData.push(['wpStarttime', time]); }						formData.push(							['wpEdittime', this.editTime],							['editRevId', this.revId],							['wpIgnoreBlankSummary', 1],							['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]); $(' ').attr({							method: 'post',							action: mw.util.getUrl(null, { action: 'submit' }),							enctype: 'multipart/form-data'						}).append(							formData.map(([n, v]) => $(' ').attr({								name: n,								type: 'hidden'							}).val(v))						).appendTo(document.body).trigger('submit').remove; });					notify('opening');				}				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 }); };		RefRenamerDialog.prototype.getBodyHeight = function { return this.index.$menu[0].scrollHeight + this.index.getCurrentTabPanel.$element[0].scrollHeight; };		dialog = new RefRenamerDialog; let winMan = new OO.ui.WindowManager; winMan.addWindows([dialog]); winMan.$element.appendTo(OO.ui.getTeleportTarget); };	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 onToggle = function { let expanded = this.parentElement.classList.toggle('refrenamer-expanded'); let newAction = expanded ? 'collapse' : 'expand'; this.className = 'refrenamer-toggle oo-ui-icon-' + newAction; this.title = getMsg(newAction); dialog.updateSize; };	window.refRenamer; });