import Highlightr import OrgSwift import SwiftUI import UIKit import WebKit /// Renders an Org README with the shared `OrgSwift` package and shows the resulting HTML in /// a self-sizing, non-scrolling `WKWebView` embedded in the surrounding list. Code blocks are /// highlighted through Highlightr; links open in the system browser. struct OrgReadmeWebView: View { let source: String var options: OrgRenderOptions = .init() @Environment(\.colorScheme) private var colorScheme @State private var html = "" @State private var height: CGFloat = 1 var body: some View { OrgWebRepresentable(html: html, height: $height) .frame(height: max(height, 1)) .task(id: colorScheme) { await render(for: colorScheme) } } private func render(for scheme: ColorScheme) async { // READMEs are small, and Highlightr (JavaScriptCore) is thread-confined; rendering // on the main actor keeps it simple and matches how the app highlights elsewhere. let highlighter = OrgCodeHighlighter(colorScheme: scheme) let body = OrgRenderer.renderToHTML(source, options: options, highlighter: highlighter) html = OrgReadmeDocument.wrap(body, colorScheme: scheme) } } // MARK: - Highlightr → OrgSwift.CodeHighlighter /// Bridges Highlightr to `OrgSwift.CodeHighlighter`: the highlighted attributed string is /// flattened to color-styled ``s, the inner HTML of a `` block. Colors are /// inlined, so a new instance is made per theme. private final class OrgCodeHighlighter: CodeHighlighter { private let highlightr: Highlightr? private let supported: Set init(colorScheme: ColorScheme) { let engine = Highlightr() engine?.setTheme(to: colorScheme == .dark ? "atom-one-dark" : "xcode") highlightr = engine supported = Set(engine?.supportedLanguages() ?? []) } func highlightedHTML(code: String, language: String?) -> String? { guard let highlightr, let language = resolvedLanguage(language) else { return nil } highlightr.theme.setCodeFont(.monospacedSystemFont(ofSize: 13, weight: .regular)) guard let attributed = highlightr.highlight(code, as: language, fastRender: true) else { return nil } return Self.html(from: attributed) } private func resolvedLanguage(_ raw: String?) -> String? { guard let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(), !trimmed.isEmpty else { return nil } let mapped = Self.aliases[trimmed] ?? trimmed return supported.contains(mapped) ? mapped : nil } private static let aliases: [String: String] = [ "js": "javascript", "jsx": "javascript", "ts": "typescript", "tsx": "typescript", "py": "python", "rb": "ruby", "rs": "rust", "sh": "bash", "shell": "bash", "zsh": "bash", "yml": "yaml", "c++": "cpp", "cc": "cpp", "kt": "kotlin", "cs": "csharp", "objc": "objectivec", "objective-c": "objectivec", "html": "xml", "htm": "xml", ] private static func html(from attributed: NSAttributedString) -> String { var html = "" let range = NSRange(location: 0, length: attributed.length) attributed.enumerateAttribute(.foregroundColor, in: range) { value, subrange, _ in let fragment = (attributed.string as NSString).substring(with: subrange) let escaped = fragment .replacingOccurrences(of: "&", with: "&") .replacingOccurrences(of: "<", with: "<") .replacingOccurrences(of: ">", with: ">") if let color = value as? UIColor, let hex = color.hexRGBString { html += "\(escaped)" } else { html += escaped } } return html } } private extension UIColor { var hexRGBString: String? { var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { return nil } return String(format: "#%02x%02x%02x", Int(round(red * 255)), Int(round(green * 255)), Int(round(blue * 255))) } } // MARK: - HTML document private enum OrgReadmeDocument { /// Wrap OrgSwift's body HTML in a themed document. The palette follows the app's /// light/dark scheme; layout is deliberately close to the native README styling. static func wrap(_ body: String, colorScheme: ColorScheme) -> String { let dark = colorScheme == .dark let text = dark ? "#e6e6e6" : "#1a1a1a" let muted = dark ? "#9aa0a6" : "#606060" let link = dark ? "#6cb6ff" : "#0a58ca" let rule = dark ? "#333" : "#e0e0e0" let codeBg = dark ? "#1c1c1e" : "#f4f4f5" let quoteBar = dark ? "#3a3a3c" : "#d0d0d0" return """ \(body) """ } } // MARK: - WKWebView representable private struct OrgWebRepresentable: UIViewRepresentable { let html: String @Binding var height: CGFloat @Environment(\.openURL) private var openURL func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIView(context: Context) -> WKWebView { let config = WKWebViewConfiguration() config.defaultWebpagePreferences.allowsContentJavaScript = false let webView = WKWebView(frame: .zero, configuration: config) webView.isOpaque = false webView.backgroundColor = .clear webView.scrollView.isScrollEnabled = false webView.scrollView.contentInsetAdjustmentBehavior = .never webView.navigationDelegate = context.coordinator return webView } func updateUIView(_ webView: WKWebView, context: Context) { context.coordinator.parent = self guard !html.isEmpty, context.coordinator.loadedHTML != html else { return } context.coordinator.loadedHTML = html webView.loadHTMLString(html, baseURL: nil) } @MainActor final class Coordinator: NSObject, WKNavigationDelegate { var parent: OrgWebRepresentable var loadedHTML: String? init(_ parent: OrgWebRepresentable) { self.parent = parent } func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { webView.evaluateJavaScript("document.body.scrollHeight") { [weak self] result, _ in guard let self, let value = result as? NSNumber else { return } self.parent.height = CGFloat(value.doubleValue) } } func webView( _ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void ) { if action.navigationType == .linkActivated, let url = action.request.url { parent.openURL(url) decisionHandler(.cancel) return } decisionHandler(.allow) } } }