Commit b1edcc8d0d
b1edcc8d0db15075ef8b5da5e6e3e8315ba2f6e9
parent: 76b847b463
Unsigned
cmc <hello@cleberg.net> · 2026-08-28 19:01 UTC
Resolve repo-relative README links and images to gitbay URLs
Pass the instance host and owner/repo into OrgSwift's render options so relative
references resolve: images against /{owner}/{repo}/raw/HEAD/ (raw bytes) and links
against /blob/HEAD/ (the file page), matching gitbay's routes. Absolute and
in-page (#anchor) references are unchanged.
Layout: unified · split
gitbay/Views/Repos/OrgReadmeWebView.swift
+2 −1
| @@ -9,6 +9,7 @@ import WebKit |
| 9 | /// highlighted through Highlightr; links open in the system browser. |
9 | /// highlighted through Highlightr; links open in the system browser. |
| 10 | struct OrgReadmeWebView: View { |
10 | struct OrgReadmeWebView: View { |
| 11 | let source: String |
11 | let source: String |
| |
12 | var options: OrgRenderOptions = .init() |
| 12 | |
13 | |
| 13 | @Environment(\.colorScheme) private var colorScheme |
14 | @Environment(\.colorScheme) private var colorScheme |
| 14 | @State private var html = "" |
15 | @State private var html = "" |
| @@ -24,7 +25,7 @@ struct OrgReadmeWebView: View { |
| 24 | // READMEs are small, and Highlightr (JavaScriptCore) is thread-confined; rendering |
25 | // READMEs are small, and Highlightr (JavaScriptCore) is thread-confined; rendering |
| 25 | // on the main actor keeps it simple and matches how the app highlights elsewhere. |
26 | // on the main actor keeps it simple and matches how the app highlights elsewhere. |
| 26 | let highlighter = OrgCodeHighlighter(colorScheme: scheme) |
27 | let highlighter = OrgCodeHighlighter(colorScheme: scheme) |
| 27 | let body = OrgRenderer.renderToHTML(source, highlighter: highlighter) |
28 | let body = OrgRenderer.renderToHTML(source, options: options, highlighter: highlighter) |
| 28 | html = OrgReadmeDocument.wrap(body, colorScheme: scheme) |
29 | html = OrgReadmeDocument.wrap(body, colorScheme: scheme) |
| 29 | } |
30 | } |
| 30 | } |
31 | } |
gitbay/Views/Repos/ReadmeView.swift
+24 −1
| @@ -1,4 +1,5 @@ |
| 1 | import Foundation |
1 | import Foundation |
| |
2 | import OrgSwift |
| 2 | import SwiftUI |
3 | import SwiftUI |
| 3 | |
4 | |
| 4 | /// README rendering follows the file's format. Org is rendered by the shared `OrgSwift` |
5 | /// README rendering follows the file's format. Org is rendered by the shared `OrgSwift` |
| @@ -6,15 +7,37 @@ import SwiftUI |
| 6 | struct ReadmeView: View { |
7 | struct ReadmeView: View { |
| 7 | let name: String |
8 | let name: String |
| 8 | let content: String |
9 | let content: String |
| |
10 | /// Instance host and `owner/repo`, used to resolve repository-relative links and images. |
| |
11 | /// Empty leaves relative references unresolved. |
| |
12 | var host: String = "" |
| |
13 | var repoPath: String = "" |
| 9 | |
14 | |
| 10 | /// Format follows the file name, never a guess at the content. |
15 | /// Format follows the file name, never a guess at the content. |
| 11 | var isOrg: Bool { name.lowercased().hasSuffix(".org") } |
16 | var isOrg: Bool { name.lowercased().hasSuffix(".org") } |
| 12 | |
17 | |
| 13 | var body: some View { |
18 | var body: some View { |
| 14 | if isOrg { |
19 | if isOrg { |
| 15 | OrgReadmeWebView(source: content) |
20 | OrgReadmeWebView(source: content, options: orgOptions) |
| 16 | } else { |
21 | } else { |
| 17 | MarkdownView(markdown: content) |
22 | MarkdownView(markdown: content) |
| 18 | } |
23 | } |
| 19 | } |
24 | } |
| |
25 | |
| |
26 | /// gitbay serves raw bytes at `/{owner}/{repo}/raw/{ref}/…` and the file page at |
| |
27 | /// `/blob/…`, so relative images resolve against `raw` and links against `blob`. |
| |
28 | private var orgOptions: OrgRenderOptions { |
| |
29 | let parts = repoPath.split(separator: "/", maxSplits: 1).map(String.init) |
| |
30 | guard parts.count == 2, !host.isEmpty else { |
| |
31 | return OrgRenderOptions() |
| |
32 | } |
| |
33 | return OrgRenderOptions( |
| |
34 | host: host, |
| |
35 | owner: parts[0], |
| |
36 | repositoryName: parts[1], |
| |
37 | ref: "HEAD", |
| |
38 | readmePath: name, |
| |
39 | imagePathSegment: "raw", |
| |
40 | linkPathSegment: "blob" |
| |
41 | ) |
| |
42 | } |
| 20 | } |
43 | } |
gitbay/Views/Repos/RepoView.swift
+9 −2
| @@ -4,9 +4,11 @@ struct RepoView: View { |
| 4 | |
4 | |
| 5 | @State private var model: RepoDetailViewModel |
5 | @State private var model: RepoDetailViewModel |
| 6 | private let path: String |
6 | private let path: String |
| |
7 | private let host: String |
| 7 | |
8 | |
| 8 | init(client: GitbayClient, path: String) { |
9 | init(client: GitbayClient, path: String) { |
| 9 | self.path = path |
10 | self.path = path |
| |
11 | self.host = client.instance.baseURL.host() ?? "" |
| 10 | _model = State(initialValue: RepoDetailViewModel(client: client, path: path)) |
12 | _model = State(initialValue: RepoDetailViewModel(client: client, path: path)) |
| 11 | } |
13 | } |
| 12 | |
14 | |
| @@ -63,8 +65,13 @@ struct RepoView: View { |
| 63 | |
65 | |
| 64 | if let readme = model.readme { |
66 | if let readme = model.readme { |
| 65 | Section("README") { |
67 | Section("README") { |
| 66 | ReadmeView(name: model.readmeName ?? "README.md", content: readme) |
68 | ReadmeView( |
| 67 | .padding(.vertical, 4) |
69 | name: model.readmeName ?? "README.md", |
| |
70 | content: readme, |
| |
71 | host: host, |
| |
72 | repoPath: path |
| |
73 | ) |
| |
74 | .padding(.vertical, 4) |
| 68 | } |
75 | } |
| 69 | } |
76 | } |
| 70 | } |
77 | } |