gitbay/Views/Repos/ReadmeView.swift

v1.6.1
gitbay-ios/gitbay/Views/Repos/ReadmeView.swift history · blame · raw

76 lines · 3142 bytes

 1import Foundation
 2import OrgSwift
 3import OrgSwiftUI
 4import SwiftUI
 5
 6/// README rendering follows the file's format. Org is rendered natively by the shared
 7/// `OrgSwiftUI` package; everything else keeps the established Markdown renderer.
 8struct ReadmeView: View {
 9    let name: String
10    let content: String
11    /// Instance host and `owner/repo`, used to resolve repository-relative links and images.
12    /// Empty leaves relative references unresolved.
13    var host: String = ""
14    var repoPath: String = ""
15
16    @Environment(\.colorScheme) private var colorScheme
17
18    /// Format follows the file name, never a guess at the content.
19    var isOrg: Bool { name.lowercased().hasSuffix(".org") }
20
21    var body: some View {
22        if isOrg {
23            // Qualified: `OrgView` here is org-mode, not the organization view.
24            OrgSwiftUI.OrgView(
25                Self.preferringPNGBadges(content, host: host),
26                options: renderOptions,
27                styler: OrgSyntaxStyler(colorScheme: colorScheme),
28                // org's TODO/DONE are the same open/done distinction the forge already
29                // colors, so they reuse those tokens rather than a third treatment.
30                keywords: OrgKeywordStyle(todo: .gbOK, done: .gbDone)
31            )
32        } else {
33            MarkdownView(markdown: content, linkOptions: renderOptions)
34        }
35    }
36
37    /// Point badge sources on this instance at the PNG the forge also serves.
38    ///
39    /// No Apple image decoder reads SVG outside an asset catalog, so the SVG badge a README
40    /// names cannot render at all. Only badges on the instance being browsed are rewritten —
41    /// a badge from anywhere else has no PNG to offer, and is left as authored.
42    static func preferringPNGBadges(_ content: String, host: String) -> String {
43        guard !host.isEmpty,
44              let pattern = try? NSRegularExpression(
45                  pattern: "(https://\(NSRegularExpression.escapedPattern(for: host))"
46                      + "/[^/\\s\\]]+/[^/\\s\\]]+/badge/[\\w.-]+)\\.svg"
47              )
48        else { return content }
49        return pattern.stringByReplacingMatches(
50            in: content,
51            range: NSRange(content.startIndex..., in: content),
52            withTemplate: "$1.png"
53        )
54    }
55
56    /// gitbay serves raw bytes at `/{owner}/{repo}/raw/{ref}/…` and the file page at
57    /// `/blob/…`, so relative images resolve against `raw` and links against `blob`.
58    ///
59    /// Both formats resolve through the same options: a relative link in a Markdown README
60    /// points at the same file page an org one does.
61    private var renderOptions: OrgRenderOptions {
62        let parts = repoPath.split(separator: "/", maxSplits: 1).map(String.init)
63        guard parts.count == 2, !host.isEmpty else {
64            return OrgRenderOptions()
65        }
66        return OrgRenderOptions(
67            host: host,
68            owner: parts[0],
69            repositoryName: parts[1],
70            ref: "HEAD",
71            readmePath: name,
72            imagePathSegment: "raw",
73            linkPathSegment: "blob"
74        )
75    }
76}