krz/org-swift

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

Sources/OrgSwiftUI/OrgBlockView.swift

v0.7.0
org-swift/Sources/OrgSwiftUI/OrgBlockView.swift history · blame · raw

111 lines · 3554 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
 22    var body: some View {
 23        switch block {
 24        case .heading(let text, let level):
 25            Text(text)
 26                .padding(.top, level <= 2 ? 8 : 2)
 27                .fixedSize(horizontal: false, vertical: true)
 28
 29        case .paragraph(let text):
 30            Text(text)
 31                .fixedSize(horizontal: false, vertical: true)
 32
 33        case .code(let code):
 34            ScrollView(.horizontal) {
 35                Text(code)
 36                    .textSelection(.enabled)
 37                    .padding(10)
 38            }
 39            .background(
 40                Color.secondary.opacity(0.12),
 41                in: RoundedRectangle(cornerRadius: 4)
 42            )
 43
 44        case .quote(let blocks):
 45            HStack(spacing: 10) {
 46                RoundedRectangle(cornerRadius: 2)
 47                    .fill(.tertiary)
 48                    .frame(width: 3)
 49                OrgBlockListView(blocks: blocks)
 50                    .foregroundStyle(.secondary)
 51            }
 52            .fixedSize(horizontal: false, vertical: true)
 53
 54        case .center(let blocks):
 55            OrgBlockListView(blocks: blocks)
 56                .multilineTextAlignment(.center)
 57                .frame(maxWidth: .infinity, alignment: .center)
 58
 59        case .verse(let lines):
 60            // Verse keeps its line breaks; that is the whole point of the block.
 61            VStack(alignment: .leading, spacing: 0) {
 62                ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
 63                    Text(line).fixedSize(horizontal: false, vertical: true)
 64                }
 65            }
 66
 67        case .special(_, let blocks):
 68            OrgBlockListView(blocks: blocks)
 69
 70        case .list(let list):
 71            OrgListView(list: list)
 72
 73        case .table(let table):
 74            OrgTableView(table: table)
 75
 76        case .image(let source, let alt, let caption):
 77            VStack(alignment: .leading, spacing: 4) {
 78                AsyncImage(url: source) { phase in
 79                    switch phase {
 80                    case .success(let image):
 81                        image.resizable().scaledToFit()
 82                    case .failure:
 83                        Text(alt.isEmpty ? source.lastPathComponent : alt)
 84                            .font(.footnote)
 85                            .foregroundStyle(.secondary)
 86                    default:
 87                        ProgressView()
 88                    }
 89                }
 90                .accessibilityLabel(alt)
 91                if let caption {
 92                    Text(caption).foregroundStyle(.secondary)
 93                }
 94            }
 95
 96        case .badges(let badges):
 97            OrgBadgeRowView(badges: badges)
 98
 99        case .captioned(let caption, let blocks):
100            VStack(alignment: .leading, spacing: 4) {
101                OrgBlockListView(blocks: blocks)
102                if let caption {
103                    Text(caption).foregroundStyle(.secondary)
104                }
105            }
106
107        case .rule:
108            Divider()
109        }
110    }
111}