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
124
125
126
127
128
129
130
131
(() => {
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 treeContainerEl = document.getElementById("summary-tree-container");
const treeBrowseLinkEl = document.getElementById("summary-tree-browse-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;
}
if (treeContainerEl) {
treeContainerEl.innerHTML = state.tree_html || "";
}
if (treeBrowseLinkEl) {
treeBrowseLinkEl.href = state.browse_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();
})();