// // RenderingBlockView.swift // GeminiRenderer // // Created by Shadowfacts on 7/13/20. // import SwiftUI import GeminiFormat struct RenderingBlockView: View { let document: Document let block: RenderingBlock let changeURL: ((URL) -> Void)? @State var hovering = false @Environment(\.colorScheme) var colorScheme: ColorScheme init(document: Document, block: RenderingBlock, changeURL: ((URL) -> Void)? = nil) { self.document = document self.block = block self.changeURL = changeURL } @ViewBuilder var body: some View { switch block { case let .text(text): self.text(text) case let .link(url, text: linkText): self.link(url, text: linkText) case let .preformatted(text, alt: _): preformatted(text) case let .heading(text, level: level): heading(text, level: level) case let .unorderedListItem(text): unorderedListItem(text) case let .quote(text): quote(text) } } private func text(_ text: String) -> some View { Text(verbatim: text) .font(.documentBody) .frame(maxWidth: .infinity, alignment: .leading) } private func link(_ url: URL, text: String?) -> some View { let text = text ?? url.absoluteString #if os(macOS) let buttonStyle = LinkButtonStyle() #elseif os(iOS) let buttonStyle = PlainButtonStyle() #endif let imageName: String if url.scheme == "gemini" { if url.host == document.url.host { imageName = "arrow.right" } else { imageName = "link" } } else if url.scheme == "http" || url.scheme == "https" { imageName = "safari" } else if url.scheme == "mailto" { imageName = "envelope" } else { imageName = "arrow.up.left.square" } let button: some View = Button { self.changeURL?(url) } label: { HStack(alignment: .firstTextBaseline, spacing: 4) { maybeLinkImage(name: imageName) Text(verbatim: text) .font(.documentBody) .foregroundColor(colorScheme == .dark ? hovering ? Color.blue.opacity(0.8) : .blue : hovering ? .blue : Color.blue.opacity(0.8)) .underline() .frame(maxWidth: .infinity, alignment: .leading) } } .buttonStyle(buttonStyle) .onHover { hovering in self.hovering = hovering } return button.maybeHelp(url.absoluteString) } private func maybeLinkImage(name: String) -> AnyView { // can't use availability check inside view body, since buildLimitedAvailability was introduced in iOS 14 :/ if #available(iOS 13.0, macOS 11.0, *) { return AnyView(Image(systemName: name).frame(minWidth: 23, alignment: .leading)) } else { return AnyView(EmptyView()) } } private func preformatted(_ text: String) -> some View { ScrollView(.horizontal) { Text(verbatim: text) .font(.documentBodyPreformatted) .frame(maxWidth: .infinity, alignment: .leading) } } private func heading(_ text: String, level: Document.HeadingLevel) -> some View { Text(verbatim: text) .font(level.font) .frame(maxWidth: .infinity, alignment: .leading) } private func unorderedListItem(_ text: String) -> some View { // todo: should this be .firstTextBaseline? HStack(alignment: .top, spacing: 4) { Text(verbatim: "\u{2022}") Text(verbatim: text) .font(.documentBody) Spacer() }.frame(maxWidth: .infinity, alignment: .leading) } private func quote(_ text: String) -> some View { HStack(spacing: 4) { Color.gray .frame(width: 4) Text(verbatim: text) .font(Font.documentBody.italic()) .foregroundColor(.gray) Spacer() }.frame(maxWidth: .infinity, alignment: .leading) } } struct RenderingBlockView_Previews: PreviewProvider { static let doc = Document(url: URL(string: "gemini://localhost/test.gmi")!, lines: [.text("Some Text"), .quote("A Quote")]) static var previews: some View { Group { RenderingBlockView(document: doc, block: .text("Some Text")) RenderingBlockView(document: doc, block: .quote("A Quote")) } } }