Commit d58e5ef7d8
Verified · cmc
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) { | |||
| 15 | private let userProfileJSON = """ | 15 | private 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 { |