aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 6d4f6e25a1fcb18470a7989a3dee4846237202c6 (plain)
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
# 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) }
)
```

## 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