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 /// 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) VStack(alignment: .leading, spacing: 6) { 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)) paragraphs(item) } } else { paragraphs(item) } if let sublist = item.sublist { OrgListView(list: sublist) } } } } /// `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 { Text(term).fixedSize(horizontal: false, vertical: true) } paragraphs(item).padding(.leading, 16) if let sublist = item.sublist { OrgListView(list: sublist).padding(.leading, 16) } } } private func paragraphs(_ item: OrgListLayout.Item) -> some View { VStack(alignment: .leading, spacing: 6) { ForEach(Array(item.paragraphs.enumerated()), id: \.offset) { _, paragraph in Text(paragraph) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) } } } 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" } } }