1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
# NorgUI
NorgUI provides themable SwiftUI views to render `.norg` / [Neorg](https://github.com/nvim-neorg/neorg) documents
parsed with [NorgKit](https://git.sr.ht/~rbdr/norgkit).
`NorgUI` doesn't provide editing capabilities.
## Installation
Add the package with Xcode package manager: `https://git.r.bdr.sh/norgui`, and
depend on `NorgUI` from your target.
## Usage
Parse a document with `NorgKit` and render it with `NorgView`:
import SwiftUI
import NorgKit
import NorgUI
```swift
struct NoteView: View {
let source: String
var body: some View {
ScrollView {
NorgView(document: NorgParser.parse(source)) { line, status in
// Persist the new status back to your file at `line`.
}
.padding()
}
}
}
```
The `onSetStatus` callback is optional (defaults to a no-op) and fires when the
user taps or long-presses a task's status symbol, reporting the source `line`
and the chosen `TaskStatus`.
### Links
Links are handled via an `onOpenLink` optional callback which will give you the
`InlineLink`. If you don't provide one, external links will be handled by the
system, while internal links and anchors will be inert.
```swift
NorgView(document: document) { link in
switch link.target {
case let target? where target.hasPrefix("http"): openURL(URL(string: target)!)
default: scrollTo(target: link.target) // a heading/anchor in this document
}
}
```
### Collapsible View
Pass `collapsible: true` to render the document as a tree of `DisclosureGroup`s.
Headings collapse their content, nested lists and quotes collapse their
children, and definitions/footnotes/table cells collapse their body. Each
section manages its own expand/collapse state and starts expanded.
```swift
NorgView(document: NorgParser.parse(source), collapsible: true)
```
### Tasks
You can render tasks independently (eg. if you got them via `TaskScanner`) with
`NorgTaskView`.
```swift
NorgTaskView(
status: task.status,
text: AttributedString(task.text),
onToggle: { setStatus(task, .done) },
onSetStatus: { setStatus(task, $0) }
)
```
### Rendering Math
NorgUI will not render math by default, but provides a trait `SwiftMathSupport`
that adds this capability and includes `SwiftMath` as a dependency.
You can also roll your own math renderer by implementing the NorgMathRenderer
protocol and passing it to the `NorgTheme`'s `mathRenderer` property.
## Theming
Fonts and colors can be changed by creating a `NorgTheme` and using the
`.norgTheme` modifier.
```swift
var theme = NorgTheme.default
theme.linkColor = .purple
theme.heading = { level in level == 1 ? .largeTitle.bold() : .title2 }
theme.statusSymbol = { $0 == .done ? "checkmark.square.fill" : "square" }
NorgView(document: document)
.norgTheme(theme)
```
Note: `AttributedString(norg:)` can't read the environment, so you need to pass
the theme explicitly if you build text outside the views.
```swift
let styled = AttributedString(norg: spans, theme: theme)
```
### NorgTheme API
| Property | Type | Default | Styles |
| --- | --- | --- | --- |
| `heading` | `(Int) -> Font` | `.title.bold()` (lvl 1) down to `.subheadline` | Heading text, per level |
| `body` | `Font` | `.body` | Paragraphs, list items, quotes |
| `termFont` | `Font` | `.body.bold()` | Definition / footnote / table-cell titles |
| `verbatimColor` | `Color` | `.pink` | `verbatim` inline spans |
| `spoilerForeground` | `Color` | `.secondary` | `spoiler` inline text |
| `spoilerBackground` | `Color` | `.secondary.opacity(0.25)` | `spoiler` inline background |
| `linkColor` | `Color` | `.accentColor` | Links and anchors |
| `superscriptOffset` | `CGFloat` | `5` | `superscript` baseline offset |
| `subscriptOffset` | `CGFloat` | `-3` | `subscript` baseline offset |
| `quoteColor` | `Color` | `.secondary` | Quote text |
| `quoteBarColor` | `Color` | `.secondary` | Quote's vertical bar |
| `codeBackground` | `AnyShapeStyle` | `.quaternary` | Code blocks and ranged tags |
| `codeFont` | `Font` | `.system(.callout, design: .monospaced)` | Code blocks and ranged tags |
| `indentWidth` | `CGFloat` | `18` | Indentation per nesting level |
| `statusTint` | `(TaskStatus) -> Color` | `.green` / `.red` / `.blue` / `.secondary` | Task status symbol colour |
| `statusSymbol` | `(TaskStatus) -> String` | SF Symbols (`circle`, `checkmark.circle.fill`, …) | Task status symbol |
| `statusLabel` | `(TaskStatus) -> LocalizedStringResource` | `TaskStatus.displayName` | Task status menu label & accessibility |
## Localization
NorgUI provides labels for the long-press menu in tasks. These can be localized
either through the package's own string catalog, but can be overridden in
the theme.
```swift
var theme = NorgTheme.default
theme.statusLabel = { status in
LocalizedStringResource(String.LocalizationValue(status.rawValue), table: "Tasks")
}
```
`statusLabel` defaults to `TaskStatus.displayName`.
## Requirements
- Swift 6 tools
- iOS 18 / macOS 15 / visionOS 2
|