cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

docs: core v1 documentation

* updated ROADMAP

+1755 -328
+6
.vscode/settings.json
···
··· 1 + { 2 + "[typescript]": { "editor.defaultFormatter": "dprint.dprint" }, 3 + "[typescriptreact]": { "editor.defaultFormatter": "dprint.dprint" }, 4 + "[javascript]": { "editor.defaultFormatter": "dprint.dprint" }, 5 + "[javascriptreact]": { "editor.defaultFormatter": "dprint.dprint" } 6 + }
+60 -27
internal/docs/ROADMAP.md
··· 47 - [ ] Add [OAuth2](#publications--authentication) 48 - [x] Verify `pub pull` fetches and syncs documents from leaflet. 49 - [x] Confirm `pub list` with status filtering (`all`, `published`, `draft`). 50 - - [ ] Test `pub post` creates new documents with draft/preview/validate modes. 51 - - [ ] Ensure `pub patch` updates existing documents correctly. 52 - - [ ] Validate `pub push` handles batch operations (create/update). 53 - - [ ] Verify markdown conversion to leaflet block format (headings, code, images, facets). 54 55 ### Media Domains 56 ··· 200 - [ ] External imports (Goodreads, IMDB, Letterboxd) 201 - [ ] Cross-referencing across media types 202 - [ ] Analytics: velocity, completion rates 203 204 ### Articles 205 ··· 232 - [ ] Ephemeral HTTP server on localhost 233 - [ ] Browser launch integration 234 - [ ] Timeout handling for abandoned flows 235 - - [ ] Migration path from app passwords to OAuth 236 - - [ ] Detect existing app password sessions 237 - - [ ] Prompt users to upgrade authentication 238 - - [ ] Maintain backward compatibility 239 240 ### User Experience 241 ··· 243 - [ ] Manpages and docs generator 244 - [ ] Theming and customizable output 245 - [ ] Calendar integration 246 247 ### Tasks 248 ··· 274 275 ### Local API Server 276 277 - A local HTTP server daemon that exposes Noteleaf data for web UIs and extensions. Runs on the user's machine and provides programmatic access to tasks, notes, and media. 278 279 #### Architecture 280 ··· 402 #### Post v1 403 404 - Backup/restore 405 - Multiple profiles 406 - Optional synchronization 407 408 ## v1 Feature Matrix 409 410 - | Domain | Feature | Status | 411 - |----------|-----------------------|-----------| 412 - | Tasks | CRUD | Complete | 413 - | Tasks | Projects/tags | Complete | 414 - | Tasks | Time tracking | Complete | 415 - | Tasks | Dependencies | Complete | 416 - | Tasks | Recurrence | Complete | 417 - | Tasks | Wait/scheduled | Planned | 418 - | Tasks | Urgency scoring | Planned | 419 - | Notes | CRUD | Complete | 420 - | Notes | Search/tagging | Planned | 421 - | Media | Books/movies/TV | Complete | 422 - | Media | Articles | Complete | 423 - | Media | Source/ratings | Planned | 424 - | Articles | Parser + storage | Complete | 425 - | System | SQLite persistence | Complete | 426 - | System | Synchronization | Future | 427 - | System | Import/export formats | Future |
··· 47 - [ ] Add [OAuth2](#publications--authentication) 48 - [x] Verify `pub pull` fetches and syncs documents from leaflet. 49 - [x] Confirm `pub list` with status filtering (`all`, `published`, `draft`). 50 + - [x] Test `pub post` creates new documents with draft/preview/validate modes. 51 + - [x] Ensure `pub patch` updates existing documents correctly. 52 + - [x] Validate `pub push` handles batch operations (create/update). 53 + - [x] Verify markdown conversion to leaflet block format (headings, code, images, facets). 54 55 ### Media Domains 56 ··· 200 - [ ] External imports (Goodreads, IMDB, Letterboxd) 201 - [ ] Cross-referencing across media types 202 - [ ] Analytics: velocity, completion rates 203 + - [ ] Books (Open Library integration enhancements) 204 + - [ ] Author detail fetching (full names, bio) 205 + - [ ] Edition-specific metadata 206 + - [ ] Cover image download and caching 207 + - [ ] Reading progress tracking 208 + - [ ] Personal reading lists sync 209 + - [ ] Movies/TV (external API integration) 210 + - [ ] Movie databases (TMDb, OMDb) 211 + - [ ] Rotten Tomatoes integration 212 + - [ ] Music 213 + - [ ] Music services (MusicBrainz, Album of the Year) 214 215 ### Articles 216 ··· 243 - [ ] Ephemeral HTTP server on localhost 244 - [ ] Browser launch integration 245 - [ ] Timeout handling for abandoned flows 246 + - [ ] Support both OAuth & App Passwords but recommend OAuth 247 + - [ ] Leaflet.pub enhancements 248 + - [ ] Multiple Publications: Manage separate publications for different topics 249 + - [ ] Image Upload: Automatically upload images to blob storage and embed in documents 250 + - [ ] Status Management: Publish drafts and unpublish documents from CLI 251 + - [ ] Metadata Editing: Update document titles, summaries, and tags 252 + - [ ] Backlink Support: Parse and resolve cross-references between documents 253 + - [ ] Offline Mode: Queue posts and patches for later upload 254 255 ### User Experience 256 ··· 258 - [ ] Manpages and docs generator 259 - [ ] Theming and customizable output 260 - [ ] Calendar integration 261 + - [ ] Task synchronization services 262 + - [ ] Git repository linking 263 + - [ ] Note export to other platforms 264 265 ### Tasks 266 ··· 292 293 ### Local API Server 294 295 + A local HTTP server daemon that exposes Noteleaf data for web UIs and extensions. 296 + Runs on the user's machine and provides programmatic access to tasks, notes, and media. 297 298 #### Architecture 299 ··· 421 #### Post v1 422 423 - Backup/restore 424 + - [ ] Automated backups 425 + - [ ] Backup scheduling and rotation 426 - Multiple profiles 427 - Optional synchronization 428 + - [ ] Sync service 429 + - Import/Export 430 + - [ ] CSV export for tasks 431 + - [ ] Markdown export for tasks 432 + - [ ] Bulk export commands 433 + - [ ] Migration utilities (TaskWarrior, todo.txt, etc.) 434 + - [ ] Git integration for notes/data versioning 435 436 ## v1 Feature Matrix 437 438 + | Domain | Feature | Status | 439 + |--------------|----------------------------|-----------| 440 + | Tasks | CRUD | Complete | 441 + | Tasks | Projects/tags | Complete | 442 + | Tasks | Time tracking | Complete | 443 + | Tasks | Dependencies | Complete | 444 + | Tasks | Recurrence | Complete | 445 + | Tasks | Wait/scheduled | Planned | 446 + | Tasks | Urgency scoring | Planned | 447 + | Notes | CRUD | Complete | 448 + | Notes | Search/tagging | Planned | 449 + | Publications | AT Protocol sync | Complete | 450 + | Publications | Post/patch/push | Complete | 451 + | Publications | Markdown conversion | Complete | 452 + | Publications | OAuth2 | Future | 453 + | Media | Books/movies/TV | Complete | 454 + | Media | Articles | Complete | 455 + | Media | Source/ratings | Planned | 456 + | Articles | Parser + storage | Complete | 457 + | System | SQLite persistence | Complete | 458 + | System | Configuration management | Complete | 459 + | System | Synchronization | Future | 460 + | System | Import/export formats | Future |
+16 -24
website/TODO.md
··· 4 5 ## Integration and Workflows 6 7 - - [ ] External Integrations 8 - - [ ] Open Library API 9 - - [ ] Leaflet.pub API 10 - - [ ] ATProto authentication 11 - [ ] Workflows and Examples 12 - [ ] Daily task review workflow 13 - [ ] Note-taking for research 14 - [ ] Reading list management 15 - [ ] Publishing a blog post to leaflet.pub 16 - [ ] Linking tasks, notes, and media 17 - - [ ] Import/Export 18 - - [ ] Exporting data 19 - - [ ] Backup and restore 20 - - [ ] Migration from other tools 21 22 ## Development 23 24 - - [ ] Building Noteleaf 25 - - [ ] Development vs production builds 26 - - [ ] Build tags 27 - - [ ] Task automation (Taskfile) 28 - - [ ] Testing 29 - - [ ] Running tests 30 - - [ ] Coverage reports 31 - - [ ] Test patterns and scaffolding 32 - [ ] Contributing 33 - [ ] Code organization 34 - [ ] Adding new commands ··· 54 - [ ] Zettelkasten method 55 - [ ] Research notes 56 - [ ] Meeting notes 57 - - [ ] Publishing Tutorials 58 - - [ ] Writing a blog post 59 - - [ ] Creating a publication 60 - - [ ] Managing drafts 61 - - [ ] Advanced Tutorials 62 - - [ ] Scripting with Noteleaf 63 - - [ ] Custom automation 64 - - [ ] Data analysis 65 66 ## Appendices 67
··· 4 5 ## Integration and Workflows 6 7 + - [x] External Integrations 8 + - [x] Open Library API 9 + - [x] Leaflet.pub API 10 + - [x] ATProto authentication 11 - [ ] Workflows and Examples 12 - [ ] Daily task review workflow 13 - [ ] Note-taking for research 14 - [ ] Reading list management 15 - [ ] Publishing a blog post to leaflet.pub 16 - [ ] Linking tasks, notes, and media 17 + - [x] Import/Export 18 + - [x] Exporting data 19 + - [x] Backup and restore 20 + - [x] Migration from other tools 21 22 ## Development 23 24 + - [x] Building Noteleaf 25 + - [x] Development vs production builds 26 + - [x] Build tags 27 + - [x] Task automation (Taskfile) 28 + - [x] Testing 29 + - [x] Running tests 30 + - [x] Coverage reports 31 + - [x] Test patterns and scaffolding 32 - [ ] Contributing 33 - [ ] Code organization 34 - [ ] Adding new commands ··· 54 - [ ] Zettelkasten method 55 - [ ] Research notes 56 - [ ] Meeting notes 57 58 ## Appendices 59
+167
website/docs/development/building.md
···
··· 1 + --- 2 + title: Building Noteleaf 3 + sidebar_label: Building 4 + sidebar_position: 1 5 + description: Build configurations and development workflows. 6 + --- 7 + 8 + # Building Noteleaf 9 + 10 + Noteleaf uses [Task](https://taskfile.dev) for build automation, providing consistent workflows across development, testing, and releases. 11 + 12 + ## Prerequisites 13 + 14 + - Go 1.21 or later 15 + - [Task](https://taskfile.dev) (install via `brew install go-task/tap/go-task` on macOS) 16 + - Git (for version information) 17 + 18 + ## Build Types 19 + 20 + ### Development Build 21 + 22 + Quick build without version injection for local development: 23 + 24 + ```sh 25 + task build 26 + ``` 27 + 28 + Output: `./tmp/noteleaf` 29 + 30 + ### Development Build with Version 31 + 32 + Build with git commit hash and development tools enabled: 33 + 34 + ```sh 35 + task build:dev 36 + ``` 37 + 38 + Version format: `git describe` output (e.g., `v0.1.0-15-g1234abc`) 39 + Output: `./tmp/noteleaf` 40 + 41 + ### Release Candidate Build 42 + 43 + Build with `-rc` tag, excludes development tools: 44 + 45 + ```sh 46 + git tag v1.0.0-rc1 47 + task build:rc 48 + ``` 49 + 50 + Requirements: 51 + 52 + - Clean git tag with `-rc` suffix 53 + - Tag format: `v1.0.0-rc1`, `v2.1.0-rc2`, etc. 54 + 55 + ### Production Build 56 + 57 + Build for release with strict validation: 58 + 59 + ```sh 60 + git tag v1.0.0 61 + task build:prod 62 + ``` 63 + 64 + Requirements: 65 + 66 + - Clean semver git tag (e.g., `v1.0.0`, `v2.1.3`) 67 + - No uncommitted changes 68 + - No prerelease suffix 69 + 70 + ## Build Tags 71 + 72 + Production builds use the `prod` build tag to exclude development and seed commands: 73 + 74 + ```go 75 + //go:build !prod 76 + ``` 77 + 78 + Commands excluded from production: 79 + 80 + - `noteleaf dev` - Development utilities 81 + - `noteleaf seed` - Test data generation 82 + 83 + ## Version Information 84 + 85 + Build process injects version metadata via ldflags: 86 + 87 + ```go 88 + // internal/version/version.go 89 + var ( 90 + Version = "dev" // Git tag or "dev" 91 + Commit = "none" // Git commit hash 92 + BuildDate = "unknown" // Build timestamp 93 + ) 94 + ``` 95 + 96 + View version information: 97 + 98 + ```sh 99 + task version:show 100 + noteleaf version 101 + ``` 102 + 103 + ## Build Artifacts 104 + 105 + All binaries are built to `./tmp/` directory: 106 + 107 + ``` 108 + tmp/ 109 + โ””โ”€โ”€ noteleaf # Binary for current platform 110 + ``` 111 + 112 + ## Development Workflow 113 + 114 + Full development cycle with linting and testing: 115 + 116 + ```sh 117 + task dev 118 + ``` 119 + 120 + Runs: 121 + 122 + 1. Clean build artifacts 123 + 2. Run linters (vet, fmt) 124 + 3. Execute all tests 125 + 4. Build binary 126 + 127 + ## Manual Build 128 + 129 + Build directly with Go (bypasses Task automation): 130 + 131 + ```sh 132 + go build -o ./tmp/noteleaf ./cmd 133 + ``` 134 + 135 + With version injection: 136 + 137 + ```sh 138 + go build -ldflags "-X github.com/stormlightlabs/noteleaf/internal/version.Version=v1.0.0" -o ./tmp/noteleaf ./cmd 139 + ``` 140 + 141 + ## Cross-Platform Builds 142 + 143 + Build for specific platforms: 144 + 145 + ```sh 146 + # Linux 147 + GOOS=linux GOARCH=amd64 go build -o ./tmp/noteleaf-linux ./cmd 148 + 149 + # Windows 150 + GOOS=windows GOARCH=amd64 go build -o ./tmp/noteleaf.exe ./cmd 151 + 152 + # macOS (ARM) 153 + GOOS=darwin GOARCH=arm64 go build -o ./tmp/noteleaf-darwin-arm64 ./cmd 154 + ``` 155 + 156 + ## Clean Build 157 + 158 + Remove all build artifacts: 159 + 160 + ```sh 161 + task clean 162 + ``` 163 + 164 + Removes: 165 + 166 + - `./tmp/` directory 167 + - `coverage.out` and `coverage.html`
+331
website/docs/development/taskfile.md
···
··· 1 + --- 2 + title: Task Automation 3 + sidebar_label: Taskfile 4 + sidebar_position: 3 5 + description: Using Taskfile for development workflows. 6 + --- 7 + 8 + # Task Automation 9 + 10 + Noteleaf uses [Task](https://taskfile.dev) to automate common development workflows. 11 + 12 + ## Installation 13 + 14 + ### macOS 15 + 16 + ```sh 17 + brew install go-task/tap/go-task 18 + ``` 19 + 20 + ### Linux 21 + 22 + ```sh 23 + sh -c "$(curl -fsSL https://taskfile.dev/install.sh)" 24 + ``` 25 + 26 + ### Go Install 27 + 28 + ```sh 29 + go install github.com/go-task/task/v3/cmd/task@latest 30 + ``` 31 + 32 + ## Available Tasks 33 + 34 + View all tasks: 35 + 36 + ```sh 37 + task 38 + # or 39 + task --list 40 + ``` 41 + 42 + ## Common Tasks 43 + 44 + ### Build Commands 45 + 46 + **task build** - Quick development build 47 + 48 + ```sh 49 + task build 50 + ``` 51 + 52 + Output: `./tmp/noteleaf` 53 + 54 + **task build:dev** - Build with version information 55 + 56 + ```sh 57 + task build:dev 58 + ``` 59 + 60 + Includes git commit hash and build date. 61 + 62 + **task build:rc** - Release candidate build 63 + 64 + ```sh 65 + git tag v1.0.0-rc1 66 + task build:rc 67 + ``` 68 + 69 + Requires git tag with `-rc` suffix. 70 + 71 + **task build:prod** - Production build 72 + 73 + ```sh 74 + git tag v1.0.0 75 + task build:prod 76 + ``` 77 + 78 + Requires clean semver tag and no uncommitted changes. 79 + 80 + ### Testing Commands 81 + 82 + **task test** - Run all tests 83 + 84 + ```sh 85 + task test 86 + ``` 87 + 88 + **task coverage** - Generate HTML coverage report 89 + 90 + ```sh 91 + task coverage 92 + open coverage.html # View report 93 + ``` 94 + 95 + **task cov** - Terminal coverage summary 96 + 97 + ```sh 98 + task cov 99 + ``` 100 + 101 + **task check** - Lint and coverage 102 + 103 + ```sh 104 + task check 105 + ``` 106 + 107 + Runs linters and generates coverage report. 108 + 109 + ### Development Commands 110 + 111 + **task dev** - Full development workflow 112 + 113 + ```sh 114 + task dev 115 + ``` 116 + 117 + Runs: 118 + 119 + 1. `task clean` 120 + 2. `task lint` 121 + 3. `task test` 122 + 4. `task build` 123 + 124 + **task lint** - Run linters 125 + 126 + ```sh 127 + task lint 128 + ``` 129 + 130 + Runs `go vet` and `go fmt`. 131 + 132 + **task run** - Build and run 133 + 134 + ```sh 135 + task run 136 + ``` 137 + 138 + Builds then executes the binary. 139 + 140 + ### Maintenance Commands 141 + 142 + **task clean** - Remove build artifacts 143 + 144 + ```sh 145 + task clean 146 + ``` 147 + 148 + Removes: 149 + 150 + - `./tmp/` directory 151 + - Coverage files 152 + 153 + **task deps** - Download and tidy dependencies 154 + 155 + ```sh 156 + task deps 157 + ``` 158 + 159 + Runs `go mod download` and `go mod tidy`. 160 + 161 + ### Documentation Commands 162 + 163 + **task docs:generate** - Generate all documentation 164 + 165 + ```sh 166 + task docs:generate 167 + ``` 168 + 169 + Generates: 170 + 171 + - Docusaurus docs (website/docs/manual) 172 + - Man pages (docs/manual) 173 + 174 + **task docs:man** - Generate man pages 175 + 176 + ```sh 177 + task docs:man 178 + ``` 179 + 180 + **task docs:serve** - Start documentation server 181 + 182 + ```sh 183 + task docs:serve 184 + ``` 185 + 186 + Starts Docusaurus development server at <http://localhost:3000>. 187 + 188 + ### Version Commands 189 + 190 + **task version:show** - Display version info 191 + 192 + ```sh 193 + task version:show 194 + ``` 195 + 196 + Shows: 197 + 198 + - Git tag 199 + - Git commit 200 + - Git describe output 201 + - Build date 202 + 203 + **task version:validate** - Validate git tag 204 + 205 + ```sh 206 + task version:validate 207 + ``` 208 + 209 + Checks tag format for releases. 210 + 211 + ### Utility Commands 212 + 213 + **task status** - Show Go environment 214 + 215 + ```sh 216 + task status 217 + ``` 218 + 219 + Displays: 220 + 221 + - Go version 222 + - Module information 223 + - Dependencies 224 + 225 + ## Taskfile Variables 226 + 227 + Variables injected during build: 228 + 229 + ```yaml 230 + BINARY_NAME: noteleaf 231 + BUILD_DIR: ./tmp 232 + VERSION_PKG: github.com/stormlightlabs/noteleaf/internal/version 233 + GIT_COMMIT: $(git rev-parse --short HEAD) 234 + GIT_TAG: $(git describe --tags --exact-match) 235 + BUILD_DATE: $(date -u +"%Y-%m-%dT%H:%M:%SZ") 236 + ``` 237 + 238 + ## Task Dependencies 239 + 240 + Some tasks automatically trigger others: 241 + 242 + ```sh 243 + task run 244 + # Automatically runs: task build 245 + ``` 246 + 247 + ```sh 248 + task dev 249 + # Runs in sequence: 250 + # 1. task clean 251 + # 2. task lint 252 + # 3. task test 253 + # 4. task build 254 + ``` 255 + 256 + ## Custom Workflows 257 + 258 + ### Pre-commit Workflow 259 + 260 + ```sh 261 + task lint && task test 262 + ``` 263 + 264 + ### Release Preparation 265 + 266 + ```sh 267 + task check && \ 268 + git tag v1.0.0 && \ 269 + task build:prod && \ 270 + ./tmp/noteleaf version 271 + ``` 272 + 273 + ### Documentation Preview 274 + 275 + ```sh 276 + task docs:generate 277 + task docs:serve 278 + ``` 279 + 280 + ### Full CI Simulation 281 + 282 + ```sh 283 + task clean && \ 284 + task deps && \ 285 + task lint && \ 286 + task test && \ 287 + task coverage && \ 288 + task build:dev 289 + ``` 290 + 291 + ## Taskfile Structure 292 + 293 + Location: `Taskfile.yml` (project root) 294 + 295 + Key sections: 296 + 297 + - **vars**: Build variables and git information 298 + - **tasks**: Command definitions with descriptions 299 + - **deps**: Task dependencies 300 + - **preconditions**: Validation before execution 301 + 302 + ## Configuration 303 + 304 + Customize via `Taskfile.yml` or environment variables: 305 + 306 + ```yaml 307 + vars: 308 + BINARY_NAME: noteleaf 309 + BUILD_DIR: ./tmp 310 + ``` 311 + 312 + Override at runtime: 313 + 314 + ```sh 315 + BINARY_NAME=custom-noteleaf task build 316 + ``` 317 + 318 + ## Why Task Over Make? 319 + 320 + - Cross-platform (Windows, macOS, Linux) 321 + - YAML syntax (more readable than Makefile) 322 + - Built-in variable interpolation 323 + - Better dependency management 324 + - Precondition validation 325 + - Native Go integration 326 + 327 + ## Further Reading 328 + 329 + - [Task Documentation](https://taskfile.dev) 330 + - [Taskfile Schema](https://taskfile.dev/api/) 331 + - Project Taskfile: `Taskfile.yml`
+268
website/docs/development/testing.md
···
··· 1 + --- 2 + title: Testing 3 + sidebar_label: Testing 4 + sidebar_position: 2 5 + description: Running tests and understanding test patterns. 6 + --- 7 + 8 + # Testing 9 + 10 + Noteleaf maintains comprehensive test coverage using Go's built-in testing framework with consistent patterns across the codebase. 11 + 12 + ## Running Tests 13 + 14 + ### All Tests 15 + 16 + ```sh 17 + task test 18 + # or 19 + go test ./... 20 + ``` 21 + 22 + ### Coverage Report 23 + 24 + Generate HTML coverage report: 25 + 26 + ```sh 27 + task coverage 28 + ``` 29 + 30 + Output: `coverage.html` (opens in browser) 31 + 32 + ### Terminal Coverage 33 + 34 + View coverage in terminal: 35 + 36 + ```sh 37 + task cov 38 + ``` 39 + 40 + Shows function-level coverage percentages. 41 + 42 + ### Package-Specific Tests 43 + 44 + Test specific package: 45 + 46 + ```sh 47 + go test ./internal/repo 48 + go test ./internal/handlers 49 + go test ./cmd 50 + ``` 51 + 52 + ### Verbose Output 53 + 54 + ```sh 55 + go test -v ./... 56 + ``` 57 + 58 + ## Test Organization 59 + 60 + Tests follow a hierarchical 3-level structure: 61 + 62 + ```go 63 + func TestRepositoryName(t *testing.T) { 64 + // Setup once 65 + db := CreateTestDB(t) 66 + repos := SetupTestData(t, db) 67 + 68 + t.Run("Feature", func(t *testing.T) { 69 + t.Run("scenario description", func(t *testing.T) { 70 + // Test logic 71 + }) 72 + }) 73 + } 74 + ``` 75 + 76 + Levels: 77 + 78 + 1. Package (top function) 79 + 2. Feature (first t.Run) 80 + 3. Scenario (nested t.Run) 81 + 82 + ## Test Patterns 83 + 84 + ### Repository Tests 85 + 86 + Repository tests use scaffolding from `internal/repo/test_utilities.go`: 87 + 88 + ```go 89 + func TestTaskRepository(t *testing.T) { 90 + db := CreateTestDB(t) 91 + repos := SetupTestData(t, db) 92 + ctx := context.Background() 93 + 94 + t.Run("Create", func(t *testing.T) { 95 + t.Run("creates task successfully", func(t *testing.T) { 96 + task := NewTaskBuilder(). 97 + WithDescription("Test task"). 98 + Build() 99 + 100 + created, err := repos.Tasks.Create(ctx, task) 101 + AssertNoError(t, err, "create should succeed") 102 + AssertEqual(t, "Test task", created.Description, "description should match") 103 + }) 104 + }) 105 + } 106 + ``` 107 + 108 + ### Handler Tests 109 + 110 + Handler tests use `internal/handlers/handler_test_suite.go`: 111 + 112 + ```go 113 + func TestHandlerName(t *testing.T) { 114 + suite := NewHandlerTestSuite(t) 115 + defer suite.cleanup() 116 + handler := CreateHandler(t, NewHandlerFunc) 117 + 118 + t.Run("Feature", func(t *testing.T) { 119 + t.Run("scenario", func(t *testing.T) { 120 + AssertNoError(t, handler.Method(), "operation should succeed") 121 + }) 122 + }) 123 + } 124 + ``` 125 + 126 + ## Test Utilities 127 + 128 + ### Assertion Helpers 129 + 130 + Located in `internal/repo/test_utilities.go` and `internal/handlers/test_utilities.go`: 131 + 132 + ```go 133 + // Error checking 134 + AssertNoError(t, err, "operation should succeed") 135 + AssertError(t, err, "operation should fail") 136 + 137 + // Value comparison 138 + AssertEqual(t, expected, actual, "values should match") 139 + AssertTrue(t, condition, "should be true") 140 + AssertFalse(t, condition, "should be false") 141 + 142 + // Nil checking 143 + AssertNil(t, value, "should be nil") 144 + AssertNotNil(t, value, "should not be nil") 145 + 146 + // String operations 147 + AssertContains(t, str, substr, "should contain substring") 148 + ``` 149 + 150 + ### Test Data Builders 151 + 152 + Create test data with builders: 153 + 154 + ```go 155 + task := NewTaskBuilder(). 156 + WithDescription("Test task"). 157 + WithStatus("pending"). 158 + WithPriority("high"). 159 + WithProject("test-project"). 160 + Build() 161 + 162 + book := NewBookBuilder(). 163 + WithTitle("Test Book"). 164 + WithAuthor("Test Author"). 165 + Build() 166 + 167 + note := NewNoteBuilder(). 168 + WithTitle("Test Note"). 169 + WithContent("Test content"). 170 + Build() 171 + ``` 172 + 173 + ### Test Database 174 + 175 + In-memory SQLite for isolated tests: 176 + 177 + ```go 178 + db := CreateTestDB(t) // Automatic cleanup via t.Cleanup() 179 + ``` 180 + 181 + ### Sample Data 182 + 183 + Pre-populated test data: 184 + 185 + ```go 186 + repos := SetupTestData(t, db) 187 + // Creates tasks, notes, books, movies, TV shows 188 + ``` 189 + 190 + ## Test Naming 191 + 192 + Use direct descriptions without "should": 193 + 194 + ```go 195 + t.Run("creates task successfully", func(t *testing.T) { }) // Good 196 + t.Run("should create task", func(t *testing.T) { }) // Bad 197 + t.Run("returns error for invalid input", func(t *testing.T) { }) // Good 198 + ``` 199 + 200 + ## Test Independence 201 + 202 + Each test must be independent: 203 + 204 + - Use `CreateTestDB(t)` for isolated database 205 + - Don't rely on test execution order 206 + - Clean up resources with `t.Cleanup()` 207 + - Avoid package-level state 208 + 209 + ## Coverage Targets 210 + 211 + Maintain high coverage for: 212 + 213 + - Repository layer (data access) 214 + - Handler layer (business logic) 215 + - Services (external integrations) 216 + - Models (data validation) 217 + 218 + Current coverage visible via: 219 + 220 + ```sh 221 + task cov 222 + ``` 223 + 224 + ## Continuous Integration 225 + 226 + Tests run automatically on: 227 + 228 + - Pull requests 229 + - Main branch commits 230 + - Release builds 231 + 232 + CI configuration validates: 233 + 234 + - All tests pass 235 + - No race conditions 236 + - Coverage thresholds met 237 + 238 + ## Debugging Tests 239 + 240 + ### Run Single Test 241 + 242 + ```sh 243 + go test -run TestTaskRepository ./internal/repo 244 + go test -run TestTaskRepository/Create ./internal/repo 245 + ``` 246 + 247 + ### Race Detector 248 + 249 + ```sh 250 + go test -race ./... 251 + ``` 252 + 253 + ### Verbose with Stack Traces 254 + 255 + ```sh 256 + go test -v -race ./internal/repo 2>&1 | grep -A 10 "FAIL" 257 + ``` 258 + 259 + ## Best Practices 260 + 261 + 1. Write tests for all public APIs 262 + 2. Use builders for complex test data 263 + 3. Apply semantic assertion helpers 264 + 4. Keep tests focused and readable 265 + 5. Test both success and error paths 266 + 6. Avoid brittle time-based tests 267 + 7. Mock external dependencies 268 + 8. Use table-driven tests for variations
+204
website/docs/integrations/openlibrary.md
···
··· 1 + --- 2 + title: Open Library API 3 + sidebar_label: Open Library 4 + sidebar_position: 1 5 + description: Book metadata via Open Library API integration. 6 + --- 7 + 8 + # Open Library API 9 + 10 + Noteleaf integrates with [Open Library](https://openlibrary.org) to fetch book metadata, search for books, and enrich your reading list. 11 + 12 + ## Overview 13 + 14 + Open Library provides: 15 + 16 + - Book search by title, author, ISBN 17 + - Work and edition metadata 18 + - Author information 19 + - Cover images 20 + - Subject classifications 21 + - Publication details 22 + 23 + ## Configuration 24 + 25 + No API key required. Open Library is a free, open API service. 26 + 27 + Optional user agent configuration is handled automatically: 28 + 29 + ```toml 30 + # .noteleaf.conf.toml 31 + # No configuration needed for Open Library 32 + ``` 33 + 34 + ## Rate Limiting 35 + 36 + Open Library enforces rate limits: 37 + 38 + - 180 requests per minute 39 + - 3 requests per second 40 + - Burst limit: 5 requests 41 + 42 + Noteleaf automatically manages rate limiting to stay within these boundaries. 43 + 44 + ## Book Search 45 + 46 + Search for books from the command line: 47 + 48 + ```sh 49 + noteleaf book search "Design Patterns" 50 + noteleaf book search "Neal Stephenson" 51 + ``` 52 + 53 + Interactive selection shows: 54 + 55 + - Title 56 + - Author(s) 57 + - First publication year 58 + - Edition count 59 + - Publisher information 60 + 61 + ## Book Metadata 62 + 63 + When adding a book, Noteleaf fetches: 64 + 65 + - Title 66 + - Author names 67 + - Publication year 68 + - Edition information 69 + - Subjects/genres 70 + - Description (when available) 71 + - Cover ID 72 + 73 + ## API Endpoints 74 + 75 + ### Search Endpoint 76 + 77 + ``` 78 + GET https://openlibrary.org/search.json 79 + ``` 80 + 81 + Parameters: 82 + 83 + - `q`: Search query 84 + - `offset`: Pagination offset 85 + - `limit`: Results per page 86 + - `fields`: Requested fields 87 + 88 + ### Work Endpoint 89 + 90 + ``` 91 + GET https://openlibrary.org/works/{work_key}.json 92 + ``` 93 + 94 + Returns detailed work information including authors, description, subjects, and covers. 95 + 96 + ## Data Mapping 97 + 98 + Open Library data maps to Noteleaf book fields: 99 + 100 + | Open Library | Noteleaf Field | 101 + |--------------|----------------| 102 + | title | Title | 103 + | author_name | Author | 104 + | first_publish_year | Notes (included) | 105 + | edition_count | Notes (included) | 106 + | publisher | Notes (included) | 107 + | subject | Notes (included) | 108 + | cover_i | Notes (cover ID) | 109 + 110 + ## Example API Response 111 + 112 + Search result document: 113 + 114 + ```json 115 + { 116 + "key": "/works/OL45804W", 117 + "title": "Design Patterns", 118 + "author_name": ["Erich Gamma", "Richard Helm"], 119 + "first_publish_year": 1994, 120 + "edition_count": 23, 121 + "isbn": ["0201633612", "9780201633610"], 122 + "publisher": ["Addison-Wesley"], 123 + "subject": ["Software design", "Object-oriented programming"], 124 + "cover_i": 8644882 125 + } 126 + ``` 127 + 128 + ## Limitations 129 + 130 + ### No Direct Page Count 131 + 132 + Open Library doesn't consistently provide page counts in search results. Use the interactive editor to add page counts manually if needed. 133 + 134 + ### Author Keys vs Names 135 + 136 + Work endpoints return author keys (`/authors/OL123A`) rather than full names. Noteleaf displays available author names from search results. 137 + 138 + ### Cover Images 139 + 140 + Cover IDs are stored but not automatically downloaded. Future versions may support local cover image caching. 141 + 142 + ## Error Handling 143 + 144 + ### Network Issues 145 + 146 + ```sh 147 + noteleaf book search "query" 148 + # Error: failed to connect to Open Library 149 + ``` 150 + 151 + Check internet connection and Open Library status. 152 + 153 + ### Rate Limit Exceeded 154 + 155 + Noteleaf automatically waits when approaching rate limits. If you see delays, this is normal behavior. 156 + 157 + ### No Results 158 + 159 + ```sh 160 + noteleaf book search "very obscure title" 161 + # No results found 162 + ``` 163 + 164 + Try: 165 + 166 + - Different search terms 167 + - Author names instead of titles 168 + - ISBNs for specific editions 169 + 170 + ## API Service Architecture 171 + 172 + Implementation in `internal/services/services.go`: 173 + 174 + ```go 175 + type BookService struct { 176 + client *http.Client 177 + limiter *rate.Limiter 178 + baseURL string 179 + } 180 + ``` 181 + 182 + Features: 183 + 184 + - Automatic rate limiting 185 + - Context-aware requests 186 + - Proper error handling 187 + - Timeout management (30s) 188 + 189 + ## Custom User Agent 190 + 191 + Noteleaf identifies itself to Open Library: 192 + 193 + ``` 194 + User-Agent: Noteleaf/v{version} (contact: info@stormlightlabs.org) 195 + ``` 196 + 197 + This helps Open Library track API usage and contact developers if needed. 198 + 199 + ## Resources 200 + 201 + - [Open Library API Documentation](https://openlibrary.org/dev/docs/api/books) 202 + - [Open Library Search](https://openlibrary.org/dev/docs/api/search) 203 + - [Open Library Covers](https://openlibrary.org/dev/docs/api/covers) 204 + - [Rate Limiting Policy](https://openlibrary.org/developers/api)
+164
website/docs/integrations/overview.md
···
··· 1 + --- 2 + title: External Integrations 3 + sidebar_label: Overview 4 + sidebar_position: 1 5 + description: Overview of external service integrations. 6 + --- 7 + 8 + # External Integrations 9 + 10 + Noteleaf integrates with external services to enrich your productivity workflow and extend functionality beyond local storage. 11 + 12 + ## Available Integrations 13 + 14 + ### Open Library API 15 + 16 + Free book metadata service for building your reading list. 17 + 18 + **Features:** 19 + 20 + - Search books by title, author, ISBN 21 + - Fetch metadata (author, year, subjects) 22 + - Edition and publication information 23 + - No API key required 24 + 25 + **Use Cases:** 26 + 27 + - Adding books to reading list 28 + - Enriching book metadata 29 + - Discovering related works 30 + 31 + See [Open Library API](./openlibrary.md) for details. 32 + 33 + ### Leaflet.pub 34 + 35 + Decentralized publishing platform built on AT Protocol. 36 + 37 + **Features:** 38 + 39 + - Publish notes as structured documents 40 + - Pull existing documents into local notes 41 + - Update published content 42 + - Manage drafts and publications 43 + 44 + **Use Cases:** 45 + 46 + - Blog publishing from terminal 47 + - Long-form content management 48 + - Decentralized content ownership 49 + 50 + See [Leaflet.pub section](../leaflet/intro.md) for details. 51 + 52 + ### AT Protocol (Bluesky) 53 + 54 + Authentication and identity via AT Protocol network. 55 + 56 + **Features:** 57 + 58 + - Decentralized identity (DID) 59 + - Session management 60 + - Token refresh 61 + - Secure authentication 62 + 63 + **Use Cases:** 64 + 65 + - Leaflet.pub authentication 66 + - Portable identity across services 67 + - Content verification 68 + 69 + See [Authentication](../leaflet/authentication.md) for details. 70 + 71 + ## Integration Architecture 72 + 73 + ### Service Layer 74 + 75 + External integrations live in `internal/services/`: 76 + 77 + - `services.go` - Open Library API client 78 + - `atproto.go` - AT Protocol authentication 79 + - `http.go` - HTTP utilities and rate limiting 80 + 81 + ### Rate Limiting 82 + 83 + All external services use rate limiting to respect API quotas: 84 + 85 + - Open Library: 3 requests/second 86 + - AT Protocol: Per PDS configuration 87 + 88 + Rate limiters are built-in and automatic. 89 + 90 + ### Error Handling 91 + 92 + Services implement consistent error handling: 93 + 94 + - Network errors 95 + - Rate limit exceeded 96 + - Authentication failures 97 + - Invalid responses 98 + 99 + Errors propagate to user with actionable messages. 100 + 101 + ## Configuration 102 + 103 + Integration configuration in `.noteleaf.conf.toml`: 104 + 105 + ```toml 106 + # Open Library (no configuration needed) 107 + # book_api_key = "" # Reserved for future use 108 + 109 + # AT Protocol / Leaflet.pub 110 + atproto_handle = "username.bsky.social" 111 + atproto_did = "did:plc:..." 112 + atproto_pds_url = "https://bsky.social" 113 + atproto_access_jwt = "..." 114 + atproto_refresh_jwt = "..." 115 + ``` 116 + 117 + See [Configuration](../Configuration.md) for all options. 118 + 119 + ## Offline Support 120 + 121 + Noteleaf works fully offline for local data. Integrations are optional enhancements: 122 + 123 + - Books can be added manually without Open Library 124 + - Notes exist locally without Leaflet.pub 125 + - Tasks and media work without any external service 126 + 127 + External services enhance but don't require connectivity. 128 + 129 + ## Privacy and Data 130 + 131 + ### Data Sent 132 + 133 + **Open Library:** 134 + 135 + - Search queries 136 + - Work/edition IDs 137 + 138 + **AT Protocol:** 139 + 140 + - Handle/DID 141 + - Published note content 142 + - Authentication credentials 143 + 144 + ### Data Stored Locally 145 + 146 + - API responses (cached) 147 + - Session tokens 148 + - Publication metadata 149 + 150 + ### No Tracking 151 + 152 + Noteleaf does not: 153 + 154 + - Track usage 155 + - Send analytics 156 + - Share data with third parties 157 + - Require accounts (except for publishing) 158 + 159 + ## Resources 160 + 161 + - [Open Library API Documentation](https://openlibrary.org/developers/api) 162 + - [AT Protocol Docs](https://atproto.com) 163 + - [Leaflet.pub](https://leaflet.pub) 164 + - [Bluesky](https://bsky.app)
-26
website/docs/leaflet/future.md
··· 1 - --- 2 - title: Leaflet Future Features 3 - sidebar_label: Future 4 - description: Upcoming enhancements planned for the leaflet integration. 5 - sidebar_position: 10 6 - --- 7 - 8 - # Leaflet Future Features 9 - 10 - Planned improvements to leaflet.pub integration: 11 - 12 - **Multiple Publications**: Create and manage separate publications for different topics 13 - 14 - **Image Upload**: Automatically upload images to blob storage and embed in documents 15 - 16 - **Status Management**: Publish drafts and unpublish documents from CLI 17 - 18 - **Metadata Editing**: Update document titles, summaries, and tags 19 - 20 - **Backlink Support**: Parse and resolve cross-references between documents 21 - 22 - **Collaborative Editing**: Pull and merge changes from multiple devices 23 - 24 - **Offline Mode**: Queue posts and patches for later upload 25 - 26 - For the latest roadmap, check the [GitHub repository](https://github.com/stormlightlabs/noteleaf).
···
+1 -1
website/docs/notes/best-practices.md
··· 11 12 **Tag consistently**: Establish a tagging taxonomy early and stick to it. Review tags periodically with `noteleaf note tags`. 13 14 - **Link liberally**: Reference related notes, tasks, and articles. Future features will leverage these connections. 15 16 **Short, focused notes**: Better to have many small notes than few giant ones. Easier to link and reuse. 17
··· 11 12 **Tag consistently**: Establish a tagging taxonomy early and stick to it. Review tags periodically with `noteleaf note tags`. 13 14 + **Link liberally**: Reference related notes, tasks, and articles using markdown links and references. 15 16 **Short, focused notes**: Better to have many small notes than few giant ones. Easier to link and reuse. 17
+297
website/docs/workflows/import-export.md
···
··· 1 + --- 2 + title: Import and Export 3 + sidebar_label: Import & Export 4 + sidebar_position: 1 5 + description: Data portability, backups, and migration. 6 + --- 7 + 8 + # Import and Export 9 + 10 + Noteleaf stores data in open formats for portability: SQLite for structured data and Markdown for notes. 11 + 12 + ## Data Storage 13 + 14 + ### SQLite Database 15 + 16 + Location varies by platform: 17 + 18 + **macOS:** 19 + 20 + ``` 21 + ~/Library/Application Support/noteleaf/noteleaf.db 22 + ``` 23 + 24 + **Linux:** 25 + 26 + ``` 27 + ~/.local/share/noteleaf/noteleaf.db 28 + ``` 29 + 30 + **Windows:** 31 + 32 + ``` 33 + %LOCALAPPDATA%\noteleaf\noteleaf.db 34 + ``` 35 + 36 + ### Markdown Files 37 + 38 + Notes are stored as individual markdown files: 39 + 40 + **Default location:** 41 + 42 + ``` 43 + <data_dir>/notes/ 44 + ``` 45 + 46 + Configure via `notes_dir` in `.noteleaf.conf.toml`. 47 + 48 + ### Articles 49 + 50 + Saved articles are stored as markdown: 51 + 52 + **Default location:** 53 + 54 + ``` 55 + <data_dir>/articles/ 56 + ``` 57 + 58 + Configure via `articles_dir` in `.noteleaf.conf.toml`. 59 + 60 + ## JSON Export 61 + 62 + ### Task Export 63 + 64 + Export tasks to JSON format: 65 + 66 + ```sh 67 + noteleaf todo view 123 --json 68 + noteleaf todo list --static --json 69 + ``` 70 + 71 + Output includes all task attributes: 72 + 73 + - Description 74 + - Status, priority 75 + - Project, context, tags 76 + - Due dates, recurrence 77 + - Dependencies, parent tasks 78 + - Timestamps 79 + 80 + ### Export Format Configuration 81 + 82 + Set default export format: 83 + 84 + ```sh 85 + noteleaf config set export_format "json" 86 + ``` 87 + 88 + Options: 89 + 90 + - `json` (default) 91 + - `csv` (planned) 92 + - `markdown` (planned) 93 + 94 + ## Backup Strategy 95 + 96 + ### Full Backup 97 + 98 + Back up the entire data directory: 99 + 100 + ```sh 101 + # macOS 102 + cp -r ~/Library/Application\ Support/noteleaf ~/Backups/noteleaf-$(date +%Y%m%d) 103 + 104 + # Linux 105 + cp -r ~/.local/share/noteleaf ~/backups/noteleaf-$(date +%Y%m%d) 106 + ``` 107 + 108 + Includes: 109 + 110 + - SQLite database 111 + - Notes directory 112 + - Articles directory 113 + - Configuration file 114 + 115 + ### Database Only 116 + 117 + ```sh 118 + # macOS 119 + cp ~/Library/Application\ Support/noteleaf/noteleaf.db ~/Backups/ 120 + 121 + # Linux 122 + cp ~/.local/share/noteleaf/noteleaf.db ~/backups/ 123 + ``` 124 + 125 + ### Notes Only 126 + 127 + ```sh 128 + # Copy notes directory 129 + cp -r <data_dir>/notes ~/Backups/notes-$(date +%Y%m%d) 130 + ``` 131 + 132 + Notes are plain markdown files, easily versioned with Git: 133 + 134 + ```sh 135 + cd <data_dir>/notes 136 + git init 137 + git add . 138 + git commit -m "Initial notes backup" 139 + ``` 140 + 141 + ## Restore from Backup 142 + 143 + ### Full Restore 144 + 145 + ```sh 146 + # Stop noteleaf 147 + # Replace data directory 148 + cp -r ~/Backups/noteleaf-20240315 ~/Library/Application\ Support/noteleaf 149 + ``` 150 + 151 + ### Database Restore 152 + 153 + ```sh 154 + cp ~/Backups/noteleaf.db ~/Library/Application\ Support/noteleaf/ 155 + ``` 156 + 157 + ### Notes Restore 158 + 159 + ```sh 160 + cp -r ~/Backups/notes-20240315 <data_dir>/notes 161 + ``` 162 + 163 + ## Direct Database Access 164 + 165 + SQLite database is accessible with standard tools: 166 + 167 + ```sh 168 + # Open database 169 + sqlite3 ~/Library/Application\ Support/noteleaf/noteleaf.db 170 + 171 + # List tables 172 + .tables 173 + 174 + # Query tasks 175 + SELECT id, description, status FROM tasks WHERE status = 'pending'; 176 + 177 + # Export to CSV 178 + .mode csv 179 + .output tasks.csv 180 + SELECT * FROM tasks; 181 + .quit 182 + ``` 183 + 184 + ## Portable Installation 185 + 186 + Use environment variables for portable setup: 187 + 188 + ```sh 189 + export NOTELEAF_DATA_DIR=/path/to/usb/noteleaf-data 190 + export NOTELEAF_CONFIG=/path/to/usb/noteleaf.conf.toml 191 + noteleaf todo list 192 + ``` 193 + 194 + Useful for: 195 + 196 + - USB drive installations 197 + - Synced folders (Dropbox, iCloud) 198 + - Multiple workspaces 199 + - Testing environments 200 + 201 + ## Migration Strategies 202 + 203 + ### From TaskWarrior 204 + 205 + Manual migration via SQLite: 206 + 207 + 1. Export TaskWarrior data to JSON 208 + 2. Parse JSON and insert into noteleaf database 209 + 3. Map TaskWarrior attributes to Noteleaf schema 210 + 211 + Custom migration script required (future documentation). 212 + 213 + ### From todo.txt 214 + 215 + Convert todo.txt to Noteleaf tasks: 216 + 217 + 1. Parse todo.txt format 218 + 2. Map projects, contexts, priorities 219 + 3. Bulk insert via SQLite 220 + 221 + Custom migration script required (future documentation). 222 + 223 + ### From Other Note Apps 224 + 225 + Notes are markdown files: 226 + 227 + 1. Export notes from source app 228 + 2. Convert to plain markdown 229 + 3. Copy to `<data_dir>/notes/` 230 + 4. Noteleaf will index them on next scan 231 + 232 + ## Sync and Cloud Storage 233 + 234 + ### Cloud Sync 235 + 236 + Store data directory in synced folder: 237 + 238 + ```sh 239 + # Use Dropbox 240 + export NOTELEAF_DATA_DIR=~/Dropbox/noteleaf-data 241 + 242 + # Use iCloud 243 + export NOTELEAF_DATA_DIR=~/Library/Mobile\ Documents/com~apple~CloudDocs/noteleaf 244 + ``` 245 + 246 + **Warning:** SQLite databases don't handle concurrent writes well. Only run one Noteleaf instance at a time per database. 247 + 248 + ### Version Control 249 + 250 + Notes directory can be versioned: 251 + 252 + ```sh 253 + cd <data_dir>/notes 254 + git init 255 + git add . 256 + git commit -m "Initial commit" 257 + git remote add origin <repository-url> 258 + git push -u origin main 259 + ``` 260 + 261 + Automatic git commits planned for future release. 262 + 263 + ## Data Formats 264 + 265 + ### SQLite Schema 266 + 267 + View schema: 268 + 269 + ```sh 270 + sqlite3 noteleaf.db .schema 271 + ``` 272 + 273 + Tables include: 274 + 275 + - `tasks` - Task management 276 + - `notes` - Note metadata 277 + - `articles` - Article metadata 278 + - `books`, `movies`, `tv_shows` - Media tracking 279 + - `publications` - Leaflet.pub publications 280 + - Linking tables for tags, dependencies 281 + 282 + ### Markdown Format 283 + 284 + Notes use standard markdown with YAML frontmatter: 285 + 286 + ```markdown 287 + --- 288 + title: Note Title 289 + created: 2024-03-15T10:30:00Z 290 + modified: 2024-03-15T11:00:00Z 291 + tags: [tag1, tag2] 292 + --- 293 + 294 + # Note Content 295 + 296 + Regular markdown content... 297 + ```
+59 -119
website/docusaurus.config.ts
··· 1 - import { themes as prismThemes } from "prism-react-renderer"; 2 - import type { Config } from "@docusaurus/types"; 3 - import type * as Preset from "@docusaurus/preset-classic"; 4 import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; 5 6 const ghURL = "https://github.com/stormlightlabs/noteleaf"; 7 // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) 8 const pragmaticSidebar: SidebarsConfig[string] = [ 9 - { type: "doc", id: "quickstart" }, 10 - { 11 - type: "category", 12 - label: "Concepts", 13 - items: [{ type: "autogenerated", dirName: "concepts" }], 14 - }, 15 - { type: "doc", id: "configuration" }, 16 - { 17 - type: "category", 18 - label: "Tasks", 19 - items: [{ type: "autogenerated", dirName: "tasks" }], 20 - }, 21 - { 22 - type: "category", 23 - label: "Notes", 24 - items: [{ type: "autogenerated", dirName: "notes" }], 25 - }, 26 - { 27 - type: "category", 28 - label: "Leaflet.pub", 29 - items: [{ type: "autogenerated", dirName: "leaflet" }], 30 - }, 31 - { 32 - type: "category", 33 - label: "Media", 34 - items: [{ type: "autogenerated", dirName: "media" }], 35 - }, 36 - { 37 - type: "category", 38 - label: "Articles", 39 - items: [{ type: "autogenerated", dirName: "articles" }], 40 - }, 41 ]; 42 43 const config: Config = { 44 - title: "Noteleaf", 45 - tagline: "Notes, blog posts, and productivity from the terminal", 46 - favicon: "img/logo.svg", 47 - // Improve compatibility with the upcoming Docusaurus v4 48 - future: { v4: true }, 49 - url: "https://stormlightlabs.github.io/", 50 - baseUrl: "/noteleaf/", 51 - organizationName: "stormlightlabs", 52 - projectName: "noteleaf", 53 - onBrokenLinks: "throw", 54 - i18n: { defaultLocale: "en", locales: ["en"] }, 55 - customFields: { pragmaticSidebar }, 56 - presets: [ 57 - [ 58 - "classic", 59 - { 60 - docs: { sidebarPath: "./sidebars.ts" }, 61 - theme: { customCss: "./src/css/custom.css" }, 62 - } satisfies Preset.Options, 63 - ], 64 - ], 65 66 - themeConfig: { 67 - image: "img/social-card.png", 68 - colorMode: { respectPrefersColorScheme: true }, 69 - navbar: { 70 - title: "Noteleaf", 71 - logo: { alt: "Noteleaf Logo", src: "img/logo.svg" }, 72 - items: [ 73 - { 74 - type: "doc", 75 - docId: "quickstart", 76 - position: "left", 77 - label: "Quickstart", 78 - }, 79 - { 80 - type: "docSidebar", 81 - sidebarId: "manualSidebar", 82 - position: "left", 83 - label: "Manual", 84 - }, 85 - { href: ghURL, label: "GitHub", position: "right" }, 86 - ], 87 - }, 88 - footer: { 89 - style: "dark", 90 - links: [ 91 - { 92 - title: "Docs", 93 - items: [ 94 - { 95 - label: "Quickstart", 96 - to: "/docs/quickstart", 97 - }, 98 - { 99 - label: "Manual", 100 - to: "/docs/concepts/overview", 101 - }, 102 - ], 103 - }, 104 - { 105 - title: "Community", 106 - items: [ 107 - { 108 - label: "BlueSky", 109 - href: "https://bsky.app/desertthunder.dev", 110 - }, 111 - { 112 - label: "X", 113 - href: "https://x.com/_desertthunder", 114 - }, 115 - ], 116 - }, 117 - { 118 - title: "More", 119 - items: [{ label: "GitHub", href: ghURL }], 120 - }, 121 - ], 122 - copyright: `Copyright ยฉ ${new Date().getFullYear()} Stormlight Labs, LLC.`, 123 - }, 124 - prism: { 125 - theme: prismThemes.github, 126 - darkTheme: prismThemes.dracula, 127 - }, 128 - } satisfies Preset.ThemeConfig, 129 }; 130 131 export default config;
··· 1 import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; 2 + import type * as Preset from "@docusaurus/preset-classic"; 3 + import type { Config } from "@docusaurus/types"; 4 + import { themes as prismThemes } from "prism-react-renderer"; 5 6 const ghURL = "https://github.com/stormlightlabs/noteleaf"; 7 // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) 8 const pragmaticSidebar: SidebarsConfig[string] = [ 9 + { type: "doc", id: "quickstart" }, 10 + { type: "category", label: "Concepts", items: [{ type: "autogenerated", dirName: "concepts" }] }, 11 + { type: "doc", id: "configuration" }, 12 + { type: "category", label: "Tasks", items: [{ type: "autogenerated", dirName: "tasks" }] }, 13 + { type: "category", label: "Notes", items: [{ type: "autogenerated", dirName: "notes" }] }, 14 + { type: "category", label: "Leaflet.pub", items: [{ type: "autogenerated", dirName: "leaflet" }] }, 15 + { type: "category", label: "Media", items: [{ type: "autogenerated", dirName: "media" }] }, 16 + { type: "category", label: "Articles", items: [{ type: "autogenerated", dirName: "articles" }] }, 17 + { type: "category", label: "Integrations", items: [{ type: "autogenerated", dirName: "integrations" }] }, 18 + { type: "category", label: "Workflows", items: [{ type: "autogenerated", dirName: "workflows" }] }, 19 + { type: "category", label: "Development", items: [{ type: "autogenerated", dirName: "development" }] }, 20 ]; 21 22 const config: Config = { 23 + title: "Noteleaf", 24 + tagline: "Notes, blog posts, and productivity from the terminal", 25 + favicon: "img/logo.svg", 26 + // Improve compatibility with the upcoming Docusaurus v4 27 + future: { v4: true }, 28 + url: "https://stormlightlabs.github.io/", 29 + baseUrl: "/noteleaf/", 30 + organizationName: "stormlightlabs", 31 + projectName: "noteleaf", 32 + onBrokenLinks: "throw", 33 + i18n: { defaultLocale: "en", locales: ["en"] }, 34 + customFields: { pragmaticSidebar }, 35 + presets: [[ 36 + "classic", 37 + { docs: { sidebarPath: "./sidebars.ts" }, theme: { customCss: "./src/css/custom.css" } } satisfies Preset.Options, 38 + ]], 39 40 + themeConfig: { 41 + image: "img/social-card.png", 42 + colorMode: { respectPrefersColorScheme: true }, 43 + navbar: { 44 + title: "Noteleaf", 45 + logo: { alt: "Noteleaf Logo", src: "img/logo.svg" }, 46 + items: [{ type: "doc", docId: "quickstart", position: "left", label: "Quickstart" }, { 47 + type: "docSidebar", 48 + sidebarId: "manualSidebar", 49 + position: "left", 50 + label: "Manual", 51 + }, { href: ghURL, label: "GitHub", position: "right" }], 52 + }, 53 + footer: { 54 + style: "dark", 55 + links: [{ 56 + title: "Docs", 57 + items: [{ label: "Quickstart", to: "/docs/quickstart" }, { label: "Manual", to: "/docs/concepts/overview" }], 58 + }, { 59 + title: "Community", 60 + items: [{ label: "BlueSky", href: "https://bsky.app/desertthunder.dev" }, { 61 + label: "X", 62 + href: "https://x.com/_desertthunder", 63 + }], 64 + }, { title: "More", items: [{ label: "GitHub", href: ghURL }] }], 65 + copyright: `Copyright ยฉ ${new Date().getFullYear()} Stormlight Labs, LLC.`, 66 + }, 67 + prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula }, 68 + } satisfies Preset.ThemeConfig, 69 }; 70 71 export default config;
+6
website/dprint.json
···
··· 1 + { 2 + "typescript": { "preferSingleLine": true, "jsx.bracketPosition": "sameLine" }, 3 + "json": { "preferSingleLine": true, "lineWidth": 121, "indentWidth": 2 }, 4 + "excludes": ["**/node_modules"], 5 + "plugins": ["https://plugins.dprint.dev/typescript-0.95.8.wasm", "https://plugins.dprint.dev/json-0.20.0.wasm"] 6 + }
+1
website/package.json
··· 31 "@docusaurus/module-type-aliases": "3.9.1", 32 "@docusaurus/tsconfig": "3.9.1", 33 "@docusaurus/types": "3.9.1", 34 "typescript": "~5.6.2" 35 }, 36 "browserslist": {
··· 31 "@docusaurus/module-type-aliases": "3.9.1", 32 "@docusaurus/tsconfig": "3.9.1", 33 "@docusaurus/types": "3.9.1", 34 + "dprint": "^0.50.2", 35 "typescript": "~5.6.2" 36 }, 37 "browserslist": {
+91 -8
website/pnpm-lock.yaml
··· 14 '@docusaurus/preset-classic': 15 specifier: 3.9.1 16 version: 3.9.1(@algolia/client-search@5.39.0)(@mdx-js/react@3.1.1(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.6.3) 17 - '@fontsource/lato': 18 - specifier: ^5.1.0 19 - version: 5.2.7 20 '@mdx-js/react': 21 specifier: ^3.0.0 22 version: 3.1.1(@types/react@19.2.0)(react@19.2.0) ··· 51 '@docusaurus/types': 52 specifier: 3.9.1 53 version: 3.9.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) 54 typescript: 55 specifier: ~5.6.2 56 version: 5.6.3 ··· 1200 resolution: {integrity: sha512-YAL4yhhWLl9DXuf5MVig260a6INz4MehrBGFU/CZu8yXmRiYEuQvRFWh9ZsjfAOyaG7za1MNmBVZ4VVAi/CiJA==} 1201 engines: {node: '>=20.0'} 1202 1203 '@emnapi/runtime@1.7.0': 1204 resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} 1205 - 1206 - '@fontsource/lato@5.2.7': 1207 - resolution: {integrity: sha512-k5mum1ADbDW5cTw1Ett1eQVWeoZ6gq0ct6SFBibEhB4LRxhniChJZTBgd6ph5yBxLkN1fcnsnmicBNA4S/3nbw==} 1208 1209 '@hapi/hoek@9.3.0': 1210 resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} ··· 2487 dot-prop@6.0.1: 2488 resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} 2489 engines: {node: '>=10'} 2490 2491 dunder-proto@1.0.1: 2492 resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} ··· 7116 - uglify-js 7117 - webpack-cli 7118 7119 '@emnapi/runtime@1.7.0': 7120 dependencies: 7121 tslib: 2.8.1 7122 optional: true 7123 - 7124 - '@fontsource/lato@5.2.7': {} 7125 7126 '@hapi/hoek@9.3.0': {} 7127 ··· 8513 dot-prop@6.0.1: 8514 dependencies: 8515 is-obj: 2.0.0 8516 8517 dunder-proto@1.0.1: 8518 dependencies:
··· 14 '@docusaurus/preset-classic': 15 specifier: 3.9.1 16 version: 3.9.1(@algolia/client-search@5.39.0)(@mdx-js/react@3.1.1(@types/react@19.2.0)(react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(search-insights@2.17.3)(typescript@5.6.3) 17 '@mdx-js/react': 18 specifier: ^3.0.0 19 version: 3.1.1(@types/react@19.2.0)(react@19.2.0) ··· 48 '@docusaurus/types': 49 specifier: 3.9.1 50 version: 3.9.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) 51 + dprint: 52 + specifier: ^0.50.2 53 + version: 0.50.2 54 typescript: 55 specifier: ~5.6.2 56 version: 5.6.3 ··· 1200 resolution: {integrity: sha512-YAL4yhhWLl9DXuf5MVig260a6INz4MehrBGFU/CZu8yXmRiYEuQvRFWh9ZsjfAOyaG7za1MNmBVZ4VVAi/CiJA==} 1201 engines: {node: '>=20.0'} 1202 1203 + '@dprint/darwin-arm64@0.50.2': 1204 + resolution: {integrity: sha512-4d08INZlTxbPW9LK9W8+93viN543/qA2Kxn4azVnPW/xCb2Im03UqJBz8mMm3nJZdtNnK3uTVG3ib1VW+XJisw==} 1205 + cpu: [arm64] 1206 + os: [darwin] 1207 + 1208 + '@dprint/darwin-x64@0.50.2': 1209 + resolution: {integrity: sha512-ZXWPBwdLojhdBATq+bKwJvB7D8bIzrD6eR/Xuq9UYE7evQazUiR069d9NPF0iVuzTo6wNf9ub9SXI7qDl11EGA==} 1210 + cpu: [x64] 1211 + os: [darwin] 1212 + 1213 + '@dprint/linux-arm64-glibc@0.50.2': 1214 + resolution: {integrity: sha512-marxQzRw8atXAnaawwZHeeUaaAVewrGTlFKKcDASGyjPBhc23J5fHPUPremm8xCbgYZyTlokzrV8/1rDRWhJcw==} 1215 + cpu: [arm64] 1216 + os: [linux] 1217 + 1218 + '@dprint/linux-arm64-musl@0.50.2': 1219 + resolution: {integrity: sha512-oGDq44ydzo0ZkJk6RHcUzUN5sOMT5HC6WA8kHXI6tkAsLUkaLO2DzZFfW4aAYZUn+hYNpQfQD8iGew0sjkyLyg==} 1220 + cpu: [arm64] 1221 + os: [linux] 1222 + 1223 + '@dprint/linux-riscv64-glibc@0.50.2': 1224 + resolution: {integrity: sha512-QMmZoZYWsXezDcC03fBOwPfxhTpPEyHqutcgJ0oauN9QcSXGji9NSZITMmtLz2Ki3T1MIvdaLd1goGzNSvNqTQ==} 1225 + cpu: [riscv64] 1226 + os: [linux] 1227 + 1228 + '@dprint/linux-x64-glibc@0.50.2': 1229 + resolution: {integrity: sha512-KMeHEzb4teQJChTgq8HuQzc+reRNDnarOTGTQovAZ9WNjOtKLViftsKWW5HsnRHtP5nUIPE9rF1QLjJ/gUsqvw==} 1230 + cpu: [x64] 1231 + os: [linux] 1232 + 1233 + '@dprint/linux-x64-musl@0.50.2': 1234 + resolution: {integrity: sha512-qM37T7H69g5coBTfE7SsA+KZZaRBky6gaUhPgAYxW+fOsoVtZSVkXtfTtQauHTpqqOEtbxfCtum70Hz1fr1teg==} 1235 + cpu: [x64] 1236 + os: [linux] 1237 + 1238 + '@dprint/win32-arm64@0.50.2': 1239 + resolution: {integrity: sha512-kuGVHGoxLwssVDsodefUIYQRoO2fQncurH/xKgXiZwMPOSzFcgUzYJQiyqmJEp+PENhO9VT1hXUHZtlyCAWBUQ==} 1240 + cpu: [arm64] 1241 + os: [win32] 1242 + 1243 + '@dprint/win32-x64@0.50.2': 1244 + resolution: {integrity: sha512-N3l9k31c3IMfVXqL0L6ygIhJFvCIrfQ+Z5Jph6RnCcBO6oDYWeYhAv/qBk1vLsF2y/e79TKsR1tvaEwnrQ03XA==} 1245 + cpu: [x64] 1246 + os: [win32] 1247 + 1248 '@emnapi/runtime@1.7.0': 1249 resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} 1250 1251 '@hapi/hoek@9.3.0': 1252 resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} ··· 2529 dot-prop@6.0.1: 2530 resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} 2531 engines: {node: '>=10'} 2532 + 2533 + dprint@0.50.2: 2534 + resolution: {integrity: sha512-+0Fzg+17jsMMUouK00/Fara5YtGOuE76EAJINHB8VpkXHd0n00rMXtw/03qorOgz23eo8Y0UpYvNZBJJo3aNtw==} 2535 + hasBin: true 2536 2537 dunder-proto@1.0.1: 2538 resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} ··· 7162 - uglify-js 7163 - webpack-cli 7164 7165 + '@dprint/darwin-arm64@0.50.2': 7166 + optional: true 7167 + 7168 + '@dprint/darwin-x64@0.50.2': 7169 + optional: true 7170 + 7171 + '@dprint/linux-arm64-glibc@0.50.2': 7172 + optional: true 7173 + 7174 + '@dprint/linux-arm64-musl@0.50.2': 7175 + optional: true 7176 + 7177 + '@dprint/linux-riscv64-glibc@0.50.2': 7178 + optional: true 7179 + 7180 + '@dprint/linux-x64-glibc@0.50.2': 7181 + optional: true 7182 + 7183 + '@dprint/linux-x64-musl@0.50.2': 7184 + optional: true 7185 + 7186 + '@dprint/win32-arm64@0.50.2': 7187 + optional: true 7188 + 7189 + '@dprint/win32-x64@0.50.2': 7190 + optional: true 7191 + 7192 '@emnapi/runtime@1.7.0': 7193 dependencies: 7194 tslib: 2.8.1 7195 optional: true 7196 7197 '@hapi/hoek@9.3.0': {} 7198 ··· 8584 dot-prop@6.0.1: 8585 dependencies: 8586 is-obj: 2.0.0 8587 + 8588 + dprint@0.50.2: 8589 + optionalDependencies: 8590 + '@dprint/darwin-arm64': 0.50.2 8591 + '@dprint/darwin-x64': 0.50.2 8592 + '@dprint/linux-arm64-glibc': 0.50.2 8593 + '@dprint/linux-arm64-musl': 0.50.2 8594 + '@dprint/linux-riscv64-glibc': 0.50.2 8595 + '@dprint/linux-x64-glibc': 0.50.2 8596 + '@dprint/linux-x64-musl': 0.50.2 8597 + '@dprint/win32-arm64': 0.50.2 8598 + '@dprint/win32-x64': 0.50.2 8599 8600 dunder-proto@1.0.1: 8601 dependencies:
+1
website/pnpm-workspace.yaml
··· 1 onlyBuiltDependencies: 2 - sharp
··· 1 onlyBuiltDependencies: 2 + - dprint 3 - sharp
+1 -2
website/sidebars.ts
··· 3 4 // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) 5 const sidebars: SidebarsConfig = { 6 - manualSidebar: 7 - (config.customFields?.pragmaticSidebar as SidebarsConfig[string]) ?? [], 8 }; 9 10 export default sidebars;
··· 3 4 // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) 5 const sidebars: SidebarsConfig = { 6 + manualSidebar: (config.customFields?.pragmaticSidebar as SidebarsConfig[string]) ?? [], 7 }; 8 9 export default sidebars;
+56 -82
website/src/components/HomepageFeatures/index.tsx
··· 1 - import type {ReactNode} from 'react'; 2 - import clsx from 'clsx'; 3 - import Heading from '@theme/Heading'; 4 - import styles from './styles.module.css'; 5 6 - type FeatureItem = { 7 - title: string; 8 - description: ReactNode; 9 - theme: keyof typeof themeClassMap; 10 - }; 11 12 const themeClassMap = { 13 primary: styles.featureCardPrimary, ··· 19 danger: styles.featureCardDanger, 20 }; 21 22 - const FeatureList: FeatureItem[] = [ 23 - { 24 - title: 'Terminal-First Interface', 25 - description: ( 26 - <> 27 - Built for the command line with a beautiful, keyboard-driven interface 28 - powered by Bubble Tea and Fang. 29 - </> 30 - ), 31 - theme: 'primary', 32 - }, 33 - { 34 - title: 'Task Management', 35 - description: ( 36 - <> 37 - Organize your tasks with projects, priorities, tags, contexts, due 38 - dates, and recurrenceโ€”all from a single CLI. 39 - </> 40 - ), 41 - theme: 'danger', 42 - }, 43 - { 44 - title: 'Leaflet.pub Publishing', 45 - description: ( 46 - <> 47 - Sync Markdown notes with leaflet.pub, push updates over AT Protocol, and 48 - manage drafts without leaving the terminal. 49 - </> 50 - ), 51 - theme: 'accent', 52 - }, 53 - { 54 - title: 'Articles & Readability', 55 - description: ( 56 - <> 57 - Capture the clean content of any article, store Markdown + HTML copies, 58 - and enjoy a terminal reader inspired by Readability. 59 - </> 60 - ), 61 - theme: 'warning', 62 - }, 63 - { 64 - title: 'Knowledge Base', 65 - description: ( 66 - <> 67 - Keep notes, track books, movies, and TV shows. Link everything together 68 - with tags, IDs, and shared metadata. 69 - </> 70 - ), 71 - theme: 'info', 72 - }, 73 - { 74 - title: 'Open Source & MIT Licensed', 75 - description: ( 76 - <> 77 - Built in the open on GitHub under the MIT license. Fork it, extend it, 78 - and make Noteleaf part of your own workflows. 79 - </> 80 - ), 81 - theme: 'plum', 82 - }, 83 - ]; 84 85 - function Feature({title, description, theme}: FeatureItem) { 86 const cardClass = themeClassMap[theme] ?? themeClassMap.primary; 87 return ( 88 - <div className={clsx('col col--4')}> 89 - <div className={clsx('text--left padding-horiz--md', styles.featureCard, cardClass)}> 90 - <Heading as="h3" className={styles.featureTitle}> 91 - {title} 92 - </Heading> 93 <p className={styles.featureCopy}>{description}</p> 94 </div> 95 </div> ··· 100 return ( 101 <section className={styles.features}> 102 <div className="container"> 103 - <div className="row"> 104 - {FeatureList.map((props, idx) => ( 105 - <Feature key={idx} {...props} /> 106 - ))} 107 - </div> 108 </div> 109 </section> 110 );
··· 1 + import Heading from "@theme/Heading"; 2 + import clsx from "clsx"; 3 + import type { ReactNode } from "react"; 4 + import styles from "./styles.module.css"; 5 6 + type FeatureItem = { title: string; description: ReactNode; theme: keyof typeof themeClassMap }; 7 8 const themeClassMap = { 9 primary: styles.featureCardPrimary, ··· 15 danger: styles.featureCardDanger, 16 }; 17 18 + const FeatureList: FeatureItem[] = [{ 19 + title: "Terminal-First Interface", 20 + description: ( 21 + <>Built for the command line with a beautiful, keyboard-driven interface powered by Bubble Tea and Fang.</> 22 + ), 23 + theme: "primary", 24 + }, { 25 + title: "Task Management", 26 + description: ( 27 + <>Organize your tasks with projects, priorities, tags, contexts, due dates, and recurrenceโ€”all from a single CLI.</> 28 + ), 29 + theme: "danger", 30 + }, { 31 + title: "Leaflet.pub Publishing", 32 + description: ( 33 + <> 34 + Sync Markdown notes with leaflet.pub, push updates over AT Protocol, and manage drafts without leaving the 35 + terminal. 36 + </> 37 + ), 38 + theme: "accent", 39 + }, { 40 + title: "Articles & Readability", 41 + description: ( 42 + <> 43 + Capture the clean content of any article, store Markdown + HTML copies, and enjoy a terminal reader inspired by 44 + Readability. 45 + </> 46 + ), 47 + theme: "warning", 48 + }, { 49 + title: "Knowledge Base", 50 + description: ( 51 + <>Keep notes, track books, movies, and TV shows. Link everything together with tags, IDs, and shared metadata.</> 52 + ), 53 + theme: "info", 54 + }, { 55 + title: "Open Source & MIT Licensed", 56 + description: ( 57 + <> 58 + Built in the open on GitHub under the MIT license. Fork it, extend it, and make Noteleaf part of your own 59 + workflows. 60 + </> 61 + ), 62 + theme: "plum", 63 + }]; 64 65 + function Feature({ title, description, theme }: FeatureItem) { 66 const cardClass = themeClassMap[theme] ?? themeClassMap.primary; 67 return ( 68 + <div className={clsx("col col--4")}> 69 + <div className={clsx("text--left padding-horiz--md", styles.featureCard, cardClass)}> 70 + <Heading as="h3" className={styles.featureTitle}>{title}</Heading> 71 <p className={styles.featureCopy}>{description}</p> 72 </div> 73 </div> ··· 78 return ( 79 <section className={styles.features}> 80 <div className="container"> 81 + <div className="row">{FeatureList.map((props, idx) => <Feature key={idx} {...props} />)}</div> 82 </div> 83 </section> 84 );
+26 -39
website/src/pages/index.tsx
··· 1 - import type { ReactNode } from "react"; 2 - import clsx from "clsx"; 3 import Link from "@docusaurus/Link"; 4 import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 5 - import Layout from "@theme/Layout"; 6 import HomepageFeatures from "@site/src/components/HomepageFeatures"; 7 import Heading from "@theme/Heading"; 8 9 import styles from "./index.module.css"; 10 11 function HomepageHeader() { 12 - const { siteConfig } = useDocusaurusContext(); 13 - return ( 14 - <header className={clsx("hero", styles.heroBanner)}> 15 - <div className="container"> 16 - <Heading 17 - as="h1" 18 - className={clsx("hero__title", styles.heroTitle)} 19 - > 20 - {siteConfig.title} 21 - </Heading> 22 - <p className={clsx("hero__subtitle", styles.heroSubtitle)}> 23 - {siteConfig.tagline} 24 - </p> 25 - <div className={styles.buttons}> 26 - <Link 27 - className="button button--info button--lg" 28 - to="/docs/quickstart" 29 - > 30 - Get Started 31 - </Link> 32 - </div> 33 - </div> 34 - </header> 35 - ); 36 } 37 38 export default function Home(): ReactNode { 39 - const { siteConfig } = useDocusaurusContext(); 40 - return ( 41 - <Layout 42 - title="Terminal-based Personal Information Manager" 43 - description="Manage tasks, notes, articles, and media from your terminal with Noteleaf" 44 - > 45 - <HomepageHeader /> 46 - <main> 47 - <HomepageFeatures /> 48 - </main> 49 - </Layout> 50 - ); 51 }
··· 1 import Link from "@docusaurus/Link"; 2 import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 3 import HomepageFeatures from "@site/src/components/HomepageFeatures"; 4 import Heading from "@theme/Heading"; 5 + import Layout from "@theme/Layout"; 6 + import clsx from "clsx"; 7 + import type { ReactNode } from "react"; 8 9 import styles from "./index.module.css"; 10 11 function HomepageHeader() { 12 + const { siteConfig } = useDocusaurusContext(); 13 + return ( 14 + <header className={clsx("hero", styles.heroBanner)}> 15 + <div className="container"> 16 + <Heading as="h1" className={clsx("hero__title", styles.heroTitle)}>{siteConfig.title}</Heading> 17 + <p className={clsx("hero__subtitle", styles.heroSubtitle)}>{siteConfig.tagline}</p> 18 + <div className={styles.buttons}> 19 + <Link className="button button--info button--lg" to="/docs/quickstart">Get Started</Link> 20 + </div> 21 + </div> 22 + </header> 23 + ); 24 } 25 26 export default function Home(): ReactNode { 27 + const { siteConfig } = useDocusaurusContext(); 28 + return ( 29 + <Layout 30 + title="Terminal-based Personal Information Manager" 31 + description="Manage tasks, notes, articles, and media from your terminal with Noteleaf"> 32 + <HomepageHeader /> 33 + <main> 34 + <HomepageFeatures /> 35 + </main> 36 + </Layout> 37 + ); 38 }