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.
10struct OrgReadmeWebView: View { 10struct 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 @@
1import Foundation 1import Foundation
2import OrgSwift
2import SwiftUI 3import 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
6struct ReadmeView: View { 7struct 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 }