enhance README link handling in plugin installer
- Introduced _rebaseReadmeLinks function to update README links to point to the correct GitHub blob URLs based on the repository and branch. - Updated readmeContent processing to apply link adjustments and ensure links open in new tabs.
Alessandro committed
Mar 21, 2026 at 13:09 UTC
e59cca3008ec4ba780d9781e62f4fd26c4f3cbd7
1 file changed
+50
-1
plugins/_plugin_installer/webui/pluginInstallStore.js
+50
-1
@@ -1,5 +1,6 @@
1
import { createStore } from "/js/AlpineStore.js";
2
import * as api from "/js/api.js";
3
+import { addBlankTargetsToLinks } from "/js/messages.js";
4
import { openModal } from "/js/modals.js";
5
import { marked } from "/vendor/marked/marked.esm.js";
6
import { toastFrontendSuccess, toastFrontendError } from "/components/notifications/notification-store.js";
@@ -78,6 +79,52 @@ const model = {
79
return url.replace("https://github.com/", "https://raw.githubusercontent.com/");
80
},
81
82
+ _rebaseReadmeLinks(html, githubUrl, branch) {
83
+ if (!html || typeof html !== "string" || !githubUrl || !branch) return html;
84
+
85
+ let repoUrl;
86
+ try {
87
+ repoUrl = new URL(githubUrl.trim().replace(/\.git$/i, ""));
88
+ } catch {
89
+ return html;
90
+ }
91
+
92
+ if (repoUrl.hostname !== "github.com") return html;
93
+
94
+ const [owner, repo] = repoUrl.pathname
95
+ .replace(/^\/+|\/+$/g, "")
96
+ .split("/");
97
+ if (!owner || !repo) return html;
98
+
99
+ const repoBlobBase = `https://github.com/${owner}/${repo}/blob/${branch}`;
100
+ const doc = new DOMParser().parseFromString(html, "text/html");
101
+
102
+ doc.querySelectorAll("a[href]").forEach((anchor) => {
103
+ const href = (anchor.getAttribute("href") || "").trim();
104
+ if (
105
+ !href ||
106
+ href.startsWith("#") ||
107
+ href.startsWith("//") ||
108
+ /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href)
109
+ ) {
110
+ return;
111
+ }
112
+
113
+ try {
114
+ const resolved = new URL(href, "https://repo-root.invalid/");
115
+ const repoPath = resolved.pathname.replace(/^\/+/, "");
116
+ anchor.setAttribute(
117
+ "href",
118
+ `${repoBlobBase}/${repoPath}${resolved.search}${resolved.hash}`,
119
+ );
120
+ } catch {
121
+ // Leave malformed links unchanged.
122
+ }
123
+ });
124
+
125
+ return doc.body.innerHTML;
126
+ },
127
+
128
_pluginPrimaryTag(plugin) {
129
const tags = Array.isArray(plugin?.tags) ? plugin.tags.filter(Boolean) : [];
130
return tags[0] || "";
@@ -474,7 +521,9 @@ const model = {
521
if (!response.ok) continue;
522
523
const readme = await response.text();
477
- this.readmeContent = marked.parse(readme, { breaks: true });
524
+ let html = marked.parse(readme, { breaks: true });
525
+ html = this._rebaseReadmeLinks(html, plugin?.github, branch);
526
+ this.readmeContent = addBlankTargetsToLinks(html);
527
return;
528
} catch (error) {
529
lastError = error;