Skip to content

Commit

Permalink
Merge pull request #2995 from w3c/rec-diff
Browse files Browse the repository at this point in the history
Improve amendment management
  • Loading branch information
alvestrand authored Sep 12, 2024
2 parents e8ac445 + e4c5fcb commit 39897d1
Show file tree
Hide file tree
Showing 13 changed files with 1,999 additions and 147 deletions.
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

0 comments on commit 39897d1

Please sign in to comment.