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;