gitbay/Views/Repos/ReadmeView.swift

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

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