diff --git a/src/core/github.js b/src/core/github.js
index fe4b77fc59..62afa9b4ab 100644
--- a/src/core/github.js
+++ b/src/core/github.js
@@ -153,6 +153,7 @@ export async function run(conf) {
repoURL: ghURL.href,
apiBase: githubAPI,
fullName: `${org}/${repo}`,
+ commitHistoryURL: commitHistoryURL.href,
};
resolveGithubPromise(normalizedGHObj);
diff --git a/src/w3c/headers.js b/src/w3c/headers.js
index b2898f37a8..1a208fcc05 100644
--- a/src/w3c/headers.js
+++ b/src/w3c/headers.js
@@ -267,7 +267,7 @@ function validateDateAndRecover(conf, prop, fallbackDate = new Date()) {
return new Date(ISODate.format(new Date()));
}
-export function run(conf) {
+export async function run(conf) {
if (!conf.specStatus) {
const msg = docLink`Missing required configuration: ${"[specStatus]"}.`;
const hint = docLink`Please select an appropriate status from ${"[specStatus]"} based on your W3C group. If in doubt, use \`"unofficial"\`.`;
@@ -506,6 +506,7 @@ export function run(conf) {
conf.publishISODate = conf.publishDate.toISOString();
conf.shortISODate = ISODate.format(conf.publishDate);
validatePatentPolicies(conf);
+ await deriveHistoryURI(conf);
// configuration done - yay!
@@ -697,6 +698,36 @@ export function run(conf) {
});
}
+async function deriveHistoryURI(conf) {
+ if (!conf.shortName || conf.historyURI === null) {
+ return; // Nothing to do
+ }
+
+ const historyURL = new URL(
+ conf.historyURI ?? conf.shortName,
+ "https://www.w3.org/standards/history/"
+ );
+
+ // If it's on the Rec Track or it's TR worthy, then it has a history.
+ const willHaveHistory = [...recTrackStatus, ...W3CNotes, ...maybeRecTrack];
+ if (willHaveHistory.includes(conf.specStatus)) {
+ conf.historyURI = historyURL.href;
+ return;
+ }
+
+ // Do a fetch HEAD request to see if the history exists...
+ // We don't discriminate... if it's on the W3C website with a history,
+ // we show it.
+ try {
+ const response = await fetch(historyURL, { method: "HEAD" });
+ if (response.ok) {
+ conf.historyURI = response.url;
+ }
+ } catch {
+ // Ignore fetch errors
+ }
+}
+
function validatePatentPolicies(conf) {
if (!conf.wgPatentPolicy) return;
const policies = new Set([].concat(conf.wgPatentPolicy));
diff --git a/src/w3c/templates/headers.js b/src/w3c/templates/headers.js
index 1ca09ba76d..b0d742458a 100644
--- a/src/w3c/templates/headers.js
+++ b/src/w3c/templates/headers.js
@@ -183,6 +183,7 @@ export default (conf, options) => {
${conf.edDraftURI}
`
: ""}
+ ${renderHistory(conf)}
${conf.testSuiteURI
? html`
${l10n.test_suite}
@@ -273,6 +274,28 @@ export default (conf, options) => {
`;
};
+function renderHistory(conf) {
+ if (!conf.historyURI && !conf.github) return;
+ const ddElements = [];
+ if (conf.historyURI) {
+ const dd = html`
+ ${l10n.publication_history}
+ `;
+ ddElements.push(dd);
+ }
+ if (conf.github) {
+ const dd = html`
+
+ ${l10n.commit_history}
+
+ `;
+ ddElements.push(dd);
+ }
+
+ return html`${l10n.history}
+ ${ddElements}`;
+}
+
function renderSpecTitle(conf) {
const specType = conf.isCR ? conf.longStatus : conf.textStatus;
const preamble = conf.prependW3C
diff --git a/tests/spec/w3c/headers-spec.js b/tests/spec/w3c/headers-spec.js
index 76bcb00897..d363ebc9c7 100644
--- a/tests/spec/w3c/headers-spec.js
+++ b/tests/spec/w3c/headers-spec.js
@@ -8,6 +8,8 @@ import {
makeStandardOps,
} from "../SpecHelper.js";
+import { recTrackStatus } from "../../../src/w3c/headers.js";
+
const findContent = string => {
return ({ textContent }) => textContent.trim() === string;
};
@@ -287,9 +289,6 @@ describe("W3C — Headers", () => {
expect(dd.nextElementSibling.nextElementSibling.textContent).toContain(
"FORMER EDITOR 3"
);
- expect(
- dd.nextElementSibling.nextElementSibling.nextElementSibling
- ).toBeNull();
});
});
it("takes a single editors into account", async () => {
@@ -2253,6 +2252,121 @@ describe("W3C — Headers", () => {
});
});
+ describe("History", () => {
+ it("shows the publication history of the spec", async () => {
+ const ops = makeStandardOps({ shortName: "test", specStatus: "WD" });
+ const doc = await makeRSDoc(ops);
+ const [history] = contains(doc, ".head dt", "History:");
+ expect(history).toBeTruthy();
+ expect(history.nextElementSibling).toBeTruthy();
+ const historyLink = history.nextElementSibling.querySelector("a");
+ expect(historyLink).toBeTruthy();
+ expect(historyLink.href).toBe(
+ "https://www.w3.org/standards/history/test"
+ );
+ expect(historyLink.textContent).toContain("Publication history");
+ });
+
+ it("includes a dd for the commit history of the document", async () => {
+ const ops = makeStandardOps({
+ github: "w3c/respec",
+ shortName: "test",
+ specStatus: "WD",
+ });
+ const doc = await makeRSDoc(ops);
+ const [commitHistory] = contains(doc, ".head dd>a", "Commit history");
+ expect(commitHistory.href).toBe("https://github.com/w3c/respec/commits/");
+ const [publicationHistory] = contains(
+ doc,
+ ".head dd>a",
+ "Publication history"
+ );
+ expect(publicationHistory.href).toBeTruthy(
+ "https://www.w3.org/standards/history/respec"
+ );
+ });
+
+ it("includes a dd for the commit history, but excludes a publication history for unpublished types", async () => {
+ for (const specStatus of ["unofficial", "base"]) {
+ const ops = makeStandardOps({
+ github: "my/some-repo",
+ shortName: "test",
+ specStatus,
+ });
+ const doc = await makeRSDoc(ops);
+ const [commitHistory] = contains(doc, ".head dd>a", "Commit history");
+ expect(commitHistory).withContext(specStatus).toBeTruthy();
+ const publicationHistory = contains(
+ doc,
+ ".head dd>a",
+ "Publication history"
+ );
+ expect(publicationHistory.length).withContext(specStatus).toBe(0);
+ }
+ });
+
+ it("allows overriding the historyURI", async () => {
+ const ops = makeStandardOps({
+ shortName: "test",
+ specStatus: "WD",
+ historyURI: "http://example.com/history",
+ });
+ const doc = await makeRSDoc(ops);
+ const [history] = contains(doc, ".head dt", "History:");
+ expect(history).toBeTruthy();
+ expect(history.nextElementSibling).toBeTruthy();
+ const historyLink = history.nextElementSibling.querySelector("a");
+ expect(historyLink).toBeTruthy();
+ expect(historyLink.href).toBe("http://example.com/history");
+ });
+
+ it("allowing removing the history entirely buy nulling it out", async () => {
+ const ops = makeStandardOps({
+ shortName: "test",
+ specStatus: "WD",
+ historyURI: null,
+ });
+ const doc = await makeRSDoc(ops);
+ const [history] = contains(doc, ".head dt", "History:");
+ expect(history).toBeFalsy();
+ });
+
+ it("derives the historyURI automatically when it's missing, but the document is on TR", async () => {
+ const ops = makeStandardOps({
+ shortName: "payment-request",
+ specStatus: "ED",
+ });
+ const doc = await makeRSDoc(ops);
+ const [history] = contains(doc, ".head dt", "History:");
+ expect(history).toBeTruthy();
+ expect(history.nextElementSibling).toBeTruthy();
+ const historyLink = history.nextElementSibling.querySelector("a");
+ expect(historyLink).toBeTruthy();
+ expect(historyLink.href).toBe(
+ "https://www.w3.org/standards/history/payment-request"
+ );
+ });
+
+ it("includes the history for all rec-track status docs", async () => {
+ for (const specStatus of recTrackStatus) {
+ const shortName = `${specStatus}-test`;
+ const ops = makeStandardOps({
+ shortName,
+ specStatus,
+ });
+ const doc = await makeRSDoc(ops);
+ const [history] = contains(doc, ".head dt", "History:");
+ expect(history).withContext(specStatus).toBeTruthy();
+ expect(history.nextElementSibling).withContext(specStatus).toBeTruthy();
+ const historyLink = history.nextElementSibling.querySelector("a");
+ expect(historyLink).withContext(specStatus).toBeTruthy();
+ expect(historyLink.href)
+ .withContext(specStatus)
+ .toBe(`https://www.w3.org/standards/history/${shortName}`);
+ }
+ });
+ });
+
describe("Add Preview Status in title", () => {
it("when document title is present", async () => {
const ops = makeStandardOps();