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.9.0
org-swift/Sources/OrgSwiftUI/OrgListView.swift history · blame · raw

77 lines · 2790 bytes

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