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.
(mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') &&
(function consecudiff() {
	mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}');
	let isHist = mw.config.get('wgAction') === 'history';
	class Consecudiff {
		constructor(lis, isContribs) {
			this.isContribs = isContribs;
			this.isEnhanced = !isHist && !isContribs &&
				lis[0].classList.contains('mw-enhanced-rc');
			this.threshold = isContribs ? window.consecudiffContribsThreshold || 120
				: isHist ? window.consecudiffHistThreshold || 720
				: window.consecudiffThreshold || 720;
			this.strictMode = !isContribs &&
				!!window.consecudiffDetectInterruptions;
			this.diffSelector = isHist
				? 'a.mw-history-histlinks-previous'
				: '.mw-changeslist-diff';
			this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' ||
				(isHist || isContribs) && 'a.mw-changeslist-date';
			this.hybridSelector = this.diffSelector;
			if (this.permaSelector) {
				this.hybridSelector += ', ' + this.permaSelector;
			}
			this.topClass = isContribs
				? 'mw-contributions-current'
				: 'mw-changeslist-last';
			let dependencies = ['mediawiki.util'];
			if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') {
				dependencies.push('mediawiki.language.months');
			}
			mw.loader.using(dependencies, () => {
				let chunks;
				if (isHist) {
					chunks = this.chunkByUser(lis);
				} else {
					chunks = [];
					this.groupByTitle(lis).forEach(group => {
						chunks.push(...this.chunkByUser(group));
					});
				}
				let subchunks = [];
				chunks.forEach(chunk => {
					subchunks.push(...this.divideByDate(chunk));
				});
				let linkPairs = [];
				subchunks.forEach(subchunk => {
					linkPairs.push(...this.makeLinks(subchunk));
				});
				linkPairs.forEach(([$span, parent]) => {
					$span.appendTo(parent);
				});
			});
		}
		groupByTitle(lis) {
			let selector = this.isContribs
				? '.mw-contributions-title'
				: '.mw-changeslist-title';
			let lisByTitle = {};
			lis.forEach(li => {
				let link = (this.isEnhanced ? li.closest('table') : li)
					.querySelector(selector);
				if (!link) return;
				let title = link.textContent;
				if (!lisByTitle.hasOwnProperty(title)) lisByTitle[title] = [];
				lisByTitle[title].push(li);
			});
			return Object.values(lisByTitle).filter(group => group.length > 1);
		}
		chunkByUser(lis) {
			if (this.isSingleContribs) return [lis];
			let chunks = [], lastSplitAt = 0, prevUser;
			this.isSingleContribs = lis.some((li, i) => {
				let link = li.querySelector('.mw-userlink');
				if (!link) return this.isContribs;
				let user = link.textContent;
				if (i && user !== prevUser) {
					chunks.push(lis.slice(lastSplitAt, i));
					lastSplitAt = i;
				}
				prevUser = user;
			});
			if (this.isSingleContribs) return [lis];
			chunks.push(lis.slice(lastSplitAt));
			return chunks.filter(chunk => chunk.length > 1);
		}
		divideByDate(lis) {
			let chunks = [], lastSplitAt = 0, prevDate;
			lis.forEach((li, i) => {
				let date;
				if (isHist || this.isContribs) {
					date = this.parseDate(
						li.querySelector('.mw-changeslist-date').textContent
					);
				} else {
					date = Date.parse(
						li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z')
					);
				}
				if (date) date = date / 60000;
				if (i && prevDate - date > this.threshold) {
					chunks.push(lis.slice(lastSplitAt, i));
					lastSplitAt = i;
				}
				prevDate = date;
				if (!this.strictMode || lastSplitAt === i) return;
				let prevDiff = lis[i - 1].querySelector(this.diffSelector);
				if (prevDiff) {
					let prevNext = mw.util.getParamValue('oldid', prevDiff.search);
					if (prevNext !== li.dataset.mwRevid) {
						chunks.push(lis.slice(lastSplitAt, i));
						lastSplitAt = i;
					}
				}
			});
			chunks.push(lis.slice(lastSplitAt));
			return chunks.filter(chunk => chunk.length > 1);
		}
		makeLinks(lis) {
			let count = lis.length;
			let firstPerma;
			let start = lis.findIndex(li => (
				firstPerma = li.querySelector(this.hybridSelector)
			));
			if (start === -1 || count - start < 2) return [];
			let end, lastDiff;
			for (let i = count - 1; i > start; i--) {
				if (!isHist && !this.isContribs) {
					lastDiff = lis[i].querySelector(this.diffSelector);
					if (lastDiff ||
						lis[i].classList.contains('mw-changeslist-src-mw-new')
					) {
						end = i + 1;
						break;
					}
				}
				if (this.permaSelector && lis[i].querySelector(this.permaSelector)) {
					end = i + 1;
					break;
				}
			}
			if (!end) return [];
			count = end - start;
			let params = { diff: lis[start].dataset.mwRevid };
			if (lastDiff) {
				params.oldid = mw.util.getParamValue('oldid', lastDiff.search);
			} else {
				params.oldid = lis[end - 1].dataset.mwRevid;
				if (isHist && lis[end - 1].querySelector(this.diffSelector) ||
					this.isContribs && !lis[end - 1].querySelector('.newpage')
				) {
					params.direction = 'prev';
				}
			}
			let title = !isHist && mw.util.getParamValue('title', firstPerma.search);
			let url = mw.util.getUrl(title, params);
			let classes = 'consecudiff';
			if (!isHist && lis[start].classList.contains(this.topClass)) {
				classes += ' consecudiff-top';
			}
			return lis.slice(start, end).map((li, i) => [
				$('<span>').addClass(classes).append(
					$('<a>')
						.attr('href', url)
						.text(this.convertNumber(count - i + '/' + count))
				),
				this.isEnhanced
					? li.tagName === 'TR'
						? li.lastElementChild
						: li.querySelector('.mw-changeslist-line-inner')
					: li
			]);
		}
		parseDate(s) {
			let date = Date.parse(s);
			if (date) return date;
			if (s.includes(',')) date = Date.parse(s.replace(',', ''));
			if (date) return date;
			if (mw.loader.getState('mediawiki.language.months') !== 'ready') return;
			s = s.replace(/\D/g, c => {
				let n = mw.language.convertNumber(c, true);
				return Number.isNaN(n) ? c : n;
			});
			let h, m;
			s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => {
				h = $1;
				m = $2;
				return ' ';
			});
			if (!h) return;
			let y, dateFirst;
			s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => {
				y = $2;
				dateFirst = /\d/.test($1);
				return $1 + ' ';
			});
			if (!y) return;
			let mo, d;
			if (dateFirst) {
				[d, s] = this.getDate(s);
				if (!d) return;
				[mo, s] = this.getMonth(s);
				if (mo === -1) return;
			} else {
				[mo, s] = this.getMonth(s);
				if (mo === -1) return;
				[d, s] = this.getDate(s);
				if (!d) return;
			}
			return new Date(y, mo, d, h, m).getTime();
		}
		getMonth(s) {
			if (!this.months) {
				this.months = mw.language.months.abbrev
					.concat(mw.language.months.names, mw.language.months.genitive)
					.reverse();
			}
			let mo = this.months.findIndex(mn => {
				let temp = s.replace(mn, ' ');
				if (temp !== s) {
					s = temp;
					return true;
				}
			});
			if (mo === -1) {
				let [numeric, temp] = this.getDate(s);
				numeric = parseInt(numeric);
				if (numeric > 0 && numeric < 13) {
					mo = numeric - 1;
					s = temp;
				}
			} else {
				mo = 11 - mo % 12;
			}
			return [mo, s];
		}
		getDate(s) {
			let d;
			s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => {
				d = $2;
				return $1 + ' ';
			});
			return [d, s];
		}
		convertNumber(num) {
			try {
				return mw.language.convertNumber(num);
			} catch (e) {
				return num;
			}
		}
	}
	mw.hook('wikipage.content').add($content => {
		$content.find('.mw-pager-body').each(function () {
			let lis = this.querySelectorAll(':scope > .mw-contributions-list > li');
			if (lis.length > 1) new Consecudiff([...lis], !isHist);
		});
		if (isHist) return;
		let $lists = $content.filter('.mw-changeslist');
		if (!$lists.length) $lists = $content.find('.mw-changeslist');
		$lists.each(function () {
			let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]');
			if (lis.length > 1) new Consecudiff([...lis]);
		});
	});
}());