import Foundation import OrgSwift import OrgSwiftUI import SwiftUI /// README rendering follows the file's format. Org is rendered natively by the shared /// `OrgSwiftUI` package; everything else keeps the established Markdown renderer. struct ReadmeView: View { let name: String let content: String /// Instance host and `owner/repo`, used to resolve repository-relative links and images. /// Empty leaves relative references unresolved. var host: String = "" var repoPath: String = "" @Environment(\.colorScheme) private var colorScheme @Environment(\.dynamicTypeSize) private var dynamicTypeSize @State private var allowance = MathAllowance() /// Format follows the file name, never a guess at the content. var isOrg: Bool { name.lowercased().hasSuffix(".org") } var body: some View { if isOrg { let _ = allowance.reset(for: content) // Qualified: `OrgView` here is org-mode, not the organization view. OrgSwiftUI.OrgView( Self.preferringPNGBadges(content, host: host), options: renderOptions, styler: OrgSyntaxStyler(colorScheme: colorScheme), // org's TODO/DONE are the same open/done distinction the forge already // colors, so they reuse those tokens rather than a third treatment. keywords: OrgKeywordStyle(todo: .gbOK, done: .gbDone), math: MathRenderer.org(pointSize: UIFont.pointSize(.body, dynamicTypeSize), allowance: allowance) ) } else { MarkdownView(markdown: content, linkOptions: renderOptions) } } /// Point badge sources on this instance at the PNG the forge also serves. /// /// No Apple image decoder reads SVG outside an asset catalog, so the SVG badge a README /// names cannot render at all. Only badges on the instance being browsed are rewritten — /// a badge from anywhere else has no PNG to offer, and is left as authored. static func preferringPNGBadges(_ content: String, host: String) -> String { guard !host.isEmpty, let pattern = try? NSRegularExpression( pattern: "(https://\(NSRegularExpression.escapedPattern(for: host))" + "/[^/\\s\\]]+/[^/\\s\\]]+/badge/[\\w.-]+)\\.svg" ) else { return content } return pattern.stringByReplacingMatches( in: content, range: NSRange(content.startIndex..., in: content), withTemplate: "$1.png" ) } /// gitbay serves raw bytes at `/{owner}/{repo}/raw/{ref}/…` and the file page at /// `/blob/…`, so relative images resolve against `raw` and links against `blob`. /// /// Both formats resolve through the same options: a relative link in a Markdown README /// points at the same file page an org one does. private var renderOptions: OrgRenderOptions { let parts = repoPath.split(separator: "/", maxSplits: 1).map(String.init) guard parts.count == 2, !host.isEmpty else { return OrgRenderOptions() } return OrgRenderOptions( host: host, owner: parts[0], repositoryName: parts[1], ref: "HEAD", readmePath: name, imagePathSegment: "raw", linkPathSegment: "blob" ) } }