krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

Sources/OrgSwiftUI/OrgBlockView.swift

799d4ba18ece2bd259bd4760cca46faeae0fd1e1
org-swift/Sources/OrgSwiftUI/OrgBlockView.swift history · blame · raw

115 lines · 3758 bytes

2 symbols in this file
  1import OrgSwift
  2import SwiftUI
  3
  4/// Renders prepared blocks. All the org work happened in ``OrgLayoutBuilder``; this is
  5/// layout only.
  6struct OrgBlockListView: View {
  7    let blocks: [OrgBlock]
  8
  9    var body: some View {
 10        VStack(alignment: .leading, spacing: 12) {
 11            ForEach(Array(blocks.enumerated()), id: \.offset) { _, block in
 12                OrgBlockView(block: block)
 13            }
 14        }
 15        .frame(maxWidth: .infinity, alignment: .leading)
 16    }
 17}
 18
 19struct OrgBlockView: View {
 20    let block: OrgBlock
 21    @Environment(\.orgMath) private var math
 22
 23    var body: some View {
 24        switch block {
 25        case .heading(let text, let level):
 26            orgText(text, math: math)
 27                .padding(.top, level <= 2 ? 8 : 2)
 28                .fixedSize(horizontal: false, vertical: true)
 29
 30        case .paragraph(let text):
 31            orgText(text, math: math)
 32                .fixedSize(horizontal: false, vertical: true)
 33
 34        case .code(let code):
 35            ScrollView(.horizontal) {
 36                Text(code)
 37                    .textSelection(.enabled)
 38                    .padding(10)
 39            }
 40            .background(
 41                Color.secondary.opacity(0.12),
 42                in: RoundedRectangle(cornerRadius: 4)
 43            )
 44
 45        case .quote(let blocks):
 46            HStack(spacing: 10) {
 47                RoundedRectangle(cornerRadius: 2)
 48                    .fill(.tertiary)
 49                    .frame(width: 3)
 50                OrgBlockListView(blocks: blocks)
 51                    .foregroundStyle(.secondary)
 52            }
 53            .fixedSize(horizontal: false, vertical: true)
 54
 55        case .center(let blocks):
 56            OrgBlockListView(blocks: blocks)
 57                .multilineTextAlignment(.center)
 58                .frame(maxWidth: .infinity, alignment: .center)
 59
 60        case .verse(let lines):
 61            // Verse keeps its line breaks; that is the whole point of the block.
 62            VStack(alignment: .leading, spacing: 0) {
 63                ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
 64                    orgText(line, math: math).fixedSize(horizontal: false, vertical: true)
 65                }
 66            }
 67
 68        case .special(_, let blocks):
 69            OrgBlockListView(blocks: blocks)
 70
 71        case .list(let list):
 72            OrgListView(list: list)
 73
 74        case .table(let table):
 75            OrgTableView(table: table)
 76
 77        case .image(let source, let alt, let caption):
 78            VStack(alignment: .leading, spacing: 4) {
 79                AsyncImage(url: source) { phase in
 80                    switch phase {
 81                    case .success(let image):
 82                        image.resizable().scaledToFit()
 83                    case .failure:
 84                        Text(alt.isEmpty ? source.lastPathComponent : alt)
 85                            .font(.footnote)
 86                            .foregroundStyle(.secondary)
 87                    default:
 88                        ProgressView()
 89                    }
 90                }
 91                .accessibilityLabel(alt)
 92                if let caption {
 93                    orgText(caption, math: math).foregroundStyle(.secondary)
 94                }
 95            }
 96
 97        case .badges(let badges):
 98            OrgBadgeRowView(badges: badges)
 99
100        case .captioned(let caption, let blocks):
101            VStack(alignment: .leading, spacing: 4) {
102                OrgBlockListView(blocks: blocks)
103                if let caption {
104                    orgText(caption, math: math).foregroundStyle(.secondary)
105                }
106            }
107
108        case .rule:
109            Divider()
110
111        case .math(let expression):
112            OrgMathBlockView(math: expression)
113        }
114    }
115}