Bugzilla - Merge "Updated" changes

Merge auxiliary changes by the same author

Verzia zo dňa 03.01.2025. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name        Bugzilla - Merge "Updated" changes
// @description Merge auxiliary changes by the same author
// @namespace   RainSlide
// @author      RainSlide
// @license     AGPL-3.0-or-later
// @version     1.0
// @icon        https://bugzilla.mozilla.org/extensions/BMO/web/images/favicon.ico
// @match       https://bugzilla.mozilla.org/show_bug.cgi?*
// @grant       none
// @inject-into content
// @run-at      document-end
// ==/UserScript==

"use strict";

const $ = (tagName, ...props) => Object.assign(
	document.createElement(tagName), ...props
);

const css = `.activity .changes-container {
	display: flex;
	align-items: center;
}
.activity .changes-separator {
	display: inline-block;
	transform: scaleY(2.5);
	white-space: pre;
}
.activity .change-name,
.activity .change-time {
	font-size: var(--font-size-medium);
}
.changes-container:target,
.change:target {
	outline: 2px solid var(--focused-control-border-color);
}`;

document.head.append($("style", { textContent: css }));

// Continuous groups of:
// 1. auxiliary .change-set (.change-set with no comment text, id starts with "a")
// 2. by the same author
const aGroups = [];

let currentAuthor = null;
let newGroup = true;
document.querySelectorAll("#main-inner > .change-set").forEach(changeSet => {

	// check if is auxiliary change set
	if (changeSet.id[0] !== "a") {
		// no, no longer continuous, add a new group for next auxiliary change set
		newGroup = true;
		return;
	}

	// get & check author vcard
	const author = changeSet.querySelector(":scope .change-author > .vcard");
	if (author === null) {
		throw new TypeError('Element ".change-set .change-author > .vcard" not found!');
	}

	// check if is the same author
	if (author.textContent !== currentAuthor) {
		// different author, set currentAuthor, add a new group directly
		currentAuthor = author.textContent;
		aGroups.push([changeSet]);
		newGroup = false;
	} else if (!newGroup) {
		// same author, push to current group
		aGroups.at(-1).push(changeSet);
	} else {
		// same author, add a new group
		aGroups.push([changeSet]);
		newGroup = false;
	}

});

// "move" an id, from an element, to another element
const moveId = (from, to) => {
	const id = from.id;
	from.removeAttribute("id");
	to.id = id;
};

// append .change to .activity, create container if needed
const appendChanges = (changeSet, activity, isFirst) => {

	// get & check .change element(s)
	const changes = changeSet.querySelectorAll(":scope > .activity > .change");
	if (changes.length === 0) {
		throw new TypeError('Element(s) ".change-set > .activity > .change" not found!');
	}

	// get name & time
	const tr = changeSet.querySelector(
		':scope > .change > .change-head > tbody > tr[id^="ar-a"]:nth-of-type(2)'
	);
	const td = tr?.querySelector(":scope > td:only-child");

	// move name & time into .change or .changes-container, append .changes-container
	if (tr && td) {
		if (changes.length > 1) {
			// a group of .change, create container for nameTime & themselves
			const container = $("div", { className: "changes-container" });
			const group     = $("div", { className: "changes" });
			const nameTime  = $("div", { id: tr.id });
			const separator = $("span", { className: "changes-separator", textContent: "| " });
			nameTime.append(...td.childNodes, separator);
			group.append(...changes);
			container.append(nameTime, group);
			tr.remove();

			// appending .changes-container

			// "move" an id onto another existing element might mess up the :target highlight,
			// so skip that for the first
			if (!isFirst) {
				moveId(changeSet, container);
			}
			// but, first .changes-container needs append!
			activity.append(container);

			return;

		} else {
			// only one .change, don't create container, just move nameTime to changes[0]
			const nameTime = $("span", { id: tr.id });
			nameTime.append(...td.childNodes, "| ");
			changes[0].prepend(nameTime);
			tr.remove();

			// no return here, append in if (!isFirst) ... below
		}
	}

	// appending .change / a group of .change

	// first doesn't need move id, see before;
	// first .change is already in .activity, doesn't need append either.
	if (!isFirst) {
		moveId(changeSet, changes[0]);
		activity.append(...changes);
	}

};

// merge the .change of each aGroup into the first .change-set with appendChanges()
aGroups.forEach(group => {
	if (group.length < 2) return;

	const first = group[0];
	const activity = first.querySelector(":scope > .activity");
	appendChanges(first, activity, true);

	// starts from 1 to skip the first change set
	for (let i = 1; i < group.length; i++) {
		appendChanges(group[i], activity);
		group[i].remove();
	}
});