您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Merge auxiliary changes by the same author
当前为
// ==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(); } });