pygitweb 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. pygitweb/__init__.py +22 -0
  2. pygitweb/__meta__.py +9 -0
  3. pygitweb/actions.py +1303 -0
  4. pygitweb/addproject_permissions_test.py +114 -0
  5. pygitweb/api/__init__.py +0 -0
  6. pygitweb/api/action.py +20 -0
  7. pygitweb/api/plugins/__init__.py +0 -0
  8. pygitweb/api/plugins/project_zip.py +71 -0
  9. pygitweb/api/project.py +67 -0
  10. pygitweb/api/subpage.py +22 -0
  11. pygitweb/auth.py +387 -0
  12. pygitweb/auth.schema.json +106 -0
  13. pygitweb/auth_config.py +243 -0
  14. pygitweb/auth_oauth.py +343 -0
  15. pygitweb/auth_test.py +390 -0
  16. pygitweb/boards_permissions_test.py +113 -0
  17. pygitweb/change_queue.py +110 -0
  18. pygitweb/change_queue_routes_test.py +146 -0
  19. pygitweb/change_queue_test.py +91 -0
  20. pygitweb/comments_permissions_test.py +140 -0
  21. pygitweb/config.py +185 -0
  22. pygitweb/conftest.py +43 -0
  23. pygitweb/dependencies.py +87 -0
  24. pygitweb/formatting.py +182 -0
  25. pygitweb/formatting_test.py +68 -0
  26. pygitweb/git_helpers.py +454 -0
  27. pygitweb/git_helpers_test.py +223 -0
  28. pygitweb/gravatar.py +16 -0
  29. pygitweb/gravatar_test.py +10 -0
  30. pygitweb/hooks_install.py +278 -0
  31. pygitweb/hooks_install_test.py +363 -0
  32. pygitweb/hooks_permissions_test.py +116 -0
  33. pygitweb/main.py +887 -0
  34. pygitweb/merge_requests.py +230 -0
  35. pygitweb/merge_requests_test.py +85 -0
  36. pygitweb/mr_permissions_test.py +148 -0
  37. pygitweb/password_hash.py +74 -0
  38. pygitweb/password_hash_test.py +20 -0
  39. pygitweb/permissions.py +209 -0
  40. pygitweb/permissions_test.py +86 -0
  41. pygitweb/plugin_loader.py +83 -0
  42. pygitweb/projects.py +213 -0
  43. pygitweb/projects_test.py +186 -0
  44. pygitweb/sessions.py +73 -0
  45. pygitweb/settings.py +412 -0
  46. pygitweb/settings.schema.json +73 -0
  47. pygitweb/settings_config.py +122 -0
  48. pygitweb/settings_config_test.py +101 -0
  49. pygitweb/settings_permissions_test.py +76 -0
  50. pygitweb/static/blob-view.js +133 -0
  51. pygitweb/static/board.js +839 -0
  52. pygitweb/static/hook-install.js +53 -0
  53. pygitweb/static/icons/cloud-down.svg +1 -0
  54. pygitweb/static/icons/cloud-exclamation.svg +1 -0
  55. pygitweb/static/icons/cloud-filled.svg +1 -0
  56. pygitweb/static/icons/cloud-storm.svg +1 -0
  57. pygitweb/static/icons/cloud-up.svg +1 -0
  58. pygitweb/static/icons/cloud.svg +1 -0
  59. pygitweb/static/logo.svg +44 -0
  60. pygitweb/static/main.css +1210 -0
  61. pygitweb/static/navbar-auth.js +82 -0
  62. pygitweb/static/readme-render.js +174 -0
  63. pygitweb/static/search-page.js +139 -0
  64. pygitweb/static/summary-ref-switcher.js +130 -0
  65. pygitweb/static/themes/solarized.css +34 -0
  66. pygitweb/tasks.py +595 -0
  67. pygitweb/tasks_permissions_test.py +137 -0
  68. pygitweb/tasks_routes_test.py +430 -0
  69. pygitweb/templates/addproject.html +174 -0
  70. pygitweb/templates/blobdiff.html +57 -0
  71. pygitweb/templates/board.html +31 -0
  72. pygitweb/templates/board_card.html +44 -0
  73. pygitweb/templates/commitdiff.html +57 -0
  74. pygitweb/templates/hooks_summary_cell.html +37 -0
  75. pygitweb/templates/login.html +37 -0
  76. pygitweb/templates/merge_request_summary_cell.html +18 -0
  77. pygitweb/templates/merge_request_tag.html +90 -0
  78. pygitweb/templates/pagination.html +44 -0
  79. pygitweb/templates/preamble.html +89 -0
  80. pygitweb/templates/project_settings.html +13 -0
  81. pygitweb/templates/pygit2_settings.html +12 -0
  82. pygitweb/templates/pygitweb_settings.html +12 -0
  83. pygitweb/templates/search.html +77 -0
  84. pygitweb/templates/settings_form_field.html +27 -0
  85. pygitweb/templates/table.html +15 -0
  86. pygitweb/templates/user_profile.html +10 -0
  87. pygitweb/templates_env.py +21 -0
  88. pygitweb/timeline_cache.py +243 -0
  89. pygitweb/validation.py +75 -0
  90. pygitweb/validation_test.py +167 -0
  91. pygitweb-0.1.0.dist-info/METADATA +165 -0
  92. pygitweb-0.1.0.dist-info/RECORD +95 -0
  93. pygitweb-0.1.0.dist-info/WHEEL +5 -0
  94. pygitweb-0.1.0.dist-info/licenses/LICENSE +21 -0
  95. pygitweb-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,82 @@
1
+ (function () {
2
+ function adminAvatarSvg() {
3
+ return '<svg xmlns="http://www.w3.org/2000/svg" class="icon text-white" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3" /></svg>';
4
+ }
5
+ function userAvatarSvg() {
6
+ return '<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>';
7
+ }
8
+ var nav = document.getElementById("navbar-user");
9
+ if (!nav) return;
10
+ var link = document.getElementById("navbar-user-link");
11
+ var avatar = document.getElementById("navbar-user-avatar");
12
+ var nameEl = nav.querySelector(".navbar-user-name");
13
+ var metaEl = nav.querySelector(".navbar-user-meta");
14
+ if (!link || !avatar || !nameEl || !metaEl) return;
15
+ function resetGuestAvatar() {
16
+ avatar.classList.remove("bg-primary", "bg-teal", "text-white");
17
+ avatar.classList.add("bg-secondary");
18
+ avatar.innerHTML = userAvatarSvg();
19
+ }
20
+ function setFallbackAvatar(isAdmin) {
21
+ avatar.classList.remove("bg-secondary");
22
+ if (isAdmin) {
23
+ avatar.classList.remove("bg-teal");
24
+ avatar.classList.add("bg-primary", "text-white");
25
+ avatar.innerHTML = adminAvatarSvg();
26
+ } else {
27
+ avatar.classList.remove("bg-primary", "text-white");
28
+ avatar.classList.add("bg-teal");
29
+ avatar.innerHTML = userAvatarSvg();
30
+ }
31
+ }
32
+ function setGravatarAvatar(url, isAdmin) {
33
+ avatar.classList.remove("bg-secondary", "bg-primary", "bg-teal", "text-white");
34
+ var img = document.createElement("img");
35
+ img.src = url;
36
+ img.alt = "";
37
+ img.width = 32;
38
+ img.height = 32;
39
+ img.onerror = function () {
40
+ setFallbackAvatar(isAdmin);
41
+ };
42
+ avatar.replaceChildren(img);
43
+ }
44
+ fetch("/auth/status", { credentials: "same-origin" })
45
+ .then(function (r) {
46
+ return r.json();
47
+ })
48
+ .then(function (s) {
49
+ if (!s.auth_enabled) {
50
+ link.removeAttribute("href");
51
+ link.setAttribute("aria-disabled", "true");
52
+ link.classList.add("pe-none", "opacity-75");
53
+ nameEl.textContent = "Guest";
54
+ metaEl.textContent = "Sign-in not used";
55
+ return;
56
+ }
57
+ link.classList.remove("pe-none", "opacity-75");
58
+ link.removeAttribute("aria-disabled");
59
+ if (s.username) {
60
+ nav.setAttribute("data-logged-in", "true");
61
+ link.href = "/user";
62
+ nameEl.textContent = s.username;
63
+ metaEl.textContent = "Signed in";
64
+ var isAdmin = String(s.username).toLowerCase() === "admin";
65
+ if (s.gravatar_url) {
66
+ setGravatarAvatar(s.gravatar_url, isAdmin);
67
+ } else {
68
+ setFallbackAvatar(isAdmin);
69
+ }
70
+ return;
71
+ }
72
+ nav.setAttribute("data-logged-in", "false");
73
+ link.href = "/login";
74
+ nameEl.textContent = "Guest";
75
+ metaEl.textContent = "Not signed in";
76
+ resetGuestAvatar();
77
+ })
78
+ .catch(function () {
79
+ link.href = "/login";
80
+ resetGuestAvatar();
81
+ });
82
+ })();
@@ -0,0 +1,174 @@
1
+ (function() {
2
+ var el = document.getElementById("Showdown-script");
3
+ function isRelativeLink(href) {
4
+ if (!href || href.charAt(0) === "#") return false;
5
+ if (href.charAt(0) === "/") return false;
6
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href)) return false;
7
+ if (href.indexOf("//") === 0) return false;
8
+ return true;
9
+ }
10
+ function rewriteRelativeLinks(container, blobBase, blobDir) {
11
+ if (!blobBase) return;
12
+ var links = container.querySelectorAll("a[href]");
13
+ for (var i = 0; i < links.length; i++) {
14
+ var a = links[i];
15
+ var href = a.getAttribute("href");
16
+ if (href && isRelativeLink(href)) {
17
+ var path = href.replace(/^\.\//, "");
18
+ var fullPath = blobDir ? blobDir + "/" + path : path;
19
+ a.setAttribute("href", blobBase + encodeURIComponent(fullPath));
20
+ }
21
+ }
22
+ }
23
+ function slugifyId(id) {
24
+ if (!id) return "";
25
+ return id.replace(/\s+/g, "-").replace(/[^\w-]/g, "").toLowerCase();
26
+ }
27
+ function ensureHeadingIds(container) {
28
+ var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
29
+ for (var i = 0; i < headings.length; i++) {
30
+ var h = headings[i];
31
+ if (!h.id) {
32
+ var text = (h.textContent || "").trim();
33
+ var base = slugifyId(text) || "section";
34
+ var id = base;
35
+ var n = 0;
36
+ while (container.ownerDocument.getElementById(id)) id = base + "-" + (++n);
37
+ h.id = id;
38
+ }
39
+ }
40
+ }
41
+ function addSectionLinks(container) {
42
+ var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
43
+ for (var i = 0; i < headings.length; i++) {
44
+ var h = headings[i];
45
+ var id = h.id;
46
+ if (!id) continue;
47
+ var wrap = document.createElement("span");
48
+ wrap.className = "readme-heading-wrap";
49
+ h.parentNode.insertBefore(wrap, h);
50
+ wrap.appendChild(h);
51
+ var anchor = document.createElement("a");
52
+ anchor.href = "#" + id;
53
+ anchor.className = "readme-section-anchor";
54
+ anchor.setAttribute("aria-label", "Copy link to section");
55
+ anchor.textContent = "\u00A7";
56
+ anchor.addEventListener("click", function(e) {
57
+ e.preventDefault();
58
+ var targetId = this.getAttribute("href").slice(1);
59
+ var url = window.location.origin + window.location.pathname + window.location.search + "#" + targetId;
60
+ if (navigator.clipboard && navigator.clipboard.writeText) {
61
+ navigator.clipboard.writeText(url);
62
+ } else {
63
+ window.location.hash = targetId;
64
+ }
65
+ });
66
+ wrap.insertBefore(anchor, h);
67
+ }
68
+ }
69
+ function buildToc(container) {
70
+ var headings = container.querySelectorAll("h1, h2, h3, h4, h5, h6");
71
+ if (headings.length === 0) return null;
72
+ var nav = document.createElement("nav");
73
+ nav.className = "readme-toc";
74
+ nav.setAttribute("aria-label", "Table of contents");
75
+ var list = document.createElement("ul");
76
+ list.className = "readme-toc-list";
77
+ var tagLevel = { H1: 1, H2: 2, H3: 3, H4: 4, H5: 5, H6: 6 };
78
+ for (var i = 0; i < headings.length; i++) {
79
+ var h = headings[i];
80
+ var id = h.id;
81
+ if (!id) continue;
82
+ var level = tagLevel[h.tagName] || 1;
83
+ var li = document.createElement("li");
84
+ li.className = "readme-toc-item readme-toc-level-" + level;
85
+ li.setAttribute("data-id", id);
86
+ var a = document.createElement("a");
87
+ a.href = "#" + id;
88
+ a.className = "readme-toc-link";
89
+ a.textContent = (h.textContent || "").trim();
90
+ a.addEventListener("click", function(e) {
91
+ e.preventDefault();
92
+ var targetId = this.getAttribute("href").slice(1);
93
+ var target = document.getElementById(targetId);
94
+ if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
95
+ if (history.replaceState) history.replaceState(null, "", "#" + targetId);
96
+ });
97
+ li.appendChild(a);
98
+ list.appendChild(li);
99
+ }
100
+ nav.appendChild(list);
101
+ return nav;
102
+ }
103
+ function mountTocAndScrollSpy(tocEl, container) {
104
+ var page = document.querySelector(".page");
105
+ if (!page) return;
106
+ var tocPlaceholder = document.createElement("div");
107
+ tocPlaceholder.className = "readme-toc-mount";
108
+ tocPlaceholder.hidden = true;
109
+ page.appendChild(tocPlaceholder);
110
+ tocPlaceholder.appendChild(tocEl);
111
+ var items = tocEl.querySelectorAll(".readme-toc-item");
112
+ function updateTocVisibility() {
113
+ var readmeTop = container.getBoundingClientRect().top;
114
+ tocPlaceholder.hidden = readmeTop >= 0;
115
+ }
116
+ function updateActive() {
117
+ updateTocVisibility();
118
+ var fromTop = window.scrollY + 8;
119
+ var current = null;
120
+ var headings = container.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]");
121
+ for (var i = headings.length - 1; i >= 0; i--) {
122
+ if (headings[i].getBoundingClientRect().top + window.scrollY <= fromTop) {
123
+ current = headings[i].id;
124
+ break;
125
+ }
126
+ }
127
+ if (!current && headings.length) current = headings[0].id;
128
+ for (var j = 0; j < items.length; j++) {
129
+ var on = items[j].getAttribute("data-id") === current;
130
+ items[j].classList.toggle("readme-toc-active", on);
131
+ }
132
+ }
133
+ var ticking = false;
134
+ function onScrollOrResize() {
135
+ if (!ticking) {
136
+ requestAnimationFrame(function() {
137
+ updateActive();
138
+ ticking = false;
139
+ });
140
+ ticking = true;
141
+ }
142
+ }
143
+ window.addEventListener("scroll", onScrollOrResize, { passive: true });
144
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
145
+ updateActive();
146
+ }
147
+ function b64ToUtf8(b64) {
148
+ var binary = atob(b64);
149
+ var bytes = new Uint8Array(binary.length);
150
+ for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
151
+ return new TextDecoder().decode(bytes);
152
+ }
153
+ function render() {
154
+ var container = document.getElementById("readme-container");
155
+ var enc = container && container.getAttribute("data-readme-b64");
156
+ var blobBase = container && container.getAttribute("data-blob-base");
157
+ var blobDir = container && container.getAttribute("data-blob-dir") || "";
158
+ if (!container || !enc || typeof showdown === "undefined") return;
159
+ try {
160
+ var raw = b64ToUtf8(enc);
161
+ var conv = new showdown.Converter({ ghCompatibleHeaderId: true });
162
+ container.innerHTML = conv.makeHtml(raw);
163
+ ensureHeadingIds(container);
164
+ rewriteRelativeLinks(container, blobBase, blobDir);
165
+ addSectionLinks(container);
166
+ var toc = buildToc(container);
167
+ if (toc) mountTocAndScrollSpy(toc, container);
168
+ } catch (e) {}
169
+ }
170
+ if (el) {
171
+ if (el.getAttribute("data-loaded")) render();
172
+ else el.addEventListener("load", function() { el.setAttribute("data-loaded", "1"); render(); });
173
+ }
174
+ })();
@@ -0,0 +1,139 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ const apiUrl = window.SEARCH_API_URL;
5
+ const elPattern = document.getElementById("search-patterns");
6
+ const elPaths = document.getElementById("search-paths");
7
+ const elGlobs = document.getElementById("search-globs");
8
+ const elSort = document.getElementById("search-sort");
9
+ const elMax = document.getElementById("search-max-count");
10
+ const elHead = document.getElementById("search-heading");
11
+ const elMulti = document.getElementById("search-multiline");
12
+ const elStatus = document.getElementById("search-status");
13
+ const elResults = document.getElementById("search-results");
14
+ const elAdvanced = document.getElementById("search-advanced");
15
+
16
+ function escapeHtml(s) {
17
+ return String(s).replace(/[&<>"']/g, function (c) {
18
+ return ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c];
19
+ });
20
+ }
21
+
22
+ function readUrl() {
23
+ const p = new URLSearchParams(window.location.search);
24
+ if (p.has("patterns")) elPattern.value = p.get("patterns");
25
+ if (p.has("paths")) elPaths.value = p.get("paths");
26
+ if (p.has("globs")) elGlobs.value = p.get("globs");
27
+ if (p.has("sort")) elSort.value = p.get("sort");
28
+ elMax.value = p.has("max_count") ? p.get("max_count") : "25";
29
+ if (p.has("heading")) elHead.checked = p.get("heading") === "true";
30
+ if (p.has("multiline")) elMulti.checked = p.get("multiline") === "true";
31
+ const anyAdvanced = ["paths", "globs", "sort", "multiline"].some((k) => p.has(k));
32
+ if (anyAdvanced) elAdvanced.open = true;
33
+ }
34
+
35
+ function buildParams() {
36
+ const qs = new URLSearchParams();
37
+ qs.set("a", "search");
38
+ const pat = elPattern.value.trim();
39
+ if (pat) qs.set("patterns", pat);
40
+ const paths = elPaths.value.trim();
41
+ if (paths) qs.set("paths", paths);
42
+ const globs = elGlobs.value.trim();
43
+ if (globs) qs.set("globs", globs);
44
+ if (elSort.value) qs.set("sort", elSort.value);
45
+ const max = elMax.value.trim();
46
+ if (max) qs.set("max_count", max);
47
+ qs.set("heading", elHead.checked ? "true" : "false");
48
+ qs.set("multiline", elMulti.checked ? "true" : "false");
49
+ return qs;
50
+ }
51
+
52
+ function persistUrl(qs) {
53
+ const url = new URL(window.location.href);
54
+ const keep = new Set(Array.from(qs.keys()).filter((k) => k !== "a"));
55
+ Array.from(url.searchParams.keys()).forEach((k) => {
56
+ if (!keep.has(k)) url.searchParams.delete(k);
57
+ });
58
+ for (const [k, v] of qs.entries()) {
59
+ if (k === "a") continue;
60
+ url.searchParams.set(k, v);
61
+ }
62
+ window.history.replaceState(null, "", url);
63
+ }
64
+
65
+ function renderResults(data) {
66
+ const lines = Array.isArray(data.results) ? data.results : [];
67
+ if (lines.length === 0) {
68
+ elResults.innerHTML = "";
69
+ return;
70
+ }
71
+ const items = lines.map((line) => {
72
+ return '<div class="search-result"><pre class="search-result-line">' + escapeHtml(line) + "</pre></div>";
73
+ });
74
+ elResults.innerHTML = items.join("");
75
+ }
76
+
77
+ let pendingCtrl = null;
78
+
79
+ async function runSearch() {
80
+ const pat = elPattern.value.trim();
81
+ if (!pat) {
82
+ elStatus.textContent = "";
83
+ elResults.innerHTML = "";
84
+ persistUrl(new URLSearchParams());
85
+ return;
86
+ }
87
+ const qs = buildParams();
88
+ persistUrl(qs);
89
+ if (pendingCtrl) pendingCtrl.abort();
90
+ pendingCtrl = new AbortController();
91
+ elStatus.textContent = "Searching\u2026";
92
+ elResults.classList.add("is-loading");
93
+ try {
94
+ const resp = await fetch(apiUrl + "?" + qs.toString(), {
95
+ signal: pendingCtrl.signal,
96
+ headers: { Accept: "application/json" },
97
+ });
98
+ if (!resp.ok) {
99
+ let detail = resp.statusText;
100
+ try {
101
+ const j = await resp.json();
102
+ if (j && j.detail) detail = j.detail;
103
+ } catch (_) { /* ignore */ }
104
+ elStatus.textContent = "Error " + resp.status + ": " + detail;
105
+ elResults.innerHTML = "";
106
+ return;
107
+ }
108
+ const data = await resp.json();
109
+ const n = (data.results || []).length;
110
+ elStatus.textContent = n + " match" + (n === 1 ? "" : "es");
111
+ renderResults(data);
112
+ } catch (err) {
113
+ if (err && err.name === "AbortError") return;
114
+ elStatus.textContent = "Error: " + (err && err.message ? err.message : String(err));
115
+ } finally {
116
+ elResults.classList.remove("is-loading");
117
+ }
118
+ }
119
+
120
+ let debounceTimer = null;
121
+ function debounced() {
122
+ clearTimeout(debounceTimer);
123
+ debounceTimer = setTimeout(runSearch, 500);
124
+ }
125
+
126
+ for (const el of [elPattern, elPaths, elGlobs, elMax]) el.addEventListener("input", debounced);
127
+ for (const el of [elSort, elHead, elMulti]) el.addEventListener("change", debounced);
128
+
129
+ elPattern.addEventListener("keydown", function (event) {
130
+ if (event.key === "Enter") {
131
+ event.preventDefault();
132
+ clearTimeout(debounceTimer);
133
+ runSearch();
134
+ }
135
+ });
136
+
137
+ readUrl();
138
+ runSearch();
139
+ })();
@@ -0,0 +1,130 @@
1
+ (() => {
2
+ const selectEl = document.getElementById("summary-ref-select");
3
+ const commitLinkEl = document.getElementById("summary-ref-commit-link");
4
+ const browseLinkEl = document.getElementById("summary-ref-browse-link");
5
+ const logLinkEl = document.getElementById("summary-ref-log-link");
6
+ const shortlogLinkEl = document.getElementById("summary-ref-shortlog-link");
7
+ const treeContainerEl = document.getElementById("summary-tree-container");
8
+ const treeBrowseLinkEl = document.getElementById("summary-tree-browse-link");
9
+ const readmeContainerEl = document.getElementById("summary-readme-container");
10
+ if (!selectEl || !commitLinkEl || !browseLinkEl || !readmeContainerEl) {
11
+ return;
12
+ }
13
+
14
+ const project = readmeContainerEl.dataset.project;
15
+ if (!project) {
16
+ return;
17
+ }
18
+
19
+ const projectApiBase = () =>
20
+ "/project/" + project.split("/").map((segment) => encodeURIComponent(segment)).join("/");
21
+
22
+ const SUBSCRIBE_BACKOFF_MS = 5000;
23
+
24
+ const rerunScripts = () => {
25
+ for (const oldScript of readmeContainerEl.querySelectorAll("script")) {
26
+ const scriptEl = document.createElement("script");
27
+ if (oldScript.src) {
28
+ scriptEl.src = oldScript.src;
29
+ } else if (oldScript.textContent) {
30
+ scriptEl.textContent = oldScript.textContent;
31
+ }
32
+ oldScript.replaceWith(scriptEl);
33
+ }
34
+ };
35
+
36
+ const loadState = async (ref) => {
37
+ const response = await fetch(
38
+ `${projectApiBase()}/summary-ref-state?ref=${encodeURIComponent(ref)}`,
39
+ );
40
+ if (!response.ok) {
41
+ return;
42
+ }
43
+ const state = await response.json();
44
+ commitLinkEl.href = state.commit_href;
45
+ commitLinkEl.textContent = state.display;
46
+ browseLinkEl.href = state.browse_href;
47
+ if (logLinkEl) {
48
+ logLinkEl.href = state.log_href;
49
+ }
50
+ if (shortlogLinkEl) {
51
+ shortlogLinkEl.href = state.shortlog_href;
52
+ }
53
+ if (treeContainerEl) {
54
+ treeContainerEl.innerHTML = state.tree_html || "";
55
+ }
56
+ if (treeBrowseLinkEl) {
57
+ treeBrowseLinkEl.href = state.browse_href;
58
+ }
59
+ readmeContainerEl.innerHTML = state.readme_html || "";
60
+ if (state.readme_html) {
61
+ rerunScripts();
62
+ }
63
+ };
64
+
65
+ const populateOptions = (options) => {
66
+ const previousValue = selectEl.value;
67
+ for (const opt of Array.from(selectEl.options)) {
68
+ if (opt.value !== "HEAD") {
69
+ opt.remove();
70
+ }
71
+ }
72
+ for (const optionData of options || []) {
73
+ const optionEl = document.createElement("option");
74
+ optionEl.value = optionData.value;
75
+ optionEl.textContent = `${optionData.kind}: ${optionData.label}`;
76
+ selectEl.appendChild(optionEl);
77
+ }
78
+ const stillPresent = Array.from(selectEl.options).some((opt) => opt.value === previousValue);
79
+ selectEl.value = stillPresent ? previousValue : "HEAD";
80
+ return selectEl.value;
81
+ };
82
+
83
+ const loadOptions = async () => {
84
+ const response = await fetch(`${projectApiBase()}/summary-refs`);
85
+ if (!response.ok) {
86
+ return null;
87
+ }
88
+ const data = await response.json();
89
+ return populateOptions(data.options);
90
+ };
91
+
92
+ selectEl.addEventListener("change", () => {
93
+ void loadState(selectEl.value);
94
+ });
95
+
96
+ const subscriptionController = new AbortController();
97
+ window.addEventListener("beforeunload", () => subscriptionController.abort());
98
+
99
+ const subscribeToUpdates = async () => {
100
+ while (!subscriptionController.signal.aborted) {
101
+ let resp;
102
+ try {
103
+ resp = await fetch(`${projectApiBase()}?a=heads&updates=true`, {
104
+ signal: subscriptionController.signal,
105
+ });
106
+ } catch (err) {
107
+ if (subscriptionController.signal.aborted) {
108
+ return;
109
+ }
110
+ await new Promise((resolve) => setTimeout(resolve, SUBSCRIBE_BACKOFF_MS));
111
+ continue;
112
+ }
113
+ if (subscriptionController.signal.aborted) {
114
+ return;
115
+ }
116
+ if (resp.status === 200) {
117
+ const text = await resp.text();
118
+ if (text.length > 0) {
119
+ const effectiveRef = await loadOptions();
120
+ await loadState(effectiveRef ?? selectEl.value);
121
+ }
122
+ } else if (resp.status !== 304) {
123
+ await new Promise((resolve) => setTimeout(resolve, SUBSCRIBE_BACKOFF_MS));
124
+ }
125
+ }
126
+ };
127
+
128
+ void loadOptions();
129
+ void subscribeToUpdates();
130
+ })();
@@ -0,0 +1,34 @@
1
+ :root {
2
+ --pgw-bg: #002b36;
3
+ --pgw-bg-raised: #073642;
4
+ --pgw-bg-muted: #0d3d4a;
5
+ --pgw-border: #586e75;
6
+ --pgw-border-muted: #465c63;
7
+
8
+ --pgw-text: #839496;
9
+ --pgw-text-muted: #657b83;
10
+ --pgw-text-dim: #586e75;
11
+
12
+ --pgw-link: #268bd2;
13
+ --pgw-link-visited: #6c71c4;
14
+ --pgw-link-hover: #dc322f;
15
+
16
+ --pgw-accent-bg: #073642;
17
+ --pgw-accent-text: #93a1a1;
18
+ --pgw-title-bg: #073642;
19
+ --pgw-title-hover-bg: #0d3d4a;
20
+
21
+ --pgw-code-bg: #002b36;
22
+ --pgw-code-border: #073642;
23
+
24
+ --pgw-table-stripe: #073642;
25
+ --pgw-table-hover: #0d3d4a;
26
+
27
+ --pgw-add-fg: #859900;
28
+ --pgw-add-bg: #073642;
29
+ --pgw-rem-fg: #dc322f;
30
+ --pgw-rem-bg: #4a0a0a;
31
+ --pgw-info-fg: #657b83;
32
+
33
+ color-scheme: dark;
34
+ }