Handle GitHub route links in README rebasing

Alessandro committed Mar 28, 2026 at 14:47 UTC baa806558e91b3ca6fc4c60335309d28aad25c95
1 file changed +57 -19
plugins/_plugin_installer/webui/pluginInstallStore.js
+57 -19
@@ -97,30 +97,68 @@ const model = {
97 .split("/");
98 if (!owner || !repo) return html;
99
100 - const repoBlobBase = `https://github.com/${owner}/${repo}/blob/${branch}`;
100 + const repoWebBase = `https://github.com/${owner}/${repo}`;
101 + const repoBlobBase = `${repoWebBase}/blob/${branch}`;
102 + const repoRawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
103 const doc = new DOMParser().parseFromString(html, "text/html");
104 + const githubRepoRoutePrefixes = new Set([
105 + "actions",
106 + "blob",
107 + "branches",
108 + "commit",
109 + "commits",
110 + "compare",
111 + "discussions",
112 + "issues",
113 + "labels",
114 + "milestones",
115 + "packages",
116 + "projects",
117 + "pulls",
118 + "raw",
119 + "releases",
120 + "security",
121 + "tags",
122 + "tree",
123 + "wiki",
124 + ]);
125 + const shouldSkipRebase = (value) =>
126 + !value ||
127 + value.startsWith("#") ||
128 + value.startsWith("//") ||
129 + /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value);
130 +
131 + const resolveRepoPath = (value) => {
132 + if (shouldSkipRebase(value)) return null;
133 + try {
134 + const resolved = new URL(value, "https://repo-root.invalid/");
135 + return `${resolved.pathname.replace(/^\/+/, "")}${resolved.search}${resolved.hash}`;
136 + } catch {
137 + return null;
138 + }
139 + };
140 + const isRepoRoutePath = (repoPath) => {
141 + const pathOnly = repoPath
142 + .split(/[?#]/, 1)[0]
143 + .replace(/^\/+|\/+$/g, "");
144 + if (!pathOnly) return false;
145 + const firstSegment = pathOnly.split("/")[0].toLowerCase();
146 + return githubRepoRoutePrefixes.has(firstSegment);
147 + };
148
149 doc.querySelectorAll("a[href]").forEach((anchor) => {
150 const href = (anchor.getAttribute("href") || "").trim();
105 - if (
106 - !href ||
107 - href.startsWith("#") ||
108 - href.startsWith("//") ||
109 - /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href)
110 - ) {
111 - return;
112 - }
151 + const repoPath = resolveRepoPath(href);
152 + if (!repoPath) return;
153 + const base = isRepoRoutePath(repoPath) ? repoWebBase : repoBlobBase;
154 + anchor.setAttribute("href", `${base}/${repoPath}`);
155 + });
156
114 - try {
115 - const resolved = new URL(href, "https://repo-root.invalid/");
116 - const repoPath = resolved.pathname.replace(/^\/+/, "");
117 - anchor.setAttribute(
118 - "href",
119 - `${repoBlobBase}/${repoPath}${resolved.search}${resolved.hash}`,
120 - );
121 - } catch {
122 - // Leave malformed links unchanged.
123 - }
157 + doc.querySelectorAll("img[src]").forEach((image) => {
158 + const src = (image.getAttribute("src") || "").trim();
159 + const repoPath = resolveRepoPath(src);
160 + if (!repoPath) return;
161 + image.setAttribute("src", `${repoRawBase}/${repoPath}`);
162 });
163
164 return doc.body.innerHTML;