gitbay/Discovery/ProfileEdit.swift
77 lines · 2864 bytes
1import Foundation
2
3/// `profile set` — unlike every other filter or edit in the app, `--link`
4/// replaces the whole set rather than appending, so every link the form
5/// wants to keep is sent on every save. The form is always seeded from
6/// the current profile, so every other field is also sent every time,
7/// including empty ones: `--description ""` clears, omitting the flag
8/// would leave the old value alone.
9nonisolated struct ProfileEdit: Equatable, Sendable {
10
11 nonisolated struct Link: Equatable, Sendable, Identifiable {
12 var id = UUID()
13 var label: String
14 var url: String
15 }
16
17 var description: String
18 var website: String
19 var about: String
20 var aboutFormat: String
21 var links: [Link]
22
23 init(
24 description: String = "", website: String = "",
25 about: String = "", aboutFormat: String = "md",
26 links: [Link] = []
27 ) {
28 self.description = description
29 self.website = website
30 self.about = about
31 self.aboutFormat = aboutFormat
32 self.links = links
33 }
34
35 init(from profile: ProfileViewModel.Profile) {
36 description = profile.description ?? ""
37 website = profile.website ?? ""
38 about = profile.about ?? ""
39 aboutFormat = profile.aboutFormat ?? "md"
40 links = (profile.links ?? []).map { Link(label: $0.label ?? "", url: $0.url) }
41 }
42
43 /// At most 5 links and every link needs a URL — the server enforces
44 /// both, but the form should not let a save reach it that way. A
45 /// label may not contain `|` either: the server splits a link on its
46 /// first pipe, so a label carrying one would silently corrupt the URL
47 /// rather than be rejected.
48 var validationError: String? {
49 if links.count > 5 { return "At most 5 links." }
50 if links.contains(where: { $0.url.trimmingCharacters(in: .whitespaces).isEmpty }) {
51 return "A link needs a URL."
52 }
53 if links.contains(where: { $0.label.contains("|") }) {
54 return "A link label can't contain \"|\" — that's what separates it from the URL."
55 }
56 return nil
57 }
58
59 /// Everything except the about text, which travels over stdin.
60 func flags() -> [String] {
61 var flags = ["--description", description, "--website", website]
62 if links.isEmpty {
63 flags += ["--link", ""]
64 } else {
65 for link in links {
66 let label = link.label.trimmingCharacters(in: .whitespaces)
67 flags += ["--link", label.isEmpty ? link.url : "\(label)|\(link.url)"]
68 }
69 }
70 flags += ["--about-format", aboutFormat, "--file", "-"]
71 return flags
72 }
73
74 /// The about text, sent over stdin — never in argv, since it is
75 /// long-form and may hold newlines.
76 var aboutStdin: String { about }
77}