|
1 | 1 | import { createStore } from "/js/AlpineStore.js"; |
2 | 2 | import * as api from "/js/api.js"; |
3 | | -import { addBlankTargetsToLinks } from "/js/messages.js"; |
4 | 3 | import { openModal } from "/js/modals.js"; |
5 | | -import { marked } from "/vendor/marked/marked.esm.js"; |
| 4 | +import { renderSafeMarkdown } from "/js/safe-markdown.js"; |
6 | 5 | import { toastFrontendSuccess, toastFrontendError } from "/components/notifications/notification-store.js"; |
7 | 6 | import { showConfirmDialog } from "/js/confirmDialog.js"; |
8 | 7 | import { store as imageViewerStore } from "/components/modals/image-viewer/image-viewer-store.js"; |
@@ -80,90 +79,6 @@ const model = { |
80 | 79 | return url.replace("https://github.com/", "https://raw.githubusercontent.com/"); |
81 | 80 | }, |
82 | 81 |
|
83 | | - _rebaseReadmeLinks(html, githubUrl, branch) { |
84 | | - if (!html || typeof html !== "string" || !githubUrl || !branch) return html; |
85 | | - |
86 | | - let repoUrl; |
87 | | - try { |
88 | | - repoUrl = new URL(githubUrl.trim().replace(/\.git$/i, "")); |
89 | | - } catch { |
90 | | - return html; |
91 | | - } |
92 | | - |
93 | | - if (repoUrl.hostname !== "github.com") return html; |
94 | | - |
95 | | - const [owner, repo] = repoUrl.pathname |
96 | | - .replace(/^\/+|\/+$/g, "") |
97 | | - .split("/"); |
98 | | - if (!owner || !repo) return html; |
99 | | - |
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(); |
151 | | - const repoPath = resolveRepoPath(href); |
152 | | - if (!repoPath) return; |
153 | | - const base = isRepoRoutePath(repoPath) ? repoWebBase : repoBlobBase; |
154 | | - anchor.setAttribute("href", `${base}/${repoPath}`); |
155 | | - }); |
156 | | - |
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; |
165 | | - }, |
166 | | - |
167 | 82 | _pluginPrimaryTag(plugin) { |
168 | 83 | const tags = Array.isArray(plugin?.tags) ? plugin.tags.filter(Boolean) : []; |
169 | 84 | return tags[0] || ""; |
@@ -589,9 +504,10 @@ const model = { |
589 | 504 | if (!response.ok) continue; |
590 | 505 |
|
591 | 506 | const readme = await response.text(); |
592 | | - let html = marked.parse(readme, { breaks: true }); |
593 | | - html = this._rebaseReadmeLinks(html, plugin?.github, branch); |
594 | | - this.readmeContent = addBlankTargetsToLinks(html); |
| 507 | + this.readmeContent = renderSafeMarkdown(readme, { |
| 508 | + githubUrl: plugin?.github, |
| 509 | + branch, |
| 510 | + }); |
595 | 511 | return; |
596 | 512 | } catch (error) { |
597 | 513 | lastError = error; |
|
0 commit comments