krz/org-swift

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

Sources/OrgSwiftUI/OrgListView.swift

v0.7.1
org-swift/Sources/OrgSwiftUI/OrgListView.swift history · blame · raw

76 lines · 2730 bytes

 1import OrgSwift
 2import SwiftUI
 3
 4/// A list, recursing into its own sublists.
 5///
 6/// SwiftUI has no list-layout primitive, so the marker sits in its own column and the content
 7/// hangs beside it — wrapped lines line up under the text, not under the bullet.
 8struct OrgListView: View {
 9    let list: OrgListLayout
10    /// Ordered numbering restarts at each nesting level, as in org.
11    var body: some View {
12        VStack(alignment: .leading, spacing: 6) {
13            ForEach(Array(list.items.enumerated()), id: \.element.id) { index, item in
14                if list.kind == .description {
15                    descriptionItem(item)
16                } else {
17                    markedItem(item, marker: marker(at: index))
18                }
19            }
20        }
21    }
22
23    private func marker(at index: Int) -> String {
24        list.kind == .ordered ? "\(index + 1)." : "•"
25    }
26
27    private func markedItem(_ item: OrgListLayout.Item, marker: String) -> some View {
28        HStack(alignment: .firstTextBaseline, spacing: 8) {
29            Text(marker)
30                .monospacedDigit()
31                .foregroundStyle(.secondary)
32            if let checkbox = item.checkbox {
33                HStack(alignment: .firstTextBaseline, spacing: 6) {
34                    Image(systemName: Self.symbol(for: checkbox))
35                        .foregroundStyle(checkbox == .on ? Color.accentColor : .secondary)
36                        .accessibilityLabel(Self.label(for: checkbox))
37                    body(item)
38                }
39            } else {
40                body(item)
41            }
42        }
43    }
44
45    /// `term :: definition` — the term above, the definition indented under it.
46    private func descriptionItem(_ item: OrgListLayout.Item) -> some View {
47        VStack(alignment: .leading, spacing: 2) {
48            if let term = item.term {
49                Text(term).fixedSize(horizontal: false, vertical: true)
50            }
51            body(item).padding(.leading, 16)
52        }
53    }
54
55    /// The item's blocks, rendered by the same view the document body uses — a nested list,
56    /// a source block, and a table all come out looking as they would anywhere else.
57    private func body(_ item: OrgListLayout.Item) -> some View {
58        OrgBlockListView(blocks: item.blocks)
59    }
60
61    private static func symbol(for checkbox: OrgCheckbox) -> String {
62        switch checkbox {
63        case .on: "checkmark.square.fill"
64        case .off: "square"
65        case .partial: "minus.square"
66        }
67    }
68
69    private static func label(for checkbox: OrgCheckbox) -> String {
70        switch checkbox {
71        case .on: "Done"
72        case .off: "Not done"
73        case .partial: "Partly done"
74        }
75    }
76}