Bugzilla - Merge "Updated" changes

Merge auxiliary changes by the same author

目前為 2025-01-03 提交的版本,檢視 最新版本

// ==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();
	}
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址