User:Nardog/AutoSectionLink.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(function autoSectionLink() {
	let form = document.getElementById('editform');
	if (!form) return;
	let formData = new FormData(form);
	let section = formData.get('wpSection');
	if (section === 'new') return;
	let widget = document.getElementById('wpSummaryWidget');
	if (!widget) return;
	let isOld = formData.get('altBaseRevId') > 0 ||
		(formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId');
	mw.loader.using([
		'jquery.textSelection', 'mediawiki.util', 'oojs-ui-core',
		'oojs-ui.styles.icons-editing-core'
	], () => {
		let $textarea = $('#wpTextbox1');
		let input = OO.ui.infuse(widget);
		let button = new OO.ui.ButtonWidget({
			framed: false,
			icon: 'undo',
			classes: ['autosectionlink-button'],
			title: 'Restore previous section link'
		}).toggle().on('click', () => {
			let cache = button.getData();
			input.setValue(input.getValue().replace(
				/^(\/\*.*?\*\/)?\s*/,
				cache[0] ? '/* ' + cache[0] + ' */ ' : ''
			));
			updatePreview(cache[0]);
			cache.reverse();
		}).on('toggle', () => {
			input.$input.css('width', `calc(100% - ${button.$element.width()}px)`);
		});
		input.$input.after(button.$element);
		let update = mw.util.debounce($diff => {
			let lines = $textarea.textSelection('getContents').replace(/\s+$/, '').split('\n');
			let firstLineNum;
			if (isOld) {
				let i, lastLineNum;
				$diff.find('td:last-child').each(function () {
					if (this.classList.contains('diff-lineno')) {
						i = this.textContent.replace(/\D+/g, '') - 1;
					} else if (this.classList.contains('diff-context')) {
						i++;
					} else if (this.classList.contains('diff-addedline')) {
						i++;
						if (!firstLineNum) firstLineNum = i;
						lastLineNum = i;
					} else if (this.classList.contains('diff-empty')) {
						if (!firstLineNum) firstLineNum = i === 0 ? 1 : i;
						lastLineNum = i;
					}
				});
				lines.length = lastLineNum || 0;
			} else {
				let origLines = $textarea.prop('defaultValue').replace(/\s+$/, '').split('\n');
				firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1;
				if (!firstLineNum) {
					firstLineNum = lines.length < origLines.length
						? lines.length
						: 1;
				}
				for (let i = 1, x = lines.length, y = origLines.length;
					(section ? i < x : i <= x) && lines[x - i] === origLines[y - i];
					i++
				) {
					lines.pop();
				}
			}
			let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/, lowest = 7;
			lines.slice(firstLineNum).forEach(line => {
				let match = line.match(re);
				if (match && match[1].length < lowest) lowest = match[1].length;
			});
			let head;
			lines.slice(0, firstLineNum).reverse().some(line => {
				let match = line.match(re);
				if (match && match[1].length < lowest) {
					head = match[2];
					return true;
				}
			});
			head = head ? head
				.replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3')
				.replace(/''(.+?)''/g, '$1')
				.trim() : '';
			let match = input.getValue().match(/^(?:\/\*\s*(.*?)\s*\*\/)?\s*(.*?)$/);
			if (!match[1]) match[1] = '';
			if (match[1] === head) return;
			if (section < 1 && lowest === 7 && !head && match[1] === 'top') return;
			input.setValue((head ? '/* ' + head + ' */ ' : '') + match[2]);
			button.setData([match[1], head]).toggle(true);
			updatePreview(head);
		}, 500);
		let updatePreview = head => {
			let $preview = $('.mw-summary-preview > .comment > span[dir="auto"]');
			if (!$preview.length) return;
			let url = head && mw.util.getUrl() + '#' + head.replace(/ /g, '_');
			let text = head && (document.dir === 'rtl' ? '←\u200F' : '→\u200E') + head;
			let $ac = $preview.children('.autocomment:first-child');
			if ($ac.length && !$ac[0].previousSibling) {
				if (head) {
					$ac.children('a').attr('href', url).text(text);
				} else {
					let node = $ac[0].nextSibling;
					if (node && node.nodeType === 3) {
						node.textContent = node.textContent.replace(/^\s+/, '');
					}
					$ac.remove();
				}
			} else if (head) {
				$('<span>').addClass('autocomment').append(
					$('<a>').attr({
						href: url,
						title: mw.config.get('wgPageName').replace(/_/g, ' ')
					}).text(text),
					mw.messages.get('colon-separator', ': ')
				).prependTo($preview);
			}
		};
		if (isOld) {
			mw.hook('wikipage.diff').add(update);
		} else {
			$textarea.on('input', update);
			mw.hook('ext.CodeMirror.switch').add((on, $codeMirror) => {
				if (on) $codeMirror[0].CodeMirror.on('change', update);
			});
		}
	});
	mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}');
});