gitbay/Views/Repos/ReadmeView.swift
80 lines · 3403 bytes
2 symbols in this file
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}