quartz build
This commit is contained in:
60
README.md
Normal file
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# org-to-quartz
|
||||||
|
|
||||||
|
Convert org-mode notes to a Quartz static site.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Build pages from your notes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#example-pages
|
||||||
|
# Output in ./result/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serve locally (development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run .#serve ./path/to/your/notes
|
||||||
|
# Opens http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Convert org to markdown only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run . -- ./input-notes ./output-dir
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use in your own flake
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs.org-notes-quartz.url = "github:your-user/org-notes-quartz";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, org-notes-quartz, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
buildQuartzPages = org-notes-quartz.lib.${system}.buildQuartzPages;
|
||||||
|
in {
|
||||||
|
packages.${system}.site = buildQuartzPages {
|
||||||
|
contentDir = ./notes;
|
||||||
|
# quartzConfig = ./quartz.config.ts; # optional
|
||||||
|
# quartzLayout = ./quartz.layout.ts; # optional
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available packages
|
||||||
|
|
||||||
|
| Package | Description |
|
||||||
|
|-----------------------------|---------------------------------------------|
|
||||||
|
| `default` / `org-to-quartz` | CLI to convert org files to Quartz markdown |
|
||||||
|
| `quartz` | Quartz v4.5.2 static site generator |
|
||||||
|
| `example-pages` | Example build from `test-notes/` |
|
||||||
|
|
||||||
|
## Available apps
|
||||||
|
|
||||||
|
| App | Description |
|
||||||
|
|-----------|-------------------------------|
|
||||||
|
| `default` | Run org-to-quartz converter |
|
||||||
|
| `serve` | Serve notes with live preview |
|
||||||
38
example-notes/concepts/atomic-notes.org
Normal file
38
example-notes/concepts/atomic-notes.org
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#+title: Atomic Notes
|
||||||
|
#+date: 2024-01-11
|
||||||
|
#+filetags: :concept:writing:
|
||||||
|
|
||||||
|
An atomic note contains exactly one idea, fully expressed.
|
||||||
|
|
||||||
|
* Characteristics
|
||||||
|
|
||||||
|
- Self-contained
|
||||||
|
- Single focus
|
||||||
|
- Linkable
|
||||||
|
- Reusable
|
||||||
|
|
||||||
|
* Benefits
|
||||||
|
|
||||||
|
When notes are atomic:
|
||||||
|
|
||||||
|
- Easy to link from multiple contexts
|
||||||
|
- Simple to reorganize
|
||||||
|
- Clear to understand
|
||||||
|
- Quick to write
|
||||||
|
|
||||||
|
* Examples
|
||||||
|
|
||||||
|
Good atomic note titles:
|
||||||
|
- "Spaced repetition improves long-term retention"
|
||||||
|
- "Links create unexpected connections"
|
||||||
|
- "Writing clarifies thinking"
|
||||||
|
|
||||||
|
Bad titles:
|
||||||
|
- "Notes about learning"
|
||||||
|
- "Stuff I read"
|
||||||
|
|
||||||
|
* Connection to Other Concepts
|
||||||
|
|
||||||
|
Atomic notes are the foundation of [[file:zettelkasten.org][Zettelkasten]] and enable effective [[file:linking.org][Linking]].
|
||||||
|
|
||||||
|
This principle comes from software engineering - see cite:martin2008 on the Single Responsibility Principle.
|
||||||
26
example-notes/concepts/knowledge-management.org
Normal file
26
example-notes/concepts/knowledge-management.org
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#+title: Knowledge Management
|
||||||
|
#+date: 2024-01-10
|
||||||
|
#+filetags: :concept:learning:
|
||||||
|
|
||||||
|
Knowledge management is the process of creating, sharing, using, and managing information.
|
||||||
|
|
||||||
|
* Core Principles
|
||||||
|
|
||||||
|
1. *Capture* - Record ideas as they come
|
||||||
|
2. *Organize* - Structure for retrieval
|
||||||
|
3. *Connect* - Link related concepts
|
||||||
|
4. *Review* - Revisit and refine
|
||||||
|
|
||||||
|
* Related Concepts
|
||||||
|
|
||||||
|
- [[file:zettelkasten.org][Zettelkasten]] - A specific method for knowledge management
|
||||||
|
- [[file:atomic-notes.org][Atomic Notes]] - The building blocks
|
||||||
|
- [[file:linking.org][Linking]] - How connections create value
|
||||||
|
|
||||||
|
* Tools
|
||||||
|
|
||||||
|
See [[file:../projects/website.org][Website Project]] for how to publish your knowledge base.
|
||||||
|
|
||||||
|
* References
|
||||||
|
|
||||||
|
The field draws from library science cite:weinberger2007 and cognitive psychology cite:ahrens2017.
|
||||||
43
example-notes/concepts/linking.org
Normal file
43
example-notes/concepts/linking.org
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#+title: Linking
|
||||||
|
#+date: 2024-01-13
|
||||||
|
#+filetags: :concept:structure:
|
||||||
|
|
||||||
|
Links between notes create a network of knowledge.
|
||||||
|
|
||||||
|
* Types of Links
|
||||||
|
|
||||||
|
** Hierarchical
|
||||||
|
Parent-child relationships (folders, outlines)
|
||||||
|
|
||||||
|
** Associative
|
||||||
|
Related concepts ([[file:zettelkasten.org][Zettelkasten]] style)
|
||||||
|
|
||||||
|
** Sequential
|
||||||
|
Reading order, workflows
|
||||||
|
|
||||||
|
* Why Links Matter
|
||||||
|
|
||||||
|
#+begin_src text
|
||||||
|
[Note A] --- related to --- [Note B]
|
||||||
|
| |
|
||||||
|
+--- supports --- [Note C] -+
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Links reveal:
|
||||||
|
- Hidden connections
|
||||||
|
- Knowledge gaps
|
||||||
|
- Entry points
|
||||||
|
|
||||||
|
* Backlinks
|
||||||
|
|
||||||
|
When [[file:atomic-notes.org][Atomic Notes]] links here, this page knows about it. Backlinks show:
|
||||||
|
|
||||||
|
- Who references this concept
|
||||||
|
- How the idea is used
|
||||||
|
- Related contexts
|
||||||
|
|
||||||
|
* Tools Supporting Links
|
||||||
|
|
||||||
|
See [[file:../projects/website.org][Website Project]] for publishing linked notes.
|
||||||
|
|
||||||
|
The web itself is built on links cite:bernerslee1999.
|
||||||
41
example-notes/concepts/spaced-repetition.org
Normal file
41
example-notes/concepts/spaced-repetition.org
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#+title: Spaced Repetition
|
||||||
|
#+date: 2024-01-14
|
||||||
|
#+filetags: :concept:learning:memory:
|
||||||
|
|
||||||
|
Spaced repetition is a learning technique that involves reviewing material at increasing intervals.
|
||||||
|
|
||||||
|
* The Forgetting Curve
|
||||||
|
|
||||||
|
Without review, we forget:
|
||||||
|
- 50% within an hour
|
||||||
|
- 70% within 24 hours
|
||||||
|
- 90% within a week
|
||||||
|
|
||||||
|
* How Spacing Helps
|
||||||
|
|
||||||
|
Review schedule example:
|
||||||
|
|
||||||
|
| Review | Interval |
|
||||||
|
|--------|----------|
|
||||||
|
| 1st | 1 day |
|
||||||
|
| 2nd | 3 days |
|
||||||
|
| 3rd | 1 week |
|
||||||
|
| 4th | 2 weeks |
|
||||||
|
| 5th | 1 month |
|
||||||
|
|
||||||
|
* Software
|
||||||
|
|
||||||
|
- Anki
|
||||||
|
- Org-drill (Emacs)
|
||||||
|
- RemNote
|
||||||
|
|
||||||
|
* Connection to Note-Taking
|
||||||
|
|
||||||
|
[[file:zettelkasten.org][Zettelkasten]] provides natural spaced repetition through:
|
||||||
|
- Random encounters while linking
|
||||||
|
- Review during searches
|
||||||
|
- Connections triggering recall
|
||||||
|
|
||||||
|
* Research
|
||||||
|
|
||||||
|
Based on memory research cite:ebbinghaus1885 and modern applications cite:pimsleur1967.
|
||||||
38
example-notes/concepts/zettelkasten.org
Normal file
38
example-notes/concepts/zettelkasten.org
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#+title: Zettelkasten
|
||||||
|
#+date: 2024-01-12
|
||||||
|
#+filetags: :concept:methodology:productivity:
|
||||||
|
|
||||||
|
The Zettelkasten (German for "slip box") is a note-taking method developed by sociologist Niklas Luhmann.
|
||||||
|
|
||||||
|
* Key Features
|
||||||
|
|
||||||
|
- Each note contains one idea ([[file:atomic-notes.org][Atomic Notes]])
|
||||||
|
- Notes link to each other extensively ([[file:linking.org][Linking]])
|
||||||
|
- No strict hierarchy - emergence through connections
|
||||||
|
|
||||||
|
* How It Works
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
"One cannot think without writing." - Niklas Luhmann
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
1. Write fleeting notes
|
||||||
|
2. Convert to permanent notes
|
||||||
|
3. Add to the slip box with links
|
||||||
|
4. Review connections
|
||||||
|
|
||||||
|
* Implementation
|
||||||
|
|
||||||
|
| Analog | Digital |
|
||||||
|
|--------|---------|
|
||||||
|
| Index cards | Org-roam |
|
||||||
|
| Physical box | Obsidian |
|
||||||
|
| Manual links | Wiki links |
|
||||||
|
|
||||||
|
* Why It Works
|
||||||
|
|
||||||
|
The method leverages [[file:spaced-repetition.org][Spaced Repetition]] through organic review and builds on [[file:knowledge-management.org][Knowledge Management]] principles.
|
||||||
|
|
||||||
|
* Further Reading
|
||||||
|
|
||||||
|
See cite:ahrens2017 for the definitive guide.
|
||||||
25
example-notes/daily/2024-01-20.org
Normal file
25
example-notes/daily/2024-01-20.org
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#+title: 2024-01-20
|
||||||
|
#+date: 2024-01-20
|
||||||
|
#+filetags: :daily:journal:
|
||||||
|
|
||||||
|
* Tasks
|
||||||
|
|
||||||
|
- [X] Review [[file:../concepts/zettelkasten.org][Zettelkasten]] notes
|
||||||
|
- [X] Update [[file:../projects/website.org][Website Project]] status
|
||||||
|
- [ ] Read more of cite:ahrens2017
|
||||||
|
|
||||||
|
* Notes
|
||||||
|
|
||||||
|
Realized that [[file:../concepts/atomic-notes.org][Atomic Notes]] principle applies beyond note-taking:
|
||||||
|
- Commit messages
|
||||||
|
- Documentation sections
|
||||||
|
- Email paragraphs
|
||||||
|
|
||||||
|
* Ideas
|
||||||
|
|
||||||
|
Could [[file:../concepts/spaced-repetition.org][Spaced Repetition]] work for code review? Flag old code for periodic review.
|
||||||
|
|
||||||
|
* References
|
||||||
|
|
||||||
|
- Discussed [[file:../concepts/knowledge-management.org][Knowledge Management]] with colleague
|
||||||
|
- Found new source on [[file:../concepts/linking.org][Linking]] theory
|
||||||
17
example-notes/index.org
Normal file
17
example-notes/index.org
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#+title: Welcome
|
||||||
|
#+date: 2024-01-15
|
||||||
|
#+filetags: :home:
|
||||||
|
|
||||||
|
This is a digital garden built from org-mode notes.
|
||||||
|
|
||||||
|
* Getting Started
|
||||||
|
|
||||||
|
Explore the different sections:
|
||||||
|
|
||||||
|
- [[file:concepts/knowledge-management.org][Knowledge Management]] - Core concepts
|
||||||
|
- [[file:projects/website.org][Website Project]] - An example project
|
||||||
|
- [[file:references/index.org][References]] - Books and papers
|
||||||
|
|
||||||
|
* Recent Updates
|
||||||
|
|
||||||
|
Check out [[file:concepts/zettelkasten.org][Zettelkasten]] for note-taking methodology.
|
||||||
34
example-notes/projects/learning-system.org
Normal file
34
example-notes/projects/learning-system.org
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#+title: Learning System
|
||||||
|
#+date: 2024-01-18
|
||||||
|
#+filetags: :project:planning:
|
||||||
|
|
||||||
|
A personal system for continuous learning.
|
||||||
|
|
||||||
|
* Components
|
||||||
|
|
||||||
|
1. *Capture* - Quick notes, highlights, ideas
|
||||||
|
2. *Process* - Convert to [[file:../concepts/atomic-notes.org][Atomic Notes]]
|
||||||
|
3. *Connect* - Add [[file:../concepts/linking.org][Links]] to existing notes
|
||||||
|
4. *Review* - [[file:../concepts/spaced-repetition.org][Spaced Repetition]] for retention
|
||||||
|
|
||||||
|
* Daily Practice
|
||||||
|
|
||||||
|
| Time | Activity |
|
||||||
|
|------|----------|
|
||||||
|
| Morning | Review queue |
|
||||||
|
| Anytime | Capture ideas |
|
||||||
|
| Evening | Process inbox |
|
||||||
|
|
||||||
|
* Tools
|
||||||
|
|
||||||
|
- Org-mode for capture
|
||||||
|
- Org-roam for [[file:../concepts/zettelkasten.org][Zettelkasten]]
|
||||||
|
- Org-drill for review
|
||||||
|
|
||||||
|
* Inspiration
|
||||||
|
|
||||||
|
Based on [[file:../concepts/knowledge-management.org][Knowledge Management]] research and cite:forte2022 Building a Second Brain methodology.
|
||||||
|
|
||||||
|
* Status
|
||||||
|
|
||||||
|
Currently implementing the capture phase. See [[file:website.org][Website Project]] for publishing component.
|
||||||
40
example-notes/projects/website.org
Normal file
40
example-notes/projects/website.org
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#+title: Website Project
|
||||||
|
#+date: 2024-01-20
|
||||||
|
#+filetags: :project:active:
|
||||||
|
|
||||||
|
Building a digital garden from org-mode notes.
|
||||||
|
|
||||||
|
* Overview
|
||||||
|
|
||||||
|
Transform [[file:../concepts/knowledge-management.org][Knowledge Management]] practices into a public website.
|
||||||
|
|
||||||
|
* Goals
|
||||||
|
|
||||||
|
- [X] Convert org files to markdown
|
||||||
|
- [X] Preserve [[file:../concepts/linking.org][Linking]] between notes
|
||||||
|
- [ ] Add search functionality
|
||||||
|
- [ ] Deploy to hosting
|
||||||
|
|
||||||
|
* Technical Stack
|
||||||
|
|
||||||
|
| Component | Tool |
|
||||||
|
|-----------|------|
|
||||||
|
| Source | Org-mode |
|
||||||
|
| Converter | org-to-quartz |
|
||||||
|
| Generator | Quartz |
|
||||||
|
| Hosting | TBD |
|
||||||
|
|
||||||
|
* Architecture
|
||||||
|
|
||||||
|
#+begin_src text
|
||||||
|
org files --> org-to-quartz --> markdown --> Quartz --> HTML
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* Related
|
||||||
|
|
||||||
|
- [[file:../concepts/zettelkasten.org][Zettelkasten]] - The methodology behind the content
|
||||||
|
- [[file:../references/index.org][References]] - Sources cited throughout
|
||||||
|
|
||||||
|
* Notes
|
||||||
|
|
||||||
|
Using [[file:../concepts/atomic-notes.org][Atomic Notes]] makes each page focused and linkable.
|
||||||
32
example-notes/references/ahrens2017.org
Normal file
32
example-notes/references/ahrens2017.org
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#+title: How to Take Smart Notes
|
||||||
|
#+date: 2024-01-05
|
||||||
|
#+filetags: :reference:book:
|
||||||
|
|
||||||
|
* Citation
|
||||||
|
|
||||||
|
Ahrens, S. (2017). /How to Take Smart Notes/. CreateSpace.
|
||||||
|
|
||||||
|
* Summary
|
||||||
|
|
||||||
|
A guide to the [[file:../concepts/zettelkasten.org][Zettelkasten]] method based on Niklas Luhmann's practice.
|
||||||
|
|
||||||
|
* Key Ideas
|
||||||
|
|
||||||
|
1. Writing is thinking
|
||||||
|
2. One idea per note ([[file:../concepts/atomic-notes.org][Atomic Notes]])
|
||||||
|
3. Links over folders ([[file:../concepts/linking.org][Linking]])
|
||||||
|
4. Bottom-up organization
|
||||||
|
|
||||||
|
* Quotes
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
"The slip-box is designed to present you with ideas you have already forgotten."
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
"Every intellectual endeavour starts from an already existing preconception."
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
* Application
|
||||||
|
|
||||||
|
This book directly influenced the [[file:../projects/learning-system.org][Learning System]] project.
|
||||||
31
example-notes/references/index.org
Normal file
31
example-notes/references/index.org
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#+title: References
|
||||||
|
#+date: 2024-01-08
|
||||||
|
#+filetags: :reference:index:
|
||||||
|
|
||||||
|
Collection of books, papers, and resources.
|
||||||
|
|
||||||
|
* Books
|
||||||
|
|
||||||
|
** Knowledge Management
|
||||||
|
- cite:ahrens2017 - How to Take Smart Notes
|
||||||
|
- cite:forte2022 - Building a Second Brain
|
||||||
|
|
||||||
|
** Software & Writing
|
||||||
|
- cite:martin2008 - Clean Code
|
||||||
|
- cite:weinberger2007 - Everything Is Miscellaneous
|
||||||
|
|
||||||
|
* Papers
|
||||||
|
|
||||||
|
- cite:ebbinghaus1885 - Memory: A Contribution to Experimental Psychology
|
||||||
|
- cite:pimsleur1967 - A Memory Schedule
|
||||||
|
|
||||||
|
* Web Resources
|
||||||
|
|
||||||
|
- cite:bernerslee1999 - Weaving the Web
|
||||||
|
|
||||||
|
* How References Are Used
|
||||||
|
|
||||||
|
References connect to concepts:
|
||||||
|
- [[file:../concepts/zettelkasten.org][Zettelkasten]] cites cite:ahrens2017
|
||||||
|
- [[file:../concepts/spaced-repetition.org][Spaced Repetition]] cites cite:ebbinghaus1885
|
||||||
|
- [[file:../concepts/linking.org][Linking]] cites cite:bernerslee1999
|
||||||
254
flake.nix
254
flake.nix
@@ -44,35 +44,267 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Script to serve quartz with converted notes
|
# Quartz package - builds the Quartz static site generator
|
||||||
|
quartz = pkgs.buildNpmPackage {
|
||||||
|
pname = "quartz";
|
||||||
|
version = "4.5.2";
|
||||||
|
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "jackyzha0";
|
||||||
|
repo = "quartz";
|
||||||
|
rev = "v4.5.2";
|
||||||
|
hash = "sha256-A6ePeNmcsbtKVnm7hVFOyjyc7gRYvXuG0XXQ6fvTLEw=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-xxK9qy04m1olekOJIyYJHfdkYFzpjsgcfyFPuKsHpKE=";
|
||||||
|
|
||||||
|
# Quartz doesn't have a build step in the traditional sense
|
||||||
|
# It's a CLI tool that builds sites at runtime
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
# Install the quartz CLI
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/lib/quartz
|
||||||
|
cp -r . $out/lib/quartz
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cat > $out/bin/quartz <<'WRAPPER'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Quartz CLI wrapper - runs quartz from its library directory
|
||||||
|
# or from current directory if it contains a quartz config
|
||||||
|
QUARTZ_LIB="$out/lib/quartz"
|
||||||
|
|
||||||
|
if [[ -f "./quartz.config.ts" ]]; then
|
||||||
|
# Run from current directory (user's project)
|
||||||
|
exec ${pkgs.nodejs}/bin/node "$QUARTZ_LIB/quartz/bootstrap-cli.mjs" "$@"
|
||||||
|
else
|
||||||
|
# Run from quartz lib directory
|
||||||
|
cd "$QUARTZ_LIB"
|
||||||
|
exec ${pkgs.nodejs}/bin/node quartz/bootstrap-cli.mjs "$@"
|
||||||
|
fi
|
||||||
|
WRAPPER
|
||||||
|
# Replace $out with actual path
|
||||||
|
substituteInPlace $out/bin/quartz --replace-quiet '$out' "$out"
|
||||||
|
chmod +x $out/bin/quartz
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "A fast, batteries-included static-site generator for digital gardens";
|
||||||
|
homepage = "https://quartz.jzhao.xyz";
|
||||||
|
mainProgram = "quartz";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Default quartz config that works in Nix sandbox (no network fetches)
|
||||||
|
defaultQuartzConfig = pkgs.writeText "quartz.config.ts" ''
|
||||||
|
import { QuartzConfig } from "./quartz/cfg"
|
||||||
|
import * as Plugin from "./quartz/plugins"
|
||||||
|
|
||||||
|
const config: QuartzConfig = {
|
||||||
|
configuration: {
|
||||||
|
pageTitle: "Quartz Notes",
|
||||||
|
pageTitleSuffix: "",
|
||||||
|
enableSPA: true,
|
||||||
|
enablePopovers: true,
|
||||||
|
analytics: null,
|
||||||
|
locale: "en-US",
|
||||||
|
baseUrl: "localhost",
|
||||||
|
ignorePatterns: ["private", "templates", ".obsidian"],
|
||||||
|
defaultDateType: "modified",
|
||||||
|
theme: {
|
||||||
|
fontOrigin: "local",
|
||||||
|
cdnCaching: false,
|
||||||
|
typography: {
|
||||||
|
header: "sans-serif",
|
||||||
|
body: "sans-serif",
|
||||||
|
code: "monospace",
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
lightMode: {
|
||||||
|
light: "#faf8f8",
|
||||||
|
lightgray: "#e5e5e5",
|
||||||
|
gray: "#b8b8b8",
|
||||||
|
darkgray: "#4e4e4e",
|
||||||
|
dark: "#2b2b2b",
|
||||||
|
secondary: "#284b63",
|
||||||
|
tertiary: "#84a59d",
|
||||||
|
highlight: "rgba(143, 159, 169, 0.15)",
|
||||||
|
textHighlight: "#fff23688",
|
||||||
|
},
|
||||||
|
darkMode: {
|
||||||
|
light: "#161618",
|
||||||
|
lightgray: "#393639",
|
||||||
|
gray: "#646464",
|
||||||
|
darkgray: "#d4d4d4",
|
||||||
|
dark: "#ebebec",
|
||||||
|
secondary: "#7b97aa",
|
||||||
|
tertiary: "#84a59d",
|
||||||
|
highlight: "rgba(143, 159, 169, 0.15)",
|
||||||
|
textHighlight: "#b3aa0288",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
transformers: [
|
||||||
|
Plugin.FrontMatter(),
|
||||||
|
Plugin.CreatedModifiedDate({
|
||||||
|
priority: ["frontmatter", "git", "filesystem"],
|
||||||
|
}),
|
||||||
|
Plugin.SyntaxHighlighting({
|
||||||
|
theme: {
|
||||||
|
light: "github-light",
|
||||||
|
dark: "github-dark",
|
||||||
|
},
|
||||||
|
keepBackground: false,
|
||||||
|
}),
|
||||||
|
Plugin.OxHugoFlavouredMarkdown(),
|
||||||
|
Plugin.GitHubFlavoredMarkdown(),
|
||||||
|
Plugin.TableOfContents(),
|
||||||
|
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
||||||
|
Plugin.Description(),
|
||||||
|
Plugin.Latex({ renderEngine: "katex" }),
|
||||||
|
],
|
||||||
|
filters: [Plugin.RemoveDrafts()],
|
||||||
|
emitters: [
|
||||||
|
Plugin.AliasRedirects(),
|
||||||
|
Plugin.ComponentResources(),
|
||||||
|
Plugin.ContentPage(),
|
||||||
|
Plugin.FolderPage(),
|
||||||
|
Plugin.TagPage(),
|
||||||
|
Plugin.ContentIndex({
|
||||||
|
enableSiteMap: true,
|
||||||
|
enableRSS: true,
|
||||||
|
}),
|
||||||
|
Plugin.Assets(),
|
||||||
|
Plugin.Static(),
|
||||||
|
Plugin.Favicon(),
|
||||||
|
Plugin.NotFoundPage(),
|
||||||
|
// CustomOgImages disabled - requires network access for fonts
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Function to build Quartz pages from a content directory
|
||||||
|
# Usage: buildQuartzPages { contentDir = ./my-org-notes; }
|
||||||
|
# contentDir: directory containing org files or markdown files
|
||||||
|
# quartzConfig: optional path to quartz.config.ts (uses sandbox-friendly default if not provided)
|
||||||
|
# quartzLayout: optional path to quartz.layout.ts
|
||||||
|
buildQuartzPages = {
|
||||||
|
contentDir,
|
||||||
|
quartzConfig ? null,
|
||||||
|
quartzLayout ? null,
|
||||||
|
name ? "quartz-pages"
|
||||||
|
}:
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
inherit name;
|
||||||
|
|
||||||
|
# Don't use src, we copy everything in buildPhase
|
||||||
|
dontUnpack = true;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.nodejs
|
||||||
|
org-to-quartz
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
# Set up a writable quartz directory
|
||||||
|
cp -r ${quartz}/lib/quartz/* .
|
||||||
|
chmod -R u+w .
|
||||||
|
|
||||||
|
# Remove default content
|
||||||
|
rm -rf content
|
||||||
|
mkdir -p content
|
||||||
|
|
||||||
|
# Convert org files to markdown, or copy markdown directly
|
||||||
|
if ls ${contentDir}/*.org >/dev/null 2>&1; then
|
||||||
|
echo "Converting org notes from ${contentDir}..."
|
||||||
|
org-to-quartz ${contentDir} content
|
||||||
|
else
|
||||||
|
echo "Copying content from ${contentDir}..."
|
||||||
|
cp -r ${contentDir}/* content/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply quartz config (use sandbox-friendly default if not provided)
|
||||||
|
${if quartzConfig != null then ''
|
||||||
|
echo "Using custom quartz.config.ts..."
|
||||||
|
cp ${quartzConfig} quartz.config.ts
|
||||||
|
'' else ''
|
||||||
|
echo "Using default sandbox-friendly quartz.config.ts..."
|
||||||
|
cp ${defaultQuartzConfig} quartz.config.ts
|
||||||
|
''}
|
||||||
|
|
||||||
|
${pkgs.lib.optionalString (quartzLayout != null) ''
|
||||||
|
echo "Using custom quartz.layout.ts..."
|
||||||
|
cp ${quartzLayout} quartz.layout.ts
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Build the static site
|
||||||
|
export HOME=$(mktemp -d)
|
||||||
|
echo "Building Quartz site..."
|
||||||
|
node quartz/bootstrap-cli.mjs build
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mv public $out
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Script to serve quartz with converted notes (for development)
|
||||||
quartz-serve = pkgs.writeShellScriptBin "quartz-serve" ''
|
quartz-serve = pkgs.writeShellScriptBin "quartz-serve" ''
|
||||||
set -e
|
set -e
|
||||||
NOTES_DIR="''${1:-.}"
|
NOTES_DIR="''${1:-.}"
|
||||||
PORT="''${2:-8080}"
|
PORT="''${2:-8080}"
|
||||||
WORK_DIR=$(mktemp -d)
|
WORK_DIR=$(mktemp -d)
|
||||||
|
|
||||||
echo "Cloning Quartz..."
|
echo "Setting up Quartz..."
|
||||||
${pkgs.git}/bin/git clone --depth 1 https://github.com/jackyzha0/quartz.git "$WORK_DIR/quartz" 2>/dev/null
|
cp -r ${quartz}/lib/quartz/* "$WORK_DIR/"
|
||||||
|
chmod -R u+w "$WORK_DIR"
|
||||||
echo "Installing dependencies..."
|
|
||||||
cd "$WORK_DIR/quartz"
|
|
||||||
${pkgs.nodejs}/bin/npm install --silent
|
|
||||||
|
|
||||||
echo "Converting org notes from $NOTES_DIR..."
|
echo "Converting org notes from $NOTES_DIR..."
|
||||||
${org-to-quartz}/bin/org-to-quartz "$NOTES_DIR" "$WORK_DIR/quartz/content" -v
|
rm -rf "$WORK_DIR/content"
|
||||||
|
mkdir -p "$WORK_DIR/content"
|
||||||
|
${org-to-quartz}/bin/org-to-quartz "$NOTES_DIR" "$WORK_DIR/content" -v
|
||||||
|
|
||||||
# Enable OxHugo plugin
|
cd "$WORK_DIR"
|
||||||
${pkgs.gnused}/bin/sed -i 's/Plugin.GitHubFlavoredMarkdown()/Plugin.OxHugoFlavouredMarkdown(),\n Plugin.GitHubFlavoredMarkdown()/' quartz.config.ts
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Starting Quartz on http://localhost:$PORT"
|
echo "Starting Quartz on http://localhost:$PORT"
|
||||||
${pkgs.nodejs}/bin/npx quartz build --serve --port "$PORT"
|
${pkgs.nodejs}/bin/node quartz/bootstrap-cli.mjs build --serve --port "$PORT"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# Example: build pages from example-notes directory
|
||||||
|
example-pages = buildQuartzPages {
|
||||||
|
contentDir = ./example-notes;
|
||||||
|
name = "example-quartz-pages";
|
||||||
|
};
|
||||||
|
|
||||||
in {
|
in {
|
||||||
packages = {
|
packages = {
|
||||||
default = org-to-quartz;
|
default = org-to-quartz;
|
||||||
org-to-quartz = org-to-quartz;
|
org-to-quartz = org-to-quartz;
|
||||||
|
quartz = quartz;
|
||||||
|
example-pages = example-pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Export the buildQuartzPages function for use in other flakes
|
||||||
|
lib = {
|
||||||
|
inherit buildQuartzPages;
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
|||||||
@@ -11,18 +11,26 @@ from .citations import CitationResolver
|
|||||||
|
|
||||||
|
|
||||||
def find_org_files(input_dir: Path) -> list[Path]:
|
def find_org_files(input_dir: Path) -> list[Path]:
|
||||||
"""Find all .org files in directory (non-recursive)."""
|
"""Find all .org files in directory (recursive)."""
|
||||||
return list(input_dir.glob("*.org"))
|
return list(input_dir.rglob("*.org"))
|
||||||
|
|
||||||
|
|
||||||
def convert_file(
|
def convert_file(
|
||||||
org_path: Path,
|
org_path: Path,
|
||||||
|
input_dir: Path,
|
||||||
output_dir: Path,
|
output_dir: Path,
|
||||||
citation_resolver: CitationResolver | None,
|
citation_resolver: CitationResolver | None,
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
) -> Path | None:
|
) -> Path | None:
|
||||||
"""Convert a single org file to Quartz markdown.
|
"""Convert a single org file to Quartz markdown.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
org_path: Path to the org file
|
||||||
|
input_dir: Root input directory (for computing relative paths)
|
||||||
|
output_dir: Root output directory
|
||||||
|
citation_resolver: Optional citation resolver
|
||||||
|
verbose: Enable verbose output
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Path to created note directory, or None on error
|
Path to created note directory, or None on error
|
||||||
"""
|
"""
|
||||||
@@ -33,8 +41,21 @@ def convert_file(
|
|||||||
if verbose:
|
if verbose:
|
||||||
print(f" Parsed: {doc.title or org_path.stem}")
|
print(f" Parsed: {doc.title or org_path.stem}")
|
||||||
|
|
||||||
|
# Compute relative path from input dir to preserve directory structure
|
||||||
|
relative_path = org_path.relative_to(input_dir)
|
||||||
|
|
||||||
|
# Special case: root index.org becomes content/index.md directly
|
||||||
|
if relative_path.stem == "index" and relative_path.parent == Path("."):
|
||||||
|
note_dir = output_dir
|
||||||
|
output_path = output_dir / "index.md"
|
||||||
|
else:
|
||||||
|
# Replace .org with directory containing index.md
|
||||||
|
# e.g., concepts/zettelkasten.org -> concepts/zettelkasten/index.md
|
||||||
|
note_relative_dir = relative_path.parent / relative_path.stem
|
||||||
|
note_dir = output_dir / note_relative_dir
|
||||||
|
output_path = note_dir / "index.md"
|
||||||
|
|
||||||
# Create output directory for this note
|
# Create output directory for this note
|
||||||
note_dir = output_dir / doc.slug
|
|
||||||
note_dir.mkdir(parents=True, exist_ok=True)
|
note_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Copy images first (before conversion, to get path mapping)
|
# Copy images first (before conversion, to get path mapping)
|
||||||
@@ -50,7 +71,6 @@ def convert_file(
|
|||||||
md_content = update_image_paths(md_content, image_mapping)
|
md_content = update_image_paths(md_content, image_mapping)
|
||||||
|
|
||||||
# Write output
|
# Write output
|
||||||
output_path = note_dir / "index.md"
|
|
||||||
output_path.write_text(md_content, encoding="utf-8")
|
output_path.write_text(md_content, encoding="utf-8")
|
||||||
|
|
||||||
return note_dir
|
return note_dir
|
||||||
@@ -123,7 +143,7 @@ def main() -> int:
|
|||||||
|
|
||||||
for org_path in org_files:
|
for org_path in org_files:
|
||||||
print(f"Converting: {org_path.name}")
|
print(f"Converting: {org_path.name}")
|
||||||
result = convert_file(org_path, args.output_dir, citation_resolver, args.verbose)
|
result = convert_file(org_path, args.input_dir, args.output_dir, citation_resolver, args.verbose)
|
||||||
if result:
|
if result:
|
||||||
success_count += 1
|
success_count += 1
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ def process_wikilinks(content: str) -> str:
|
|||||||
Pandoc converts org links to markdown links:
|
Pandoc converts org links to markdown links:
|
||||||
- [[roam:Title]] -> [roam:Title](roam:Title) -> [[Title]]
|
- [[roam:Title]] -> [roam:Title](roam:Title) -> [[Title]]
|
||||||
- [[id:uuid][Desc]] -> [Desc](id:uuid) -> [[Desc]]
|
- [[id:uuid][Desc]] -> [Desc](id:uuid) -> [[Desc]]
|
||||||
- [[file:x.org][Desc]] -> [Desc](file:x.org) -> [[Desc]]
|
- [[file:x.org][Desc]] -> [Desc](x) (relative link)
|
||||||
|
- [[file:../dir/x.org][Desc]] -> [Desc](../dir/x) (relative link)
|
||||||
"""
|
"""
|
||||||
# [roam:Title](roam:Title) -> [[Title]]
|
# [roam:Title](roam:Title) -> [[Title]]
|
||||||
content = re.sub(r"\[roam:([^\]]+)\]\(roam:[^)]+\)", r"[[\1]]", content)
|
content = re.sub(r"\[roam:([^\]]+)\]\(roam:[^)]+\)", r"[[\1]]", content)
|
||||||
@@ -65,8 +66,21 @@ def process_wikilinks(content: str) -> str:
|
|||||||
# [Description](id:uuid) -> [[Description]]
|
# [Description](id:uuid) -> [[Description]]
|
||||||
content = re.sub(r"\[([^\]]+)\]\(id:[a-f0-9-]+\)", r"[[\1]]", content)
|
content = re.sub(r"\[([^\]]+)\]\(id:[a-f0-9-]+\)", r"[[\1]]", content)
|
||||||
|
|
||||||
# [Description](file:something.org) -> [[Description]]
|
# [Description](file:path/to/something.org) -> [Description](/path/to/something)
|
||||||
content = re.sub(r"\[([^\]]+)\]\(file:[^)]+\.org\)", r"[[\1]]", content)
|
# Convert org file links to proper Quartz paths
|
||||||
|
def convert_file_link(match):
|
||||||
|
description = match.group(1)
|
||||||
|
path = match.group(2)
|
||||||
|
# Remove file: prefix and .org suffix, convert to Quartz path
|
||||||
|
# ../concepts/foo.org -> ../concepts/foo
|
||||||
|
clean_path = re.sub(r"\.org$", "", path)
|
||||||
|
return f"[{description}]({clean_path})"
|
||||||
|
|
||||||
|
content = re.sub(r"\[([^\]]+)\]\(file:([^)]+\.org)\)", convert_file_link, content)
|
||||||
|
|
||||||
|
# Also handle relative paths without file: prefix (pandoc sometimes drops it)
|
||||||
|
# [Description](../something.org) -> [Description](../something)
|
||||||
|
content = re.sub(r"\[([^\]]+)\]\(([^):]+\.org)\)", convert_file_link, content)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user