Sources/OrgSwiftUI/OrgBlockView.swift
115 lines · 3758 bytes
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}