aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-06-15 14:35:36 +0200
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-06-16 14:47:30 +0200
commit657aa83a5ca24dc35572c040df64a0abb85e8612 (patch)
treefe0b6fd425d8cad9dc6a28ada4ad8600e42b367e /README.md
Initial extraction from Norganize
Diffstat (limited to 'README.md')
-rw-r--r--README.md142
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