Commit d58e5ef7d8

d58e5ef7d8a719fdf2a0117721a6a5dd7d44131a

parent: 0a519cfa8f

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-22 18:04 UTC

profile: render the about text under about_path

Layout: unified · split

gitbay/Discovery/ProfileViewModel.swift +4 −7
@@ -16,7 +16,9 @@ final class ProfileViewModel {
16 /// Long-form profile text, rendered between the header and the 16 /// Long-form profile text, rendered between the header and the
17 /// activity graph the way the web renders it. 17 /// activity graph the way the web renders it.
18 let about: String? 18 let about: String?
19 let aboutFormat: String? 19 /// The file in `<name>/.gitbay` the text was read from, e.g.
20 /// `profile/README.org`. Its extension picks the renderer.
21 let aboutPath: String?
20 let links: [Link]? 22 let links: [Link]?
21 let orgs: [Member]? 23 let orgs: [Member]?
22 let members: [Member]? 24 let members: [Member]?
@@ -26,7 +28,7 @@ final class ProfileViewModel {
26 28
27 enum CodingKeys: String, CodingKey { 29 enum CodingKeys: String, CodingKey {
28 case name, kind, description, website, about, links, orgs, members, repos, activity 30 case name, kind, description, website, about, links, orgs, members, repos, activity
29 case aboutFormat = "about_format" 31 case aboutPath = "about_path"
30 case activityTotal = "activity_total" 32 case activityTotal = "activity_total"
31 } 33 }
32 34
@@ -42,11 +44,6 @@ final class ProfileViewModel {
42 return name == username 44 return name == username
43 } 45 }
44 46
45 /// The filename the about text is rendered under. The server has
46 /// none to dispatch on either, so the stored format picks the
47 /// extension and anything but org is markdown.
48 var aboutFile: String { aboutFormat == "org" ? "about.org" : "about.md" }
49
50 /// One of the free-form links a profile carries beside its 47 /// One of the free-form links a profile carries beside its
51 /// website. The server caps the list at five. 48 /// website. The server caps the list at five.
52 nonisolated struct Link: Decodable, Sendable, Hashable { 49 nonisolated struct Link: Decodable, Sendable, Hashable {
gitbay/Views/Discovery/ProfileView.swift +1 −1
@@ -142,7 +142,7 @@ struct ProfileView: View {
142 private func aboutSection(_ profile: ProfileViewModel.Profile) -> some View { 142 private func aboutSection(_ profile: ProfileViewModel.Profile) -> some View {
143 if let about = profile.about, !about.isEmpty { 143 if let about = profile.about, !about.isEmpty {
144 Section("About") { 144 Section("About") {
145 ReadmeView(name: profile.aboutFile, content: about) 145 ReadmeView(name: profile.aboutPath ?? "", content: about)
146 .padding(.vertical, 4) 146 .padding(.vertical, 4)
147 } 147 }
148 } 148 }
gitbayTests/ProfileCommitTests.swift +5 −5
@@ -15,7 +15,7 @@ private func makeClient() throws -> (GitbayClient, StubProtocol.Box) {
15private let userProfileJSON = """ 15private let userProfileJSON = """
16 {"protocol_version":1,"data":{"name":"cmc","kind":"user",\ 16 {"protocol_version":1,"data":{"name":"cmc","kind":"user",\
17 "description":"Org-Mode · Self-Hosting · Privacy","website":"https://cleberg.net",\ 17 "description":"Org-Mode · Self-Hosting · Privacy","website":"https://cleberg.net",\
18 "about":"* Christian\\n\\nOrg-Mode.","about_format":"org",\ 18 "about":"* Christian\\n\\nOrg-Mode.","about_format":"org","about_path":"profile/README.org",\
19 "links":[{"label":"Tor","url":"http://example.onion/"},{"url":"https://krz.sh"}],\ 19 "links":[{"label":"Tor","url":"http://example.onion/"},{"url":"https://krz.sh"}],\
20 "orgs":[{"name":"krz","role":"admin"},{"name":"audit-labs","role":"admin"}],\ 20 "orgs":[{"name":"krz","role":"admin"},{"name":"audit-labs","role":"admin"}],\
21 "repos":[{"path":"cmc/notes","visibility":"private","description":"notes",\ 21 "repos":[{"path":"cmc/notes","visibility":"private","description":"notes",\
@@ -53,8 +53,8 @@ struct ProfileAggregateTests {
53 #expect(profile.repos[1].topics == nil) 53 #expect(profile.repos[1].topics == nil)
54 #expect(profile.repos[1].meta.isEmpty) 54 #expect(profile.repos[1].meta.isEmpty)
55 #expect(profile.about == "* Christian\n\nOrg-Mode.") 55 #expect(profile.about == "* Christian\n\nOrg-Mode.")
56 // The format picks the filename the about text renders under. 56 // The file's own path picks the renderer, as it does on the web.
57 #expect(profile.aboutFile == "about.org") 57 #expect(profile.aboutPath == "profile/README.org")
58 #expect(profile.links?.map(\.title) == ["Tor", "https://krz.sh"]) 58 #expect(profile.links?.map(\.title) == ["Tor", "https://krz.sh"])
59 #expect(profile.activityTotal == 15) 59 #expect(profile.activityTotal == 15)
60 // One read builds the whole page. 60 // One read builds the whole page.
@@ -78,8 +78,8 @@ struct ProfileAggregateTests {
78 // A profile with no about text and no links renders neither. 78 // A profile with no about text and no links renders neither.
79 #expect(profile.about == nil) 79 #expect(profile.about == nil)
80 #expect(profile.links == nil) 80 #expect(profile.links == nil)
81 // Markdown is the default for anything but org. 81 // No about file, no path.
82 #expect(profile.aboutFile == "about.md") 82 #expect(profile.aboutPath == nil)
83 } 83 }
84 84
85 @Test func anUnknownNameIsAnEmptyState() async throws { 85 @Test func anUnknownNameIsAnEmptyState() async throws {