import OrgSwift import SwiftUI /// A list, recursing into its own sublists. /// /// SwiftUI has no list-layout primitive, so the marker sits in its own column and the content /// hangs beside it — wrapped lines line up under the text, not under the bullet. struct OrgListView: View { let list: OrgListLayout @Environment(\.orgMath) private var math /// Ordered numbering restarts at each nesting level, as in org. var body: some View { VStack(alignment: .leading, spacing: 6) { ForEach(Array(list.items.enumerated()), id: \.element.id) { index, item in if list.kind == .description { descriptionItem(item) } else { markedItem(item, marker: marker(at: index)) } } } } private func marker(at index: Int) -> String { list.kind == .ordered ? "\(index + 1)." : "•" } private func markedItem(_ item: OrgListLayout.Item, marker: String) -> some View { HStack(alignment: .firstTextBaseline, spacing: 8) { Text(marker) .monospacedDigit() .foregroundStyle(.secondary) if let checkbox = item.checkbox { HStack(alignment: .firstTextBaseline, spacing: 6) { Image(systemName: Self.symbol(for: checkbox)) .foregroundStyle(checkbox == .on ? Color.accentColor : .secondary) .accessibilityLabel(Self.label(for: checkbox)) body(item) } } else { body(item) } } } /// `term :: definition` — the term above, the definition indented under it. private func descriptionItem(_ item: OrgListLayout.Item) -> some View { VStack(alignment: .leading, spacing: 2) { if let term = item.term { orgText(term, math: math).fixedSize(horizontal: false, vertical: true) } body(item).padding(.leading, 16) } } /// The item's blocks, rendered by the same view the document body uses — a nested list, /// a source block, and a table all come out looking as they would anywhere else. private func body(_ item: OrgListLayout.Item) -> some View { OrgBlockListView(blocks: item.blocks) } private static func symbol(for checkbox: OrgCheckbox) -> String { switch checkbox { case .on: "checkmark.square.fill" case .off: "square" case .partial: "minus.square" } } private static func label(for checkbox: OrgCheckbox) -> String { switch checkbox { case .on: "Done" case .off: "Not done" case .partial: "Partly done" } } }