diff options
| author | Christian Cleberg <[email protected]> | 2026-04-12 22:06:16 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-12 22:06:16 -0500 |
| commit | 86bcd9452d3107f68aca085e03e69b02667c0dbb (patch) | |
| tree | 8b0adece5cb0f8ffb178eaab2cd12d025c9ebf0a /Hutch/Models | |
| parent | 31cbca674957eea7ae2529012e20a9d95158e561 (diff) | |
| download | hutch-86bcd9452d3107f68aca085e03e69b02667c0dbb.tar.gz hutch-86bcd9452d3107f68aca085e03e69b02667c0dbb.tar.bz2 hutch-86bcd9452d3107f68aca085e03e69b02667c0dbb.zip | |
feat: polish read-only project views and home integration
Refs: https://todo.sr.ht/~ccleberg/hutch/36
Refs: https://todo.sr.ht/~ccleberg/hutch/37
Refs: https://todo.sr.ht/~ccleberg/hutch/38
Diffstat (limited to 'Hutch/Models')
| -rw-r--r-- | Hutch/Models/Project.swift | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/Hutch/Models/Project.swift b/Hutch/Models/Project.swift index 54c0318..678d52e 100644 --- a/Hutch/Models/Project.swift +++ b/Hutch/Models/Project.swift @@ -115,11 +115,104 @@ struct Project: Identifiable, Hashable, Sendable { return nil } + var displayName: String { + Self.normalizedText(name) ?? "Untitled Project" + } + + var displayDescription: String? { + Self.normalizedText(description) + } + + var displayTags: [String] { + var seen = Set<String>() + return tags.compactMap { tag in + guard let normalized = Self.normalizedText(tag) else { return nil } + let key = normalized.lowercased() + guard seen.insert(key).inserted else { return nil } + return normalized + } + } + + var websiteURL: URL? { + guard let website = Self.normalizedText(website) else { return nil } + return URL(string: website) + } + + var hasLinkedResources: Bool { + !sources.isEmpty || !trackers.isEmpty || !mailingLists.isEmpty + } + + var metadataLine: String { + var parts = [visibility.displayName, updated.relativeDescription] + if let summary = resourceSummary { + parts.append(summary) + } + return parts.joined(separator: " • ") + } + private static func resourceCountText(count: Int, singular: String) -> String? { guard count > 0 else { return nil } let label = count == 1 ? singular : "\(singular)s" return "\(count) \(label)" } + + fileprivate static func normalizedText(_ value: String?) -> String? { + guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines), + !trimmed.isEmpty else { + return nil + } + return trimmed + } +} + +extension Project.MailingList { + var displayName: String { + Project.normalizedText(name) ?? "Untitled Mailing List" + } + + var displayDescription: String? { + Project.normalizedText(description) + } + + var ownerDisplayName: String { + Project.normalizedText(owner.canonicalName) ?? "~unknown" + } +} + +extension Project.SourceRepo { + var displayName: String { + Project.normalizedText(name) ?? "Untitled Repository" + } + + var displayDescription: String? { + Project.normalizedText(description) + } + + var ownerDisplayName: String { + Project.normalizedText(owner.canonicalName) ?? "~unknown" + } + + var webURL: URL? { + SRHTWebURL.projectSource(self) + } +} + +extension Project.Tracker { + var displayName: String { + Project.normalizedText(name) ?? "Untitled Tracker" + } + + var displayDescription: String? { + Project.normalizedText(description) + } + + var ownerDisplayName: String { + Project.normalizedText(owner.canonicalName) ?? "~unknown" + } + + var webURL: URL? { + SRHTWebURL.tracker(ownerUsername: ownerUsername, trackerName: name) + } } extension String { @@ -127,3 +220,16 @@ extension String { hasPrefix("~") ? String(dropFirst()) : self } } + +extension Visibility { + var displayName: String { + switch self { + case .public: + "Public" + case .unlisted: + "Unlisted" + case .private: + "Private" + } + } +} |
