let amendments;
var baseRec = document.createElement("html");
const PLUGIN_NAME = "amendments manager";
const differ = new HTMLTreeDiff();
const params = new URLSearchParams(window.location.search);
function removeComments(el) {
// Remove HTML comments
const commentsIterator = document.createNodeIterator(el, NodeFilter.SHOW_COMMENT);
let comment;
while ((comment = commentsIterator.nextNode())) {
comment.remove();
}
}
function markInsertion(el, controller) {
const wrapper = document.createElement("ins");
if (el.tagName === "DIV" || el.tagName === "SECTION" || el.tagName === "DT" || el.tagName === "DD" || el.tagName === "LI") {
// special casing the case where
is used to group
/
if (el.tagName === "DIV" && el.parentNode.tagName === "DL") {
for (let child of el.children) {
wrapChildNodes(child, document.createElement("ins"));
}
el.children[0].prepend(controller);
} else {
wrapChildNodes(el, wrapper);
el.prepend(controller);
}
} else {
wrapElement(el, wrapper);
el.parentNode.insertBefore(controller, el);
}
}
function wrapElement(el, wrapper) {
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
}
function wrapChildNodes(parent, wrapper) {
// freeze the list by copying it in array
const children = [...parent.childNodes];
if (children.length) {
parent.prepend(wrapper);
for (let i in children) {
wrapper.appendChild(children[i]);
}
}
}
function titleFromId(id) {
const container = baseRec.querySelector(`#${id}`) ?? document.getElementById(id);
if (!container) return id;
return container.closest("section").querySelector("h1,h2,h3,h4,h5,h6").textContent;
}
function listPRs(pr, repoURL) {
const span = document.createElement("span");
span.appendChild(document.createTextNode(" ("));
pr = Array.isArray(pr) ? pr : [pr];
for (let i in pr) {
const number = pr[i];
const url = repoURL + "pull/" + number;
const a = document.createElement("a");
a.href = url;
a.textContent = `PR #${number}`;
span.appendChild(a);
if (i < pr.length - 1) {
span.appendChild(document.createTextNode(", "));
}
}
span.appendChild(document.createTextNode(")"));
return span;
}
function listTestUpdates(updates) {
const s = document.createElement("span");
if (updates === "not-testable") {
s.textContent = " (not testable)";
} else if (updates === "already-tested") {
s.textContent = " (no change needed in tests)";
} else if (Array.isArray(updates)) {
s.textContent = " - Changes to Web Platform Tests: ";
updates.forEach(u => {
const link = document.createElement("a");
link.href = "https://github.com/web-platform-tests/wpt/pull/" + u.split("#")[1];
link.textContent = "#" + u.split("#")[1];
s.append(link);
s.append(" ");
});
}
return s;
}
const capitalize = s => s[0].toUpperCase() + s.slice(1);
async function listAmendments(config, _, {showError}) {
amendments = await fetch("amendments.json").then(r => r.json());
baseRec.innerHTML = await fetch("base-rec.html").then(r => r.text());
for (let id of Object.keys(amendments)) {
}
let m;
let i = 0;
let consolidatedAmendments = {proposed: {}, candidate: {}};
for (let id of Object.keys(amendments)) {
// validate that an amendment is not embedded in another
const container = document.getElementById(id) ?? baseRec.querySelector("#" + id);
if (!container) {
showError(`Unknown element with id ${id} identified in amendments, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME);
continue;
}
if (amendments[id][0].difftype !== 'append') {
const embedded = Object.keys(amendments).filter(iid => iid !== id).find(iid => container.querySelector("#" + iid));
if (embedded) {
showError(`The container with id \`${id}\` marked as amended cannot embed the other container of amendment \`${embedded}\`, see the [instructions for amendments mangements](https://github.com/w3c/webrtc-pc/blob/main/amendments.md)`, PLUGIN_NAME, {elements: [container]});
}
}
// validate that a section has only one difftype, one amendment type, one amendment status
if (amendments[id].some(a => a.difftype && a.difftype !== amendments[id][0].difftype)) {
showError(`Amendments in container with id \`${id}\` are mixing \`modify\` and \`append\` difftypes, see the [instructions for amendments mangements](https://github.com/w3c/webrtc-pc/blob/main/amendments.md)`, PLUGIN_NAME, {elements: [container]});
}
if (amendments[id].some(a => a.status !== amendments[id][0].status)) {
showError(`Amendments in container with id \`${id}\` are mixing \`candidate\` and \`proposed\` amendments, see [instructions for amendments mangements](https://github.com/w3c/webrtc-pc/blob/main/amendments.md)`, PLUGIN_NAME, {elements: [container]});
}
// Group by candidate id for listing in the appendix
for (let amendment of amendments[id]) {
if (!consolidatedAmendments[amendment.status][amendment.id]) {
consolidatedAmendments[amendment.status][amendment.id] = [];
}
consolidatedAmendments[amendment.status][amendment.id].push({...amendment, section: id});
}
}
for (const status of ["proposed", "candidate"]) {
const section = document.getElementById(`${status}-amendments`);
if (section) {
const ul = document.createElement("ul");
for (const amendment of Object.values(consolidatedAmendments[status])) {
const {status, id, type} = amendment[0];
const li = document.createElement("li");
const entriesUl = document.createElement("ul");
li.appendChild(document.createTextNode(`${capitalize(status)} ${capitalize(type)} ${id}: `));
amendment.forEach(({description, section, pr, testUpdates}, i) => {
const entryLi = document.createElement("li");
entryLi.innerHTML = description;
const link = document.createElement("a");
entryLi.appendChild(document.createTextNode(" - "));
link.href = "#" + section;
link.textContent = `section ${titleFromId(section)}`;
entryLi.appendChild(link);
entryLi.appendChild(listPRs(pr, config.github.repoURL));
entryLi.appendChild(listTestUpdates(testUpdates));
entriesUl.appendChild(entryLi);
});
li.appendChild(entriesUl);
ul.appendChild(li);
}
section.appendChild(ul);
}
}
}
const makeIdlDiffable = pre => {
pre.querySelector(".idlHeader").remove();
pre.textContent = pre.textContent ;
};
async function showAmendments(config, _, {showError}) {
if (config.specStatus !== "REC") {
return;
}
const diffmode = params.get("diff") === "old" ? "old" : "diff";
for (let section of Object.keys(amendments)) {
const target = document.getElementById(section);
let wrapper = document.createElement("div");
if (amendments[section][0].difftype !== "append") {
if (["LI", "DD"].includes(target?.tagName)) {
wrapper = document.createElement(target.tagName);
wrapper.className = "skip";
}
} else {
if (["DL"].includes(target?.tagName)) {
wrapper = document.createElement("dt");
}
}
wrapper.id = section + "-change-wrapper";
const annotations = [];
for (let {description, id, difftype, status, type, pr} of amendments[section]) {
// integrate the annotations for candidate/proposed amendments
// only when Status = REC
// (but keep them all in for other statuses of changes)
if (!(["correction", "addition"].includes(type) || ["candidate", "proposed"].includes(status))) {
continue;
}
const amendmentDiv = document.createElement("div");
amendmentDiv.className = `${status} ${type}`;
const marker = document.createElement("span");
marker.className = "marker";
marker.textContent = `${capitalize(status)} ${capitalize(type)} ${id}:`;
const title = document.createElement("span");
title.innerHTML = description;
amendmentDiv.appendChild(marker);
amendmentDiv.appendChild(title);
amendmentDiv.appendChild(listPRs(pr, config.github.repoURL));
annotations.push(amendmentDiv);
}
for (let div of annotations) {
wrapper.appendChild(div);
}
if (annotations.length) {
const amendmentTitle = `${capitalize(amendments[section][0].status)} ${capitalize(amendments[section][0].type)}${amendments[section].length > 1 ? "s" : ""} ${amendments[section].map(a => `${a.id}`).join(', ')}`;
const ui = document.createElement("fieldset");
ui.className = "diff-ui";
ui.innerHTML = ``;
wrapper.appendChild(ui);
if (amendments[section][0].difftype === "modify" || !amendments[section][0].difftype) {
ui.querySelectorAll('input[type="radio"]').forEach(inp => {
inp.setAttribute("aria-controls", `${section}`);
});
ui.classList.add("modify");
const containerOld = baseRec.querySelector("#" + section);
if (!containerOld) {
showError(`Unknown element with id ${section} in Recommendation used as basis, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME);
continue;
}
const containerNew = document.getElementById(section)?.cloneNode(true);
if (!containerNew) {
showError(`No element with id ${section} in editors draft, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME);
continue;
}
if (diffmode === "old") {
const current = document.getElementById(section);
current.replaceWith(containerOld);
continue;
}
removeComments(containerNew);
containerNew.querySelectorAll(".removeOnSave").forEach(el => el.remove());
const container = document.getElementById(section);
container.innerHTML = "";
// Use text-only content for pre - syntax highlights
// messes it up otherwise
if (containerNew.matches("pre.idl")) makeIdlDiffable(containerNew);
containerNew.querySelectorAll("pre.idl").forEach(makeIdlDiffable);
if (containerOld.matches("pre.idl")) makeIdlDiffable(containerOld);
containerOld.querySelectorAll("pre.idl").forEach(makeIdlDiffable);
await differ.diff(container, containerOld, containerNew);
container.parentNode.insertBefore(wrapper, container);
} else if (amendments[section][0].difftype === "append") {
ui.classList.add("append");
const appendedEl = document.getElementById(section);
if (!appendedEl) {
showError(`No element with id ${section} in editors draft, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME);
continue;
}
if (diffmode === "old") {
appendedEl.remove();
continue;
}
appendedEl.setAttribute("aria-label", `Addition from ${amendmentTitle}`);
appendedEl.classList.add('diff-new');
markInsertion(appendedEl, wrapper);
ui.querySelectorAll('input[type="radio"]').forEach(inp => {
inp.setAttribute("aria-controls", `${section}`);
});
}
}
}
// We clean up any remaining duplicate ids that might be left
const elements = [...document.querySelectorAll('[id]')];
const ids = [];
const dups = [];
elements.forEach(el => ids.includes(el.id) ? dups.push(el) : ids.push(el.id));
dups.forEach((el, i) => el.id = el.id + "-dedup-" + i);
// and internal links to ids that no longer exist
const brokenLinks = [...document.querySelectorAll('[href^="#"]')];
brokenLinks.forEach(el => {
const target = document.getElementById(el.getAttribute("href").slice(1));
if (!target) {
el.removeAttribute("href");
}
});
}