summaryrefslogtreecommitdiff
path: root/Shared
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-05-14 12:41:55 -0500
committerChristian Cleberg <[email protected]>2026-05-14 12:41:55 -0500
commit0d61e05e98f6f020722ee4d43f024c1da348d2a9 (patch)
treeb388972b86cf83de380381c52c4390c3beafc69a /Shared
parentf28e5cee1643e3f99d7bf90476d1dcf9347229a0 (diff)
downloadhutch-0d61e05e98f6f020722ee4d43f024c1da348d2a9.tar.gz
hutch-0d61e05e98f6f020722ee4d43f024c1da348d2a9.tar.bz2
hutch-0d61e05e98f6f020722ee4d43f024c1da348d2a9.zip
feat: add Safari extension for sourcehut deep linksv3.4.0
Diffstat (limited to 'Shared')
-rw-r--r--Shared/HutchDeepLinkURLs.swift5
-rw-r--r--Shared/SourceHutWebDeepLinkMapper.swift80
2 files changed, 85 insertions, 0 deletions
diff --git a/Shared/HutchDeepLinkURLs.swift b/Shared/HutchDeepLinkURLs.swift
index 9b60086..eed678c 100644
--- a/Shared/HutchDeepLinkURLs.swift
+++ b/Shared/HutchDeepLinkURLs.swift
@@ -5,13 +5,18 @@ enum HutchDeepLinkURL {
static let home = URL(string: "hutch://home")!
static let emptyHost = URL(string: "hutch://")!
static let repositoryGit = URL(string: "hutch://git/~user/repo")!
+ static let repositoryHg = URL(string: "hutch://hg/~user/repo")!
+ static let gitOwnerRoot = URL(string: "hutch://git/~owner/")!
static let ticket = URL(string: "hutch://todo/~owner/tracker/42")!
static let buildJob = URL(string: "hutch://builds/12345")!
+ static let buildJobWithOwner = URL(string: "hutch://builds/~owner/job/12345")!
+ static let mailingList = URL(string: "hutch://lists/~owner/list")!
static let trackers = URL(string: "hutch://trackers")!
static let builds = URL(string: "hutch://builds")!
static let repositories = URL(string: "hutch://repositories")!
static let status = URL(string: "hutch://status")!
static let lookup = URL(string: "hutch://lookup")!
+ static let userProfile = URL(string: "hutch://lookup/~owner")!
static let unknown = URL(string: "hutch://unknown")!
static let invalidTicketId = URL(string: "hutch://todo/~owner/tracker/abc")!
static let invalidBuildId = URL(string: "hutch://builds/abc")!
diff --git a/Shared/SourceHutWebDeepLinkMapper.swift b/Shared/SourceHutWebDeepLinkMapper.swift
new file mode 100644
index 0000000..236e32e
--- /dev/null
+++ b/Shared/SourceHutWebDeepLinkMapper.swift
@@ -0,0 +1,80 @@
+import Foundation
+
+enum SourceHutWebDeepLinkMapper {
+ static let supportedHosts: Set<String> = [
+ "git.sr.ht",
+ "hg.sr.ht",
+ "todo.sr.ht",
+ "builds.sr.ht",
+ "lists.sr.ht",
+ "meta.sr.ht",
+ "sr.ht",
+ ]
+
+ static func deepLink(for webURL: URL) -> URL? {
+ guard let components = URLComponents(url: webURL, resolvingAgainstBaseURL: false),
+ components.scheme == "https",
+ let host = components.host?.lowercased(),
+ supportedHosts.contains(host)
+ else {
+ return nil
+ }
+
+ let path = normalizedPercentEncodedPath(from: components.percentEncodedPath)
+ let deepLinkPath = normalizedDeepLinkPath(for: host, path: path)
+ let service = deepLinkService(for: host, path: deepLinkPath)
+ var deepLinkComponents = URLComponents()
+ deepLinkComponents.scheme = "hutch"
+ deepLinkComponents.host = service
+
+ if !deepLinkPath.isEmpty {
+ deepLinkComponents.percentEncodedPath = "/" + deepLinkPath
+ }
+ deepLinkComponents.percentEncodedQuery = components.percentEncodedQuery
+ deepLinkComponents.percentEncodedFragment = components.percentEncodedFragment
+ return deepLinkComponents.url
+ }
+
+ private static func deepLinkService(for host: String, path: String) -> String {
+ if isOwnerRootPath(path) {
+ return "lookup"
+ }
+
+ switch host {
+ case "git.sr.ht":
+ return "git"
+ case "hg.sr.ht":
+ return "hg"
+ case "todo.sr.ht":
+ return "todo"
+ case "builds.sr.ht":
+ return "builds"
+ case "lists.sr.ht":
+ return "lists"
+ default:
+ return "lookup"
+ }
+ }
+
+ private static func isOwnerRootPath(_ path: String) -> Bool {
+ let components = path.split(separator: "/", omittingEmptySubsequences: true)
+ return components.count == 1 && components[0].hasPrefix("~")
+ }
+
+ private static func normalizedDeepLinkPath(for host: String, path: String) -> String {
+ let components = path.split(separator: "/", omittingEmptySubsequences: true)
+ if host == "sr.ht",
+ components.count == 2,
+ components[0] == "projects",
+ components[1].hasPrefix("~") {
+ return String(components[1])
+ }
+ return path
+ }
+
+ private static func normalizedPercentEncodedPath(from path: String) -> String {
+ path
+ .split(separator: "/", omittingEmptySubsequences: true)
+ .joined(separator: "/")
+ }
+}