Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve amendment management #2995

Merged
merged 14 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 86 additions & 58 deletions amendments.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
let amendments;
var baseRec = document.createElement("html");

function wrap(el, wrapper) {
if (el.tagName === "DIV" || el.tagName === "SECTION" || el.tagName === "P" || el.tagName === "DT" || el.tagName === "DD" || el.tagName === "LI") {
wrapChildren(el, wrapper);
const PLUGIN_NAME = "amendments manager";

const differ = new HTMLTreeDiff();

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 <div> is used to group <dt>/<dd>
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);
}
}

Expand All @@ -14,38 +38,30 @@ function wrapElement(el, wrapper) {
wrapper.appendChild(el);
}


function wrapChildren(parent, wrapper) {
function wrapChildNodes(parent, wrapper) {
// freeze the list by copying it in array
const children = [...parent.childNodes];
if (children && children.length) {
parent.insertBefore(wrapper, children[0]);
if (children.length) {
parent.prepend(wrapper);
for (let i in children) {
wrapper.appendChild(children[i]);
}
}
}

function containerFromId(id) {
const container = baseRec.querySelector('#' + id);
if (!container) {
throw new Error(`Unknown element with id ${id} in Recommendation used as basis, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`);
}
return container;
}

function titleFromId(id) {
const container = baseRec.querySelector('#' + 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) {
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 = respecConfig.github.repoURL + "pull/" + number;
const url = repoURL + "pull/" + number;
const a = document.createElement("a");
a.href = url;
a.textContent = `PR #${number}`;
Expand Down Expand Up @@ -79,7 +95,7 @@ function listTestUpdates(updates) {

const capitalize = s => s[0].toUpperCase() + s.slice(1);

async function listAmendments() {
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());

Expand All @@ -90,22 +106,26 @@ async function listAmendments() {
let consolidatedAmendments = {};
for (let id of Object.keys(amendments)) {
// validate that an amendment is not embedded in another
const container = containerFromId(id);
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) {
throw new Error(`The container with id ${id} marked as amended cannot embed the other container of amendment ${embedded}, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`);
showError(`The container with id ${id} marked as amended cannot embed the other container of amendment ${embedded}, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME, {elements: [container]});
}
}
// validate that a section has only one difftype, one amendment type, one amendemnt status
// 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)) {
throw new Error(`Amendments in container with id ${id} are mixing "modification" and "append" difftypes, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`);
showError(`Amendments in container with id ${id} are mixing "modification" and "append" difftypes, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME, {elements: [container]});
}
if (amendments[id].some(a => a.type !== amendments[id][0].type)) {
//throw new Error(`Amendments in container with id ${id} are mixing "corrections" and "addition" types`);
}
if (amendments[id].some(a => a.status !== amendments[id][0].status)) {
throw new Error(`Amendments in container with id ${id} are mixing "candidate" and "proposed" amendments, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`);
showError(`Amendments in container with id ${id} are mixing "candidate" and "proposed" amendments, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`, PLUGIN_NAME, {elements: [container]});
}

// Group by candidate id for listing in the appendix
Expand All @@ -131,7 +151,7 @@ async function listAmendments() {
link.href = "#" + section;
link.textContent = `section ${titleFromId(section)}`;
entryLi.appendChild(link);
entryLi.appendChild(listPRs(pr));
entryLi.appendChild(listPRs(pr, config.github.repoURL));
entryLi.appendChild(listTestUpdates(testUpdates));
entriesUl.appendChild(entryLi);
});
Expand All @@ -142,7 +162,12 @@ async function listAmendments() {
}
}

function showAmendments() {
const makeIdlDiffable = pre => {
pre.querySelector(".idlHeader").remove();
pre.textContent = pre.textContent ;
};

async function showAmendments(config, _, {showError}) {
for (let section of Object.keys(amendments)) {
const target = document.getElementById(section);
let wrapper = document.createElement("div");
Expand All @@ -162,7 +187,7 @@ function showAmendments() {
// integrate the annotations for candidate/proposed amendments
// only when Status = REC
// (but keep them all in for other statuses of changes)
if (respecConfig.specStatus !== "REC" && (["correction", "addition"].includes(type) || ["candidate", "proposed"].includes(status))) {
if (config.specStatus !== "REC" && (["correction", "addition"].includes(type) || ["candidate", "proposed"].includes(status))) {
continue;
}
const amendmentDiv = document.createElement("div");
Expand All @@ -174,7 +199,7 @@ function showAmendments() {
title.innerHTML = description;
amendmentDiv.appendChild(marker);
amendmentDiv.appendChild(title);
amendmentDiv.appendChild(listPRs(pr));
amendmentDiv.appendChild(listPRs(pr, config.github.repoURL));
annotations.push(amendmentDiv);
}

Expand All @@ -185,45 +210,48 @@ function showAmendments() {
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 = `<label><input aria-controls="${section} ${section}-new" name="change-${section}" class=both checked type=radio> Show Current and Future</label><label><input name="change-${section}" class=current type=radio> Show Current</label><label><input name="change-${section}" class=future type=radio>Show Future</label>`;
ui.innerHTML = `<label><input aria-controls="${section}" name="change-${section}" class=both checked type=radio> Show Current and Future</label><label><input name="change-${section}" class=current type=radio> Show Current</label><label><input name="change-${section}" class=future type=radio>Show Future</label>`;
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} ${section}-new`);
inp.setAttribute("aria-controls", `${section}`);
});
ui.classList.add("modify");
let containerOld = containerFromId(section);
containerOld = containerOld.cloneNode(true);
containerOld.classList.add("diff-old", "exclude");
containerOld.setAttribute("aria-label", `Deletion from ${amendmentTitle}`);
// clean up ids to avoid duplicates, but not for headings since they're required by pubrules
containerOld.querySelectorAll("*:not(:is(h2,h3,h4,h5,h6))[id]").forEach(el => el.removeAttribute("id"));
const containerNew = document.getElementById(section);
if (!containerNew) throw new Error(`No element with id ${section} in editors draft, see https://github.com/w3c/webrtc-pc/blob/main/amendments.md for amendments management`);

containerNew.classList.add("diff-new");
containerNew.id += "-new";
containerNew.setAttribute("aria-label", `Addition from ${amendmentTitle}`);
containerNew.parentNode.insertBefore(containerOld, containerNew);
containerNew.parentNode.insertBefore(wrapper, containerOld);
wrap(containerOld, document.createElement("del"));
wrap(containerNew, document.createElement("ins"));
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;
}
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 appendBase = document.getElementById(section);
appendBase.appendChild(wrapper);
const controlledIds = [];
document.querySelectorAll(`.add-to-${section}`).forEach((el,i) => {
el.setAttribute("aria-label", `Addition from ${amendmentTitle}`);
el.classList.add('diff-new');
el.id = `${section}-new-${i}`;
controlledIds.push(el.id);
wrap(el, document.createElement("ins"));
});
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;
}
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} ${controlledIds.join(" ")}`);
inp.setAttribute("aria-controls", `${section}`);
});

}
}
}
Expand Down
Loading
Loading