gitbay/Views/Repos/ReadmeView.swift

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

73 lines · 2954 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: orgOptions,
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)
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    private var orgOptions: OrgRenderOptions {
59        let parts = repoPath.split(separator: "/", maxSplits: 1).map(String.init)
60        guard parts.count == 2, !host.isEmpty else {
61            return OrgRenderOptions()
62        }
63        return OrgRenderOptions(
64            host: host,
65            owner: parts[0],
66            repositoryName: parts[1],
67            ref: "HEAD",
68            readmePath: name,
69            imagePathSegment: "raw",
70            linkPathSegment: "blob"
71        )
72    }
73}