1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
(() => {
const selectEl = document.getElementById("summary-ref-select");
const commitLinkEl = document.getElementById("summary-ref-commit-link");
const browseLinkEl = document.getElementById("summary-ref-browse-link");
const logLinkEl = document.getElementById("summary-ref-log-link");
const shortlogLinkEl = document.getElementById("summary-ref-shortlog-link");
const readmeContainerEl = document.getElementById("summary-readme-container");
if (!selectEl || !commitLinkEl || !browseLinkEl || !readmeContainerEl) {
return;
}
const project = readmeContainerEl.dataset.project;
if (!project) {
return;
}
const projectApiBase = () =>
"/project/" + project.split("/").map((segment) => encodeURIComponent(segment)).join("/");
const SUBSCRIBE_BACKOFF_MS = 5000;
const rerunScripts = () => {
for (const oldScript of readmeContainerEl.querySelectorAll("script")) {
const scriptEl = document.createElement("script");
if (oldScript.src) {
scriptEl.src = oldScript.src;
} else if (oldScript.textContent) {
scriptEl.textContent = oldScript.textContent;
}
oldScript.replaceWith(scriptEl);
}
};
const loadState = async (ref) => {
const response = await fetch(
`${projectApiBase()}/summary-ref-state?ref=${encodeURIComponent(ref)}`,
);
if (!response.ok) {
return;
}
const state = await response.json();
commitLinkEl.href = state.commit_href;
commitLinkEl.textContent = state.display;
browseLinkEl.href = state.browse_href;
if (logLinkEl) {
logLinkEl.href = state.log_href;
}
if (shortlogLinkEl) {
shortlogLinkEl.href = state.shortlog_href;
}
readmeContainerEl.innerHTML = state.readme_html || "";
if (state.readme_html) {
rerunScripts();
}
};
const populateOptions = (options) => {
const previousValue = selectEl.value;
for (const opt of Array.from(selectEl.options)) {
if (opt.value !== "HEAD") {
opt.remove();
}
}
for (const optionData of options || []) {
const optionEl = document.createElement("option");
optionEl.value = optionData.value;
optionEl.textContent = `${optionData.kind}: ${optionData.label}`;
selectEl.appendChild(optionEl);
}
const stillPresent = Array.from(selectEl.options).some((opt) => opt.value === previousValue);
selectEl.value = stillPresent ? previousValue : "HEAD";
return selectEl.value;
};
const loadOptions = async () => {
const response = await fetch(`${projectApiBase()}/summary-refs`);
if (!response.ok) {
return null;
}
const data = await response.json();
return populateOptions(data.options);
};
selectEl.addEventListener("change", () => {
void loadState(selectEl.value);
});
const subscriptionController = new AbortController();
window.addEventListener("beforeunload", () => subscriptionController.abort());
const subscribeToUpdates = async () => {
while (!subscriptionController.signal.aborted) {
let resp;
try {
resp = await fetch(`${projectApiBase()}?a=heads&updates=true`, {
signal: subscriptionController.signal,
});
} catch (err) {
if (subscriptionController.signal.aborted) {
return;
}
await new Promise((resolve) => setTimeout(resolve, SUBSCRIBE_BACKOFF_MS));
continue;
}
if (subscriptionController.signal.aborted) {
return;
}
if (resp.status === 200) {
const text = await resp.text();
if (text.length > 0) {
const effectiveRef = await loadOptions();
await loadState(effectiveRef ?? selectEl.value);
}
} else if (resp.status !== 304) {
await new Promise((resolve) => setTimeout(resolve, SUBSCRIBE_BACKOFF_MS));
}
}
};
void loadOptions();
void subscribeToUpdates();
})();