forked from github/quartz
Compare commits
1 Commits
ec1426241c
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c425ff2fc |
2
.github/workflows/docker-build-push.yaml
vendored
2
.github/workflows/docker-build-push.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Inject slug/short variables
|
- name: Inject slug/short variables
|
||||||
uses: rlespinasse/github-slug-action@v5.4.0
|
uses: rlespinasse/github-slug-action@v5.5.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,7 +9,3 @@ tsconfig.tsbuildinfo
|
|||||||
private/
|
private/
|
||||||
.replit
|
.replit
|
||||||
replit.nix
|
replit.nix
|
||||||
erl_crash.dump
|
|
||||||
# content/ is generated by the export script; only keep the placeholder
|
|
||||||
content/*
|
|
||||||
!content/.gitkeep
|
|
||||||
|
|||||||
282
AGENTS.md
282
AGENTS.md
@@ -1,282 +0,0 @@
|
|||||||
# AGENTS.md - Coding Agent Instructions
|
|
||||||
|
|
||||||
This document provides essential information for AI coding agents working in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
**Quartz** is a static site generator for publishing digital gardens and notes as websites.
|
|
||||||
Built with TypeScript, Preact, and unified/remark/rehype for markdown processing.
|
|
||||||
|
|
||||||
| Stack | Technology |
|
|
||||||
| ------------- | ----------------------------------------- |
|
|
||||||
| Language | TypeScript 5.x (strict mode) |
|
|
||||||
| Runtime | Node.js >=22 (v22.16.0 pinned) |
|
|
||||||
| Package Mgr | npm >=10.9.2 |
|
|
||||||
| Module System | ES Modules (`"type": "module"`) |
|
|
||||||
| UI Framework | Preact 10.x (JSX with `react-jsx` pragma) |
|
|
||||||
| Build Tool | esbuild |
|
|
||||||
| Styling | SCSS via esbuild-sass-plugin |
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
This is a Nix project. Use the provided `flake.nix` to enter a dev shell with Node.js 22 and npm:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix develop
|
|
||||||
```
|
|
||||||
|
|
||||||
All `npm` commands below must be run inside the dev shell.
|
|
||||||
|
|
||||||
## Build, Lint, and Test Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Type check and format check (CI validation)
|
|
||||||
npm run check
|
|
||||||
|
|
||||||
# Auto-format code with Prettier
|
|
||||||
npm run format
|
|
||||||
|
|
||||||
# Run all tests
|
|
||||||
npm run test
|
|
||||||
|
|
||||||
# Run a single test file
|
|
||||||
npx tsx --test quartz/util/path.test.ts
|
|
||||||
|
|
||||||
# Run tests matching a pattern (use --test-name-pattern)
|
|
||||||
npx tsx --test --test-name-pattern="typeguards" quartz/util/path.test.ts
|
|
||||||
|
|
||||||
# Build the static site
|
|
||||||
npx quartz build
|
|
||||||
|
|
||||||
# Build and serve with hot reload
|
|
||||||
npx quartz build --serve
|
|
||||||
|
|
||||||
# Profile build performance
|
|
||||||
npm run profile
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Files Location
|
|
||||||
|
|
||||||
Tests use Node.js native test runner via `tsx`. Test files follow the `*.test.ts` pattern:
|
|
||||||
|
|
||||||
- `quartz/util/path.test.ts`
|
|
||||||
- `quartz/util/fileTrie.test.ts`
|
|
||||||
- `quartz/components/scripts/search.test.ts`
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
|
|
||||||
### Prettier Configuration (`.prettierrc`)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"printWidth": 100,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": false,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"quoteProps": "as-needed"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**No ESLint** - only Prettier for formatting. Run `npm run format` before committing.
|
|
||||||
|
|
||||||
### TypeScript Configuration
|
|
||||||
|
|
||||||
- **Strict mode enabled** (`strict: true`)
|
|
||||||
- `noUnusedLocals: true` - no unused variables
|
|
||||||
- `noUnusedParameters: true` - no unused function parameters
|
|
||||||
- JSX configured for Preact (`jsxImportSource: "preact"`)
|
|
||||||
|
|
||||||
### Import Conventions
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 1. External packages first
|
|
||||||
import { PluggableList } from "unified"
|
|
||||||
import { visit } from "unist-util-visit"
|
|
||||||
|
|
||||||
// 2. Internal utilities/types (relative paths)
|
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
|
||||||
import { FilePath, slugifyFilePath } from "../../util/path"
|
|
||||||
import { i18n } from "../../i18n"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
| Element | Convention | Example |
|
|
||||||
| ---------------- | ------------ | ----------------------------------- |
|
|
||||||
| Files (utils) | camelCase | `path.ts`, `fileTrie.ts` |
|
|
||||||
| Files (comps) | PascalCase | `TableOfContents.tsx`, `Search.tsx` |
|
|
||||||
| Types/Interfaces | PascalCase | `QuartzComponent`, `FullSlug` |
|
|
||||||
| Type Guards | `is*` prefix | `isFilePath()`, `isFullSlug()` |
|
|
||||||
| Constants | UPPER_CASE | `QUARTZ`, `UPSTREAM_NAME` |
|
|
||||||
| Options types | `Options` | `interface Options { ... }` |
|
|
||||||
|
|
||||||
### Branded Types Pattern
|
|
||||||
|
|
||||||
This codebase uses branded types for type-safe path handling:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type SlugLike<T> = string & { __brand: T }
|
|
||||||
export type FilePath = SlugLike<"filepath">
|
|
||||||
export type FullSlug = SlugLike<"full">
|
|
||||||
export type SimpleSlug = SlugLike<"simple">
|
|
||||||
|
|
||||||
// Always validate with type guards before using
|
|
||||||
export function isFilePath(s: string): s is FilePath { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component Pattern (Preact)
|
|
||||||
|
|
||||||
Components use a factory function pattern with attached static properties:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export default ((userOpts?: Partial<Options>) => {
|
|
||||||
const opts: Options = { ...defaultOptions, ...userOpts }
|
|
||||||
|
|
||||||
const ComponentName: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => {
|
|
||||||
return <div class={classNames(displayClass, "component-name")}>...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
ComponentName.css = style // SCSS styles
|
|
||||||
ComponentName.afterDOMLoaded = script // Client-side JS
|
|
||||||
return ComponentName
|
|
||||||
}) satisfies QuartzComponentConstructor
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plugin Pattern
|
|
||||||
|
|
||||||
Three plugin types: transformers, filters, and emitters.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export const PluginName: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
|
|
||||||
const opts = { ...defaultOptions, ...userOpts }
|
|
||||||
return {
|
|
||||||
name: "PluginName",
|
|
||||||
markdownPlugins(ctx) { return [...] },
|
|
||||||
htmlPlugins(ctx) { return [...] },
|
|
||||||
externalResources(ctx) { return { js: [], css: [] } },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Pattern
|
|
||||||
|
|
||||||
Use Node.js native test runner with `assert`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import test, { describe, beforeEach } from "node:test"
|
|
||||||
import assert from "node:assert"
|
|
||||||
|
|
||||||
describe("FeatureName", () => {
|
|
||||||
test("should do something", () => {
|
|
||||||
assert.strictEqual(actual, expected)
|
|
||||||
assert.deepStrictEqual(actualObj, expectedObj)
|
|
||||||
assert(condition) // truthy assertion
|
|
||||||
assert(!condition) // falsy assertion
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
- Use `try/catch` for critical operations (file I/O, parsing)
|
|
||||||
- Custom `trace` utility for error reporting with stack traces
|
|
||||||
- `process.exit(1)` for fatal errors
|
|
||||||
- `console.warn()` for non-fatal issues
|
|
||||||
|
|
||||||
### Async Patterns
|
|
||||||
|
|
||||||
- Prefer `async/await` over raw promises
|
|
||||||
- Use async generators (`async *emit()`) for streaming file output
|
|
||||||
- Use `async-mutex` for concurrent build protection
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
quartz/
|
|
||||||
├── bootstrap-cli.mjs # CLI entry point
|
|
||||||
├── build.ts # Build orchestration
|
|
||||||
├── cfg.ts # Configuration types
|
|
||||||
├── components/ # Preact UI components
|
|
||||||
│ ├── *.tsx # Components
|
|
||||||
│ ├── scripts/ # Client-side scripts (*.inline.ts)
|
|
||||||
│ └── styles/ # Component SCSS
|
|
||||||
├── plugins/
|
|
||||||
│ ├── transformers/ # Markdown AST transformers
|
|
||||||
│ ├── filters/ # Content filters
|
|
||||||
│ ├── emitters/ # Output generators
|
|
||||||
│ └── types.ts # Plugin type definitions
|
|
||||||
├── processors/ # Build pipeline (parse/filter/emit)
|
|
||||||
├── util/ # Utility functions
|
|
||||||
└── i18n/ # Internationalization (30+ locales)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Branch Workflow
|
|
||||||
|
|
||||||
This is a fork of [jackyzha0/quartz](https://github.com/jackyzha0/quartz) with org-roam customizations.
|
|
||||||
|
|
||||||
| Branch | Purpose |
|
|
||||||
| ----------- | ------------------------------------------------ |
|
|
||||||
| `main` | Clean mirror of upstream quartz — no custom code |
|
|
||||||
| `org-roam` | Default branch — all customizations live here |
|
|
||||||
| `feature/*` | Short-lived branches off `org-roam` |
|
|
||||||
|
|
||||||
### Pulling Upstream Updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout main
|
|
||||||
git fetch upstream
|
|
||||||
git merge upstream/main
|
|
||||||
git checkout org-roam
|
|
||||||
git merge main
|
|
||||||
# Resolve conflicts if any, then commit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working on Features
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout org-roam
|
|
||||||
git checkout -b feature/my-feature
|
|
||||||
# ... work ...
|
|
||||||
git checkout org-roam
|
|
||||||
git merge feature/my-feature
|
|
||||||
git branch -d feature/my-feature
|
|
||||||
```
|
|
||||||
|
|
||||||
**Merge direction:** `upstream → main → org-roam → feature/*`
|
|
||||||
|
|
||||||
## Org-Roam Workflow
|
|
||||||
|
|
||||||
Notes live in a **separate directory** outside this repo. The export script
|
|
||||||
converts them to Markdown via ox-hugo, then Quartz builds the site.
|
|
||||||
|
|
||||||
### Tooling
|
|
||||||
|
|
||||||
The dev shell (`nix develop`) provides:
|
|
||||||
|
|
||||||
- `nodejs_22` — Quartz build
|
|
||||||
- `elixir` — runs the export script
|
|
||||||
- `emacs` + `ox-hugo` — performs the org → markdown conversion
|
|
||||||
|
|
||||||
### Export and build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export only (wipes content/, exports all .org files)
|
|
||||||
NOTES_DIR=/path/to/notes npm run export
|
|
||||||
|
|
||||||
# Export then build the site
|
|
||||||
NOTES_DIR=/path/to/notes npm run build:notes
|
|
||||||
|
|
||||||
# Positional arg also works
|
|
||||||
elixir scripts/export.exs /path/to/notes
|
|
||||||
```
|
|
||||||
|
|
||||||
The export script (`scripts/export.exs`) runs Emacs in batch mode, calling
|
|
||||||
`org-hugo-export-to-md` per file (per-file mode, not per-subtree). It uses
|
|
||||||
YAML frontmatter (ox-hugo default). `content/` is wiped before each export.
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
- **Client-side scripts**: Use `.inline.ts` suffix, bundled via esbuild
|
|
||||||
- **Isomorphic code**: `quartz/util/path.ts` must not use Node.js APIs
|
|
||||||
- **Incremental builds**: Plugins can implement `partialEmit` for efficiency
|
|
||||||
- **Markdown flavors**: Supports Obsidian (`ofm.ts`) and Roam (`roam.ts`) syntax
|
|
||||||
64
README.md
64
README.md
@@ -1,71 +1,13 @@
|
|||||||
# Quartz v4 — org-roam edition
|
# Quartz v4
|
||||||
|
|
||||||
> "[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important." — Richard Hamming
|
> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.” — Richard Hamming
|
||||||
|
|
||||||
Quartz is a set of tools that helps you publish your [digital garden](https://jzhao.xyz/posts/networked-thought) and notes as a website for free.
|
Quartz is a set of tools that helps you publish your [digital garden](https://jzhao.xyz/posts/networked-thought) and notes as a website for free.
|
||||||
|
|
||||||
This fork adds first-class support for [org-roam](https://www.orgroam.com/) notes via [ox-hugo](https://ox-hugo.scripter.co/).
|
🔗 Read the documentation and get started: https://quartz.jzhao.xyz/
|
||||||
|
|
||||||
🔗 Upstream documentation: https://quartz.jzhao.xyz/
|
|
||||||
|
|
||||||
[Join the Discord Community](https://discord.gg/cRFFHYye7t)
|
[Join the Discord Community](https://discord.gg/cRFFHYye7t)
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
This project uses Nix. Enter the development shell, which provides Node.js 22, Elixir, and Emacs with ox-hugo:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix develop
|
|
||||||
```
|
|
||||||
|
|
||||||
All commands below must be run inside this shell.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building from org-roam notes
|
|
||||||
|
|
||||||
Your org-roam notes live in a separate directory. Point `NOTES_DIR` at it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export notes to content/ and build the site
|
|
||||||
NOTES_DIR=/path/to/notes npm run build:notes
|
|
||||||
|
|
||||||
# Export, build, and serve with hot reload
|
|
||||||
NOTES_DIR=/path/to/notes npm run serve:notes
|
|
||||||
|
|
||||||
# Export only (wipes content/ and re-exports all .org files)
|
|
||||||
NOTES_DIR=/path/to/notes npm run export
|
|
||||||
```
|
|
||||||
|
|
||||||
The export script mirrors the subdirectory structure of your notes into `content/`
|
|
||||||
and generates a fallback `index.md` if your notes don't include one.
|
|
||||||
|
|
||||||
### Building without org-roam notes
|
|
||||||
|
|
||||||
If you manage `content/` directly with Markdown files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build the site
|
|
||||||
npx quartz build
|
|
||||||
|
|
||||||
# Build and serve with hot reload
|
|
||||||
npx quartz build --serve
|
|
||||||
```
|
|
||||||
|
|
||||||
The site is generated in `public/`. When serving, visit http://localhost:8080.
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run check # type check + format check
|
|
||||||
npm run format # auto-format with Prettier
|
|
||||||
npm run test # run tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
61
flake.lock
generated
61
flake.lock
generated
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1771008912,
|
|
||||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
30
flake.nix
30
flake.nix
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
description = "Quartz org-roam dev shell";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.nodejs_22
|
|
||||||
pkgs.elixir
|
|
||||||
((pkgs.emacsPackagesFor pkgs.emacs-nox).emacsWithPackages (epkgs: [ epkgs.ox-hugo ]))
|
|
||||||
pkgs.mcp-nixos
|
|
||||||
];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
echo "Node $(node --version) / npm $(npm --version)"
|
|
||||||
elixir --version 2>/dev/null | head -1 || true
|
|
||||||
echo "Emacs $(emacs --version | head -1)"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: emt-madrid
|
|
||||||
:END:
|
|
||||||
#+title: EMT Madrid (urban bus)
|
|
||||||
|
|
||||||
Empresa Municipal de Transportes (EMT) operates the urban bus network
|
|
||||||
within the municipality of Madrid — around 200 lines.
|
|
||||||
|
|
||||||
* Notable lines
|
|
||||||
- *Line 27* — connects Embajadores with Barrio de la Concepción, one of the
|
|
||||||
oldest routes in the network.
|
|
||||||
- *Line 34* — Argüelles to Carabanchel, crossing the city centre via Gran Vía.
|
|
||||||
- *Búho (owl) lines* — night buses running from Cibeles from midnight to 6 am.
|
|
||||||
|
|
||||||
* See also
|
|
||||||
- [[id:madrid-transport][Madrid Public Transport]]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: madrid-transport
|
|
||||||
:END:
|
|
||||||
#+title: Madrid Public Transport
|
|
||||||
|
|
||||||
Madrid has one of the most extensive public transport networks in Europe,
|
|
||||||
operated primarily by [[id:crtm][Consorcio Regional de Transportes de Madrid]] (CRTM).
|
|
||||||
|
|
||||||
* Modes
|
|
||||||
- [[id:metro-madrid][Metro de Madrid]] — 13 lines, ~300 km of track
|
|
||||||
- [[id:emt-madrid][EMT Bus]] — urban buses within the city
|
|
||||||
- Cercanías — suburban rail run by Renfe
|
|
||||||
- Interurbano — regional buses to the wider Community of Madrid
|
|
||||||
|
|
||||||
* Ticketing
|
|
||||||
A single [[https://www.crtm.es][tarjeta transporte]] (transport card) works across all modes.
|
|
||||||
The Multi card covers zones A–C and is topped up at any metro station.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: metro-madrid
|
|
||||||
:END:
|
|
||||||
#+title: Metro de Madrid
|
|
||||||
|
|
||||||
The Madrid Metro is the main rapid transit network in the city, opened in 1919.
|
|
||||||
It is the second oldest metro in the Iberian Peninsula after Barcelona.
|
|
||||||
|
|
||||||
* Key Lines
|
|
||||||
| Line | Name | Colour | Terminals |
|
|
||||||
|------+-----------------+--------+------------------------------|
|
|
||||||
| L1 | Pinar de Chamartín–Valdecarros | Blue | Pinar de Chamartín / Valdecarros |
|
|
||||||
| L6 | Circular | Grey | Circular (loop) |
|
|
||||||
| L10 | — | Dark blue | Hospital Infanta Sofía / Tres Olivos |
|
|
||||||
|
|
||||||
* See also
|
|
||||||
- [[id:madrid-transport][Madrid Public Transport]]
|
|
||||||
- [[id:sol-interchange][Sol interchange]]
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: sol-interchange
|
|
||||||
:END:
|
|
||||||
#+title: Sol (interchange)
|
|
||||||
|
|
||||||
Sol is the busiest interchange station in the Madrid Metro, sitting beneath
|
|
||||||
Puerta del Sol in the city centre.
|
|
||||||
|
|
||||||
Lines serving Sol: [[id:metro-madrid][L1]], L2, L3.
|
|
||||||
|
|
||||||
It also connects to the Cercanías hub underneath, making it the de-facto
|
|
||||||
zero point of Madrid's public transport.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: crtm
|
|
||||||
:END:
|
|
||||||
#+title: CRTM — Consorcio Regional de Transportes de Madrid
|
|
||||||
|
|
||||||
The CRTM is the regional authority that coordinates public transport across
|
|
||||||
the Community of Madrid. It does not operate services directly but sets
|
|
||||||
fares, zones, and integration policy.
|
|
||||||
|
|
||||||
* Fare zones
|
|
||||||
| Zone | Coverage |
|
|
||||||
|-------+-----------------------------|
|
|
||||||
| A | Municipality of Madrid |
|
|
||||||
| B1 | Inner ring municipalities |
|
|
||||||
| B2 | Outer ring municipalities |
|
|
||||||
| B3 | Further suburban area |
|
|
||||||
| C1–C2 | Commuter belt |
|
|
||||||
|
|
||||||
* Related
|
|
||||||
- [[id:madrid-transport][Madrid Public Transport]]
|
|
||||||
- [[id:metro-madrid][Metro de Madrid]]
|
|
||||||
- [[id:emt-madrid][EMT Madrid]]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
:PROPERTIES:
|
|
||||||
:ID: m30
|
|
||||||
:END:
|
|
||||||
#+title: M-30
|
|
||||||
|
|
||||||
The M-30 is Madrid's innermost ring road, circling the city centre at a
|
|
||||||
radius of roughly 3–5 km from Puerta del Sol.
|
|
||||||
|
|
||||||
It runs mostly underground through the Madrid Río tunnel section along the
|
|
||||||
Manzanares river, built during the 2004–2007 renovation that reclaimed the
|
|
||||||
riverbank as a public park.
|
|
||||||
|
|
||||||
* Key junctions
|
|
||||||
- Nudo Norte — connects to A-1 (Burgos) and A-6 (La Coruña)
|
|
||||||
- Nudo Sur — connects to A-4 (Cádiz) and A-42 (Toledo)
|
|
||||||
|
|
||||||
* See also
|
|
||||||
- [[id:crtm][CRTM]]
|
|
||||||
- [[id:madrid-transport][Madrid Public Transport]]
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://opencode.ai/config.json",
|
|
||||||
"mcp": {
|
|
||||||
"nixos": {
|
|
||||||
"type": "local",
|
|
||||||
"command": ["mcp-nixos"],
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,10 +17,7 @@
|
|||||||
"check": "tsc --noEmit && npx prettier . --check",
|
"check": "tsc --noEmit && npx prettier . --check",
|
||||||
"format": "npx prettier . --write",
|
"format": "npx prettier . --write",
|
||||||
"test": "tsx --test",
|
"test": "tsx --test",
|
||||||
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1",
|
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
|
||||||
"export": "elixir scripts/export.exs",
|
|
||||||
"build:notes": "elixir scripts/export.exs && npx quartz build",
|
|
||||||
"serve:notes": "elixir scripts/export.exs && npx quartz build --serve"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">=10.9.2",
|
"npm": ">=10.9.2",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const config: QuartzConfig = {
|
|||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
transformers: [
|
transformers: [
|
||||||
Plugin.FrontMatter({ delimiters: "+++", language: "toml" }),
|
Plugin.FrontMatter(),
|
||||||
Plugin.CreatedModifiedDate({
|
Plugin.CreatedModifiedDate({
|
||||||
priority: ["frontmatter", "git", "filesystem"],
|
priority: ["frontmatter", "git", "filesystem"],
|
||||||
}),
|
}),
|
||||||
@@ -66,11 +66,7 @@ const config: QuartzConfig = {
|
|||||||
},
|
},
|
||||||
keepBackground: false,
|
keepBackground: false,
|
||||||
}),
|
}),
|
||||||
// OxHugoFlavouredMarkdown must come before GitHubFlavoredMarkdown.
|
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
|
||||||
// Note: not compatible with ObsidianFlavoredMarkdown — use one or the other.
|
|
||||||
// If ox-hugo exports TOML frontmatter, change FrontMatter to:
|
|
||||||
// Plugin.FrontMatter({ delims: "+++", language: "toml" })
|
|
||||||
Plugin.OxHugoFlavouredMarkdown(),
|
|
||||||
Plugin.GitHubFlavoredMarkdown(),
|
Plugin.GitHubFlavoredMarkdown(),
|
||||||
Plugin.TableOfContents(),
|
Plugin.TableOfContents(),
|
||||||
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
#!/usr/bin/env elixir
|
|
||||||
# Export org-roam notes (per-file) to content/ via ox-hugo.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# NOTES_DIR=~/notes elixir scripts/export.exs
|
|
||||||
# elixir scripts/export.exs /path/to/notes
|
|
||||||
#
|
|
||||||
# The positional argument takes precedence over the NOTES_DIR env var.
|
|
||||||
|
|
||||||
notes_dir =
|
|
||||||
case System.argv() do
|
|
||||||
[dir | _] -> dir
|
|
||||||
[] ->
|
|
||||||
System.get_env("NOTES_DIR") ||
|
|
||||||
(IO.puts(:stderr, "Usage: NOTES_DIR=/path/to/notes elixir scripts/export.exs"); System.halt(1))
|
|
||||||
end
|
|
||||||
|
|
||||||
notes_dir = Path.expand(notes_dir)
|
|
||||||
repo_root = __DIR__ |> Path.join("..") |> Path.expand()
|
|
||||||
content_dir = Path.join(repo_root, "content")
|
|
||||||
|
|
||||||
unless File.dir?(notes_dir) do
|
|
||||||
IO.puts(:stderr, "Error: notes directory does not exist: #{notes_dir}")
|
|
||||||
System.halt(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Wipe content/, preserving .gitkeep
|
|
||||||
IO.puts("==> Wiping #{content_dir}")
|
|
||||||
|
|
||||||
content_dir
|
|
||||||
|> File.ls!()
|
|
||||||
|> Enum.reject(&(&1 == ".gitkeep"))
|
|
||||||
|> Enum.each(fn entry ->
|
|
||||||
Path.join(content_dir, entry) |> File.rm_rf!()
|
|
||||||
end)
|
|
||||||
|
|
||||||
# Collect all .org files
|
|
||||||
IO.puts("==> Exporting org files from #{notes_dir}")
|
|
||||||
|
|
||||||
org_files =
|
|
||||||
Path.join(notes_dir, "**/*.org")
|
|
||||||
|> Path.wildcard()
|
|
||||||
|
|
||||||
if org_files == [] do
|
|
||||||
IO.puts("No .org files found in #{notes_dir}")
|
|
||||||
System.halt(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Export each file via emacs --batch
|
|
||||||
results =
|
|
||||||
Enum.map(org_files, fn orgfile ->
|
|
||||||
IO.puts(" exporting: #{orgfile}")
|
|
||||||
|
|
||||||
# Mirror the notes subdirectory structure under content/
|
|
||||||
section =
|
|
||||||
orgfile
|
|
||||||
|> Path.dirname()
|
|
||||||
|> Path.relative_to(notes_dir)
|
|
||||||
|
|
||||||
{output, exit_code} =
|
|
||||||
System.cmd(
|
|
||||||
"emacs",
|
|
||||||
[
|
|
||||||
"--batch",
|
|
||||||
"--eval", "(require 'ox-hugo)",
|
|
||||||
"--eval", ~s[(setq org-hugo-base-dir "#{repo_root}")],
|
|
||||||
"--eval", ~s[(setq org-hugo-default-section-directory "#{section}")],
|
|
||||||
"--visit", orgfile,
|
|
||||||
"--funcall", "org-hugo-export-to-md"
|
|
||||||
],
|
|
||||||
stderr_to_stdout: true
|
|
||||||
)
|
|
||||||
|
|
||||||
# Filter noisy emacs startup lines, same as the shell script
|
|
||||||
filtered =
|
|
||||||
output
|
|
||||||
|> String.split("\n")
|
|
||||||
|> Enum.reject(&String.match?(&1, ~r/^Loading|^ad-handle|^For information/))
|
|
||||||
|> Enum.join("\n")
|
|
||||||
|
|
||||||
if filtered != "", do: IO.puts(filtered)
|
|
||||||
|
|
||||||
{orgfile, exit_code}
|
|
||||||
end)
|
|
||||||
|
|
||||||
failures = Enum.filter(results, fn {_, code} -> code != 0 end)
|
|
||||||
|
|
||||||
if failures != [] do
|
|
||||||
IO.puts(:stderr, "\nFailed to export #{length(failures)} file(s):")
|
|
||||||
Enum.each(failures, fn {f, code} -> IO.puts(:stderr, " [exit #{code}] #{f}") end)
|
|
||||||
System.halt(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
md_count =
|
|
||||||
Path.join(content_dir, "**/*.md")
|
|
||||||
|> Path.wildcard()
|
|
||||||
|> length()
|
|
||||||
|
|
||||||
# Generate a default index.md if none was exported
|
|
||||||
index_path = Path.join(content_dir, "index.md")
|
|
||||||
|
|
||||||
unless File.exists?(index_path) do
|
|
||||||
IO.puts("==> Generating default index.md")
|
|
||||||
|
|
||||||
pages =
|
|
||||||
Path.join(content_dir, "**/*.md")
|
|
||||||
|> Path.wildcard()
|
|
||||||
|> Enum.map(fn path ->
|
|
||||||
slug = Path.relative_to(path, content_dir) |> Path.rootname()
|
|
||||||
|
|
||||||
title =
|
|
||||||
path
|
|
||||||
|> File.read!()
|
|
||||||
|> then(fn content ->
|
|
||||||
case Regex.run(~r/^title\s*=\s*"(.+)"/m, content) do
|
|
||||||
[_, t] -> t
|
|
||||||
_ -> slug
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
{slug, title}
|
|
||||||
end)
|
|
||||||
|> Enum.sort_by(fn {_, title} -> title end)
|
|
||||||
|> Enum.map(fn {slug, title} -> "- [#{title}](#{slug})" end)
|
|
||||||
|> Enum.join("\n")
|
|
||||||
|
|
||||||
File.write!(index_path, """
|
|
||||||
---
|
|
||||||
title: Index
|
|
||||||
---
|
|
||||||
|
|
||||||
#{pages}
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
|
|
||||||
IO.puts("==> Done. #{md_count} markdown files in #{content_dir}")
|
|
||||||
Reference in New Issue
Block a user