gitbay/Views/Repos/ReadmeView.swift
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}