diff options
| author | Ruben Beltran del Rio <jj@r.bdr.sh> | 2026-06-15 14:35:36 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <jj@r.bdr.sh> | 2026-06-16 14:47:30 +0200 |
| commit | 657aa83a5ca24dc35572c040df64a0abb85e8612 (patch) | |
| tree | fe0b6fd425d8cad9dc6a28ada4ad8600e42b367e /README.md | |
Initial extraction from Norganize
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d4f6e2 --- /dev/null +++ b/README.md @@ -0,0 +1,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 |