Initial import
@@ -0,0 +1,4 @@
|
|||||||
|
Guests/
|
||||||
|
**/.secrets/
|
||||||
|
**/.env
|
||||||
|
**/.env*
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
name: pflab-id
|
||||||
|
|
||||||
|
########## ===== ANCHORS ===== ##########
|
||||||
|
|
||||||
|
x-authentik-env: &authentik-env
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: pflab-postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||||
|
|
||||||
|
AUTHENTIK_ERROR_REPORTING__ENABLED: false
|
||||||
|
AUTHENTIK_LOG_LEVEL: info
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
pflab_proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
|
||||||
|
pflab_backend:
|
||||||
|
name: pflab_backend
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
authentik_media:
|
||||||
|
name: pflab_authentik_media
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
authentik_templates:
|
||||||
|
name: pflab_authentik_template
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### AUTHENTIK SERVER #####
|
||||||
|
|
||||||
|
pflab-authentik:
|
||||||
|
|
||||||
|
image: ghcr.io/goauthentik/server:2026.5.3
|
||||||
|
container_name: pflab-authentik
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment: *authentik-env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_templates:/templates
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_proxy
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
##### AUTHENTIK WORKER #####
|
||||||
|
|
||||||
|
pflab-authentik-worker:
|
||||||
|
|
||||||
|
image: ghcr.io/goauthentik/server:2026.5.3
|
||||||
|
container_name: pflab-authentik-worker
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
command: worker
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment: *authentik-env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_templates:/templates
|
||||||
|
|
||||||
|
# Needed only if you want Authentik-managed outposts
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
pflab-authentik:
|
||||||
|
condition: service_started
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: pflab-secrets
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
pflab_proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
|
||||||
|
pflab_auth:
|
||||||
|
name: pflab_auth
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
vaultwarden_data:
|
||||||
|
name: pflab_vaultwarden
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### VAULTWARDEN
|
||||||
|
pflab-vaultwarden:
|
||||||
|
|
||||||
|
image: vaultwarden/server:latest
|
||||||
|
container_name: pflab-vaultwarden
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
WEBSOCKET_ENABLED: "true"
|
||||||
|
LOG_LEVEL: warn
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- vaultwarden_data:/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_proxy
|
||||||
|
- pflab_auth
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: pflab-master-compose
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ###########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
auth:
|
||||||
|
name: pflab_auth
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
backend:
|
||||||
|
name: pflab_backend
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
########## ===== INCLUDES ===== ##########
|
||||||
|
|
||||||
|
include:
|
||||||
|
|
||||||
|
##### REVERSE-PROXY
|
||||||
|
- ./reverse-proxy/pflab-proxy/compose.yaml
|
||||||
|
|
||||||
|
##### AUTH
|
||||||
|
# - ./auth/pflab-id/compose.yaml
|
||||||
|
- ./auth/pflab-secrets/compose.yaml
|
||||||
|
|
||||||
|
##### STORAGE
|
||||||
|
- ./storage/pflab-postgres/compose.yaml
|
||||||
|
- ./storage/pflab-git/compose.yaml
|
||||||
|
## SPACETIME-DB
|
||||||
|
|
||||||
|
##### COMS
|
||||||
|
# - ./coms/pflab-alias/compose.yaml
|
||||||
|
# - ./coms/pflab-coms/compose.yaml
|
||||||
|
|
||||||
|
##### MEDIA
|
||||||
|
## JELLYFIN
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
name: simplelogin
|
||||||
|
|
||||||
|
x-simplelogin-image: &simplelogin-image
|
||||||
|
image: simplelogin/app:4.6.5-beta
|
||||||
|
|
||||||
|
x-simplelogin-env: &simplelogin-env
|
||||||
|
URL: https://alias.tinyhome.ndmv.net
|
||||||
|
EMAIL_DOMAIN: tinyhome.ndmv.net
|
||||||
|
SUPPORT_EMAIL: alias@postbox.tinyhome.ndmv.net
|
||||||
|
EMAIL_SERVERS_WITH_PRIORITY: '[(10, "alias.tinyhome.ndmv.net.")]'
|
||||||
|
DISABLE_ALIAS_SUFFIX: "1"
|
||||||
|
DKIM_PRIVATE_KEY_PATH: /dkim.key
|
||||||
|
GNUPGHOME: /sl/pgp
|
||||||
|
LOCAL_FILE_UPLOAD: "1"
|
||||||
|
POSTFIX_SERVER: simplelogin-postfix
|
||||||
|
|
||||||
|
x-simplelogin-volumes: &simplelogin-volumes
|
||||||
|
- simplelogin_gpg:/sl/pgp
|
||||||
|
- simplelogin_upload:/code/static/upload
|
||||||
|
- ./.secrets/dkim-rsa.key:/dkim.key:ro
|
||||||
|
- ./.secrets/dkim.pub.key:/dkim.pub.key:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pflab_proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
|
||||||
|
pflab_backend:
|
||||||
|
name: pflab_backend
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
simplelogin_gpg:
|
||||||
|
name: pflab_simplelogin_gpg
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
simplelogin_upload:
|
||||||
|
name: pflab_simplelogin_uploads
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
simplelogin-migration:
|
||||||
|
<<: *simplelogin-image
|
||||||
|
command: ["alembic", "upgrade", "head"]
|
||||||
|
container_name: pflab-simplelogin-migration
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
simplelogin-init:
|
||||||
|
<<: *simplelogin-image
|
||||||
|
command: ["python", "init_app.py"]
|
||||||
|
container_name: pflab-simplelogin-init
|
||||||
|
depends_on:
|
||||||
|
simplelogin-migration:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
simplelogin-postfix:
|
||||||
|
image: simplelogin/postfix:latest
|
||||||
|
container_name: pflab-simplelogin-postfix
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment:
|
||||||
|
ALIASES_DEFAULT_DOMAIN: tinyhome.ndmv.net
|
||||||
|
DB_HOST: pflab-postgres
|
||||||
|
DB_USER: simplelogin
|
||||||
|
DB_NAME: simplelogin
|
||||||
|
EMAIL_HANDLER_HOST: pflab-simplelogin-email
|
||||||
|
LETSENCRYPT_EMAIL: alias@postbox.tinyhome.ndmv.net
|
||||||
|
POSTFIX_FQDN: alias.tinyhome.ndmv.net
|
||||||
|
SIMPLELOGIN_COMPATIBILITY_MODE: v4
|
||||||
|
# If you want the postfix container to use certs managed elsewhere,
|
||||||
|
# mount the files and uncomment these:
|
||||||
|
# TLS_KEY_FILE: /tls/privkey.pem
|
||||||
|
# TLS_CERT_FILE: /tls/fullchain.pem
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "25:25"
|
||||||
|
- "587:587"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
simplelogin:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: pflab-simplelogin
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- simplelogin-postfix
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://localhost:7777 >/dev/null || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_proxy
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
simplelogin-email:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: pflab-simplelogin-email
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
simplelogin-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
simplelogin-postfix:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
command: python email_handler.py
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:20381:20381"
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
simplelogin-worker:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: pflab-simplelogin-worker
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
simplelogin-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
simplelogin-email:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
command: python job_runner.py
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_backend
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQC7mLecRqtTzsTTLqooj3gIFnKo7sUBXFI+khykVO6DxX8L4TeF
|
||||||
|
6GaTof8uzdrblTK91FQNDZoVmT+uYsLbDgVQ9sS5kRFJisT7OID20rownsboDJnL
|
||||||
|
dIcPoT7yRleBDW2iaVii7fa6EBXQsBTYftbu+9aphCtIo4eJiS1LVGhEXQIDAQAB
|
||||||
|
AoGAA83gA1JHjSaHRUUP/EyUgY16+8QDyLhHjq9F12tFfvSVU/dYOeXxlpLVauyP
|
||||||
|
wJ4w7jqNDcq10jROX0nva6PIJchHsMll0qPNV7LYxs205uDkkp7Bo4KAWrW5sagw
|
||||||
|
DULem1LjNWtRaiO8KGKArLmvHvW29sXsULJf9YtEUKVDb10CQQDedgMp/+Zb+0rs
|
||||||
|
DgMebdf34UYY4aIrPoVI6WrKQAcXVhOROvduzE0a9xT70CpmGZz92L2Mm9AZQt0c
|
||||||
|
Y/4jI4ALAkEA1+EYbqfSNSXcLk7OPePUjM453t2sDWcgk/hqzmYsinCDk6GPRZpA
|
||||||
|
0Tv9Qy9ygY7F/BSOwf5N1jqsMFQzp4aGNwJBAKql+n1gWPRcSzfS89+GwXHb9Cqo
|
||||||
|
Av+LQTESJSIqhYYIOJBinGX5AHjb6tPT9oJFyaEMfzL6X2LRYM4jglKwK8sCQQCC
|
||||||
|
jfzKnu9/eOAJmVsdDrWyWd2hrImqqV2IX0mofR4esyC/nYsZ2smsQA1QHlevhnqx
|
||||||
|
knUrWPIVxvHvYFzEyZRNAkEAl99yuYIuaUSv1xdTdGI1OKXpAAgCfl5O3GkFej8v
|
||||||
|
5dBIM7t7OgIJvsOHB8PPzIXQ0hhH3xZnmOLiuOipIj/OtA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7mLecRqtTzsTTLqooj3gIFnKo
|
||||||
|
7sUBXFI+khykVO6DxX8L4TeF6GaTof8uzdrblTK91FQNDZoVmT+uYsLbDgVQ9sS5
|
||||||
|
kRFJisT7OID20rownsboDJnLdIcPoT7yRleBDW2iaVii7fa6EBXQsBTYftbu+9ap
|
||||||
|
hCtIo4eJiS1LVGhEXQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: pflab-proxy
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
edge_proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
pflab_proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
|
||||||
|
pflab_auth:
|
||||||
|
name: pflab_auth
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
npm_data:
|
||||||
|
name: pflab_npm_data
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
npm_letsencrypt:
|
||||||
|
name: pflab_npm_letsencrypt
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
npm_app:
|
||||||
|
name: pflab_npm_app
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### PROXY
|
||||||
|
pflab-nginx:
|
||||||
|
|
||||||
|
image: jc21/nginx-proxy-manager:latest
|
||||||
|
container_name: pflab-nginx
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
DISABLE_IPV6: "true"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- npm_data:/data
|
||||||
|
- npm_letsencrypt:/etc/letsencrypt
|
||||||
|
- npm_app:/app
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pflab_proxy:
|
||||||
|
pflab_auth:
|
||||||
|
edge_proxy:
|
||||||
|
ipv4_address: 10.60.0.11
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
name: pflab-git
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
pflab_proxy:
|
||||||
|
name: pflab_proxy
|
||||||
|
|
||||||
|
pflab_backend:
|
||||||
|
name: pflab_backend
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
gitea_data:
|
||||||
|
name: pflab_gitea
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### == GITEA == #####
|
||||||
|
|
||||||
|
pflab-gitea:
|
||||||
|
|
||||||
|
image: docker.gitea.com/gitea:latest
|
||||||
|
container_name: pflab-gitea
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
GITEA__database__DB_TYPE: postgres
|
||||||
|
GITEA__database__HOST: pflab-postgres:5432
|
||||||
|
GITEA__database__NAME: gitea
|
||||||
|
GITEA__server__SSH_PORT: "2223"
|
||||||
|
GITEA__server__SSH_LISTEN_PORT: "22"
|
||||||
|
GITEA__server__HTTP_PORT: "3000"
|
||||||
|
GITEA__mailer__ENABLED: "false"
|
||||||
|
GITEA__mailer__PROTOCOL: "smtp"
|
||||||
|
ports:
|
||||||
|
- "2223:22"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- gitea_data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- pflab_proxy
|
||||||
|
- pflab_backend
|
||||||
|
|
||||||
|
#####
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: pflab-postgres
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
pflab_backend:
|
||||||
|
name: pflab_backend
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
postgres_data:
|
||||||
|
name: pflab_postgres
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
pflab-postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: pflab-postgres
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pflab_backend:
|
||||||
|
aliases:
|
||||||
|
- postgres
|
||||||
|
|
||||||
|
#####
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
name: thlab-id
|
||||||
|
|
||||||
|
########## ===== ANCHORS ===== ##########
|
||||||
|
|
||||||
|
x-authentik-env: &authentik-env
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: thlab-postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||||
|
|
||||||
|
AUTHENTIK_ERROR_REPORTING__ENABLED: false
|
||||||
|
AUTHENTIK_LOG_LEVEL: info
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
|
||||||
|
thlab_backend:
|
||||||
|
name: thlab_backend
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
authentik_media:
|
||||||
|
name: thlab_authentik_media
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
authentik_templates:
|
||||||
|
name: thlab_authentik_template
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### AUTHENTIK SERVER #####
|
||||||
|
|
||||||
|
thlab-authentik:
|
||||||
|
|
||||||
|
image: ghcr.io/goauthentik/server:2026.5.3
|
||||||
|
container_name: thlab-authentik
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment: *authentik-env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_templates:/templates
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_proxy
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
##### AUTHENTIK WORKER #####
|
||||||
|
|
||||||
|
thlab-authentik-worker:
|
||||||
|
|
||||||
|
image: ghcr.io/goauthentik/server:2026.5.3
|
||||||
|
container_name: thlab-authentik-worker
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
command: worker
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment: *authentik-env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_templates:/templates
|
||||||
|
|
||||||
|
# Needed only if you want Authentik-managed outposts
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
thlab-authentik:
|
||||||
|
condition: service_started
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: thlab-secrets
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
|
||||||
|
thlab_auth:
|
||||||
|
name: thlab_auth
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
vaultwarden_data:
|
||||||
|
name: thlab_vaultwarden
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### VAULTWARDEN
|
||||||
|
thlab-vaultwarden:
|
||||||
|
|
||||||
|
image: vaultwarden/server:latest
|
||||||
|
container_name: thlab-vaultwarden
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
WEBSOCKET_ENABLED: "true"
|
||||||
|
LOG_LEVEL: warn
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- vaultwarden_data:/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_proxy
|
||||||
|
- thlab_auth
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: master-compose
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ###########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
auth:
|
||||||
|
name: thlab_auth
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
backend:
|
||||||
|
name: thlab_backend
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
########## ===== INCLUDES ===== ##########
|
||||||
|
|
||||||
|
include:
|
||||||
|
|
||||||
|
##### REVERSE-PROXY
|
||||||
|
- ./reverse-proxy/thlab-proxy/compose.yaml
|
||||||
|
|
||||||
|
##### AUTH
|
||||||
|
- ./auth/thlab-id/compose.yaml
|
||||||
|
- ./auth/thlab-secrets/compose.yaml
|
||||||
|
|
||||||
|
##### STORAGE
|
||||||
|
- ./storage/thlab-postgres/compose.yaml
|
||||||
|
- ./storage/thlab-git/compose.yaml
|
||||||
|
- ./storage/thlab-notes/compose.yaml
|
||||||
|
## SPACETIME-DB
|
||||||
|
|
||||||
|
##### COMS
|
||||||
|
- ./coms/thlab-alias/compose.yaml
|
||||||
|
# - ./coms/thlab-coms/compose.yaml
|
||||||
|
|
||||||
|
##### MEDIA
|
||||||
|
## JELLYFIN
|
||||||
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
name: simplelogin
|
||||||
|
|
||||||
|
x-simplelogin-image: &simplelogin-image
|
||||||
|
image: simplelogin/app:4.6.5-beta
|
||||||
|
|
||||||
|
x-simplelogin-env: &simplelogin-env
|
||||||
|
URL: https://alias.tinyhome.ndmv.net
|
||||||
|
EMAIL_DOMAIN: tinyhome.ndmv.net
|
||||||
|
SUPPORT_EMAIL: alias@postbox.tinyhome.ndmv.net
|
||||||
|
EMAIL_SERVERS_WITH_PRIORITY: '[(10, "alias.tinyhome.ndmv.net.")]'
|
||||||
|
DISABLE_ALIAS_SUFFIX: "1"
|
||||||
|
DKIM_PRIVATE_KEY_PATH: /dkim.key
|
||||||
|
GNUPGHOME: /sl/pgp
|
||||||
|
LOCAL_FILE_UPLOAD: "1"
|
||||||
|
POSTFIX_SERVER: simplelogin-postfix
|
||||||
|
|
||||||
|
x-simplelogin-volumes: &simplelogin-volumes
|
||||||
|
- simplelogin_gpg:/sl/pgp
|
||||||
|
- simplelogin_upload:/code/static/upload
|
||||||
|
- ./.secrets/dkim-rsa.key:/dkim.key:ro
|
||||||
|
- ./.secrets/dkim.pub.key:/dkim.pub.key:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
|
||||||
|
thlab_backend:
|
||||||
|
name: thlab_backend
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
simplelogin_gpg:
|
||||||
|
name: thlab_simplelogin_gpg
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
simplelogin_upload:
|
||||||
|
name: thlab_simplelogin_uploads
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
simplelogin-migration:
|
||||||
|
<<: *simplelogin-image
|
||||||
|
command: ["alembic", "upgrade", "head"]
|
||||||
|
container_name: thlab-simplelogin-migration
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
simplelogin-init:
|
||||||
|
<<: *simplelogin-image
|
||||||
|
command: ["python", "init_app.py"]
|
||||||
|
container_name: thlab-simplelogin-init
|
||||||
|
depends_on:
|
||||||
|
simplelogin-migration:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
simplelogin-postfix:
|
||||||
|
image: simplelogin/postfix:latest
|
||||||
|
container_name: thlab-simplelogin-postfix
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment:
|
||||||
|
ALIASES_DEFAULT_DOMAIN: tinyhome.ndmv.net
|
||||||
|
DB_HOST: thlab-postgres
|
||||||
|
DB_USER: simplelogin
|
||||||
|
DB_NAME: simplelogin
|
||||||
|
EMAIL_HANDLER_HOST: thlab-simplelogin-email
|
||||||
|
LETSENCRYPT_EMAIL: alias@postbox.tinyhome.ndmv.net
|
||||||
|
POSTFIX_FQDN: alias.tinyhome.ndmv.net
|
||||||
|
SIMPLELOGIN_COMPATIBILITY_MODE: v4
|
||||||
|
# If you want the postfix container to use certs managed elsewhere,
|
||||||
|
# mount the files and uncomment these:
|
||||||
|
# TLS_KEY_FILE: /tls/privkey.pem
|
||||||
|
# TLS_CERT_FILE: /tls/fullchain.pem
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "25:25"
|
||||||
|
- "587:587"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
simplelogin:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: thlab-simplelogin
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- simplelogin-postfix
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -fsS http://localhost:7777 >/dev/null || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_proxy
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
simplelogin-email:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: thlab-simplelogin-email
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
simplelogin-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
simplelogin-postfix:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
command: python email_handler.py
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:20381:20381"
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
simplelogin-worker:
|
||||||
|
<<: [*simplelogin-image]
|
||||||
|
container_name: thlab-simplelogin-worker
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
simplelogin-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
simplelogin-email:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
command: python job_runner.py
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- ./.secrets/.env
|
||||||
|
environment: *simplelogin-env
|
||||||
|
|
||||||
|
volumes: *simplelogin-volumes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_backend
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQC7mLecRqtTzsTTLqooj3gIFnKo7sUBXFI+khykVO6DxX8L4TeF
|
||||||
|
6GaTof8uzdrblTK91FQNDZoVmT+uYsLbDgVQ9sS5kRFJisT7OID20rownsboDJnL
|
||||||
|
dIcPoT7yRleBDW2iaVii7fa6EBXQsBTYftbu+9aphCtIo4eJiS1LVGhEXQIDAQAB
|
||||||
|
AoGAA83gA1JHjSaHRUUP/EyUgY16+8QDyLhHjq9F12tFfvSVU/dYOeXxlpLVauyP
|
||||||
|
wJ4w7jqNDcq10jROX0nva6PIJchHsMll0qPNV7LYxs205uDkkp7Bo4KAWrW5sagw
|
||||||
|
DULem1LjNWtRaiO8KGKArLmvHvW29sXsULJf9YtEUKVDb10CQQDedgMp/+Zb+0rs
|
||||||
|
DgMebdf34UYY4aIrPoVI6WrKQAcXVhOROvduzE0a9xT70CpmGZz92L2Mm9AZQt0c
|
||||||
|
Y/4jI4ALAkEA1+EYbqfSNSXcLk7OPePUjM453t2sDWcgk/hqzmYsinCDk6GPRZpA
|
||||||
|
0Tv9Qy9ygY7F/BSOwf5N1jqsMFQzp4aGNwJBAKql+n1gWPRcSzfS89+GwXHb9Cqo
|
||||||
|
Av+LQTESJSIqhYYIOJBinGX5AHjb6tPT9oJFyaEMfzL6X2LRYM4jglKwK8sCQQCC
|
||||||
|
jfzKnu9/eOAJmVsdDrWyWd2hrImqqV2IX0mofR4esyC/nYsZ2smsQA1QHlevhnqx
|
||||||
|
knUrWPIVxvHvYFzEyZRNAkEAl99yuYIuaUSv1xdTdGI1OKXpAAgCfl5O3GkFej8v
|
||||||
|
5dBIM7t7OgIJvsOHB8PPzIXQ0hhH3xZnmOLiuOipIj/OtA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7mLecRqtTzsTTLqooj3gIFnKo
|
||||||
|
7sUBXFI+khykVO6DxX8L4TeF6GaTof8uzdrblTK91FQNDZoVmT+uYsLbDgVQ9sS5
|
||||||
|
kRFJisT7OID20rownsboDJnLdIcPoT7yRleBDW2iaVii7fa6EBXQsBTYftbu+9ap
|
||||||
|
hCtIo4eJiS1LVGhEXQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: thlab-proxy
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
edge_proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
|
||||||
|
thlab_auth:
|
||||||
|
name: thlab_auth
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
npm_data:
|
||||||
|
name: thlab_npm_data
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
npm_letsencrypt:
|
||||||
|
name: thlab_npm_letsencrypt
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
npm_app:
|
||||||
|
name: thlab_npm_app
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### PROXY
|
||||||
|
thlab-nginx:
|
||||||
|
|
||||||
|
image: jc21/nginx-proxy-manager:latest
|
||||||
|
container_name: thlab-nginx
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
DISABLE_IPV6: "true"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- npm_data:/data
|
||||||
|
- npm_letsencrypt:/etc/letsencrypt
|
||||||
|
- npm_app:/app
|
||||||
|
|
||||||
|
networks:
|
||||||
|
thlab_proxy:
|
||||||
|
thlab_auth:
|
||||||
|
edge_proxy:
|
||||||
|
ipv4_address: 10.60.0.10
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: thlab-git
|
||||||
|
|
||||||
|
########## ===== NETWORKS ===== ##########
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
|
||||||
|
thlab_backend:
|
||||||
|
name: thlab_backend
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== VOLUMES ===== ##########
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
|
||||||
|
gitea_data:
|
||||||
|
name: thlab_gitea
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
#####
|
||||||
|
|
||||||
|
########## ===== SERVICES ===== ##########
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
##### == GITEA == #####
|
||||||
|
|
||||||
|
thlab-gitea:
|
||||||
|
|
||||||
|
image: docker.gitea.com/gitea:latest
|
||||||
|
container_name: thlab-gitea
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
env_file: [.secrets/.env]
|
||||||
|
environment:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
GITEA__database__DB_TYPE: postgres
|
||||||
|
GITEA__database__HOST: thlab-postgres:5432
|
||||||
|
GITEA__database__NAME: gitea
|
||||||
|
GITEA__server__SSH_LISTEN_PORT: "22"
|
||||||
|
GITEA__server__HTTP_PORT: "3000"
|
||||||
|
GITEA__mailer__ENABLED: "true"
|
||||||
|
GITEA__mailer__PROTOCOL: "smtp"
|
||||||
|
ports:
|
||||||
|
- "2222:22"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- gitea_data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_proxy
|
||||||
|
- thlab_backend
|
||||||
|
|
||||||
|
#####
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: thlab-notes
|
||||||
|
|
||||||
|
networks:
|
||||||
|
thlab_proxy:
|
||||||
|
name: thlab_proxy
|
||||||
|
volumes:
|
||||||
|
quartz:
|
||||||
|
name: thlab_notes
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
services:
|
||||||
|
quartz:
|
||||||
|
build:
|
||||||
|
context: ./worker
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: thlab-notes
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- quartz:/usr/src/app
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- thlab_proxy
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- VAULT_REPO=https://git.tinyhome.ndmv.net/Tiny-Home/Tiny-Home.git
|
||||||
|
- VAULT_BRANCH=main
|
||||||
|
- SYNC_INTERVAL=30
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
v22.16.0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
public
|
||||||
|
node_modules
|
||||||
|
.quartz-cache
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100,
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.canvas",
|
||||||
|
"options": {
|
||||||
|
"parser": "json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.base",
|
||||||
|
"options": {
|
||||||
|
"parser": "yaml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Citizen Code of Conduct
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
|
||||||
|
A primary goal of the Quartz community is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||||
|
|
||||||
|
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||||
|
|
||||||
|
We invite all those who participate in the Quartz community to help us create safe and positive experiences for everyone.
|
||||||
|
|
||||||
|
## 2. Open [Source/Culture/Tech] Citizenship
|
||||||
|
|
||||||
|
A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
|
||||||
|
|
||||||
|
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
|
||||||
|
|
||||||
|
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
|
||||||
|
|
||||||
|
## 3. Expected Behavior
|
||||||
|
|
||||||
|
The following behaviors are expected and requested of all community members:
|
||||||
|
|
||||||
|
- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
|
||||||
|
- Exercise consideration and respect in your speech and actions.
|
||||||
|
- Attempt collaboration before conflict.
|
||||||
|
- Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||||
|
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
|
||||||
|
- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
|
||||||
|
|
||||||
|
## 4. Unacceptable Behavior
|
||||||
|
|
||||||
|
The following behaviors are considered harassment and are unacceptable within our community:
|
||||||
|
|
||||||
|
- Violence, threats of violence or violent language directed against another person.
|
||||||
|
- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
|
||||||
|
- Posting or displaying sexually explicit or violent material.
|
||||||
|
- Posting or threatening to post other people's personally identifying information ("doxing").
|
||||||
|
- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
|
||||||
|
- Inappropriate photography or recording.
|
||||||
|
- Inappropriate physical contact. You should have someone's consent before touching them.
|
||||||
|
- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
|
||||||
|
- Deliberate intimidation, stalking or following (online or in person).
|
||||||
|
- Advocating for, or encouraging, any of the above behavior.
|
||||||
|
- Sustained disruption of community events, including talks and presentations.
|
||||||
|
|
||||||
|
## 5. Weapons Policy
|
||||||
|
|
||||||
|
No weapons will be allowed at Quartz community events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter.
|
||||||
|
|
||||||
|
## 6. Consequences of Unacceptable Behavior
|
||||||
|
|
||||||
|
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
|
||||||
|
|
||||||
|
Anyone asked to stop unacceptable behavior is expected to comply immediately.
|
||||||
|
|
||||||
|
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
|
||||||
|
|
||||||
|
## 7. Reporting Guidelines
|
||||||
|
|
||||||
|
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. j.zhao2k19@gmail.com.
|
||||||
|
|
||||||
|
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
|
||||||
|
|
||||||
|
## 8. Addressing Grievances
|
||||||
|
|
||||||
|
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify @jackyzha0 with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||||
|
|
||||||
|
## 9. Scope
|
||||||
|
|
||||||
|
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business.
|
||||||
|
|
||||||
|
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||||
|
|
||||||
|
## 10. Contact info
|
||||||
|
|
||||||
|
j.zhao2k19@gmail.com
|
||||||
|
|
||||||
|
## 11. License and attribution
|
||||||
|
|
||||||
|
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||||
|
|
||||||
|
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||||
|
|
||||||
|
_Revision 2.3. Posted 6 March 2017._
|
||||||
|
|
||||||
|
_Revision 2.2. Posted 4 February 2016._
|
||||||
|
|
||||||
|
_Revision 2.1. Posted 23 June 2014._
|
||||||
|
|
||||||
|
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
FROM node:22-slim AS builder
|
||||||
|
|
||||||
|
# Install git and other dependencies
|
||||||
|
RUN apt-get update && apt-get install -y git ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY package.json .
|
||||||
|
COPY package-lock.json* .
|
||||||
|
COPY quartz/ ./quartz/
|
||||||
|
COPY quartz.lock.json .
|
||||||
|
RUN npm ci && npx quartz plugin install
|
||||||
|
|
||||||
|
FROM node:22-slim
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install git and ca-certificates for vault repo access
|
||||||
|
RUN apt-get update && apt-get install -y git ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /usr/src/app/ /usr/src/app/
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Copy and make entrypoint executable
|
||||||
|
COPY entrypoint.sh /usr/src/app/entrypoint.sh
|
||||||
|
RUN chmod +x /usr/src/app/entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 jackyzha0
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Quartz v5
|
||||||
|
|
||||||
|
> “[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.
|
||||||
|
|
||||||
|
🔗 Read the documentation and get started: https://quartz.jzhao.xyz/
|
||||||
|
|
||||||
|
[Join the Discord Community](https://discord.gg/cRFFHYye7t)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/sponsors/jackyzha0">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/jackyzha0/jackyzha0/sponsorkit/sponsors.svg" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
filters:
|
||||||
|
and:
|
||||||
|
- file.ext == "md"
|
||||||
|
formulas:
|
||||||
|
doc_type: |
|
||||||
|
if(file.hasTag("plugin/transformer"), "transformer",
|
||||||
|
if(file.hasTag("plugin/emitter"), "emitter",
|
||||||
|
if(file.hasTag("plugin/filter"), "filter",
|
||||||
|
if(file.hasTag("component"), "component",
|
||||||
|
if(file.inFolder("features"), "feature",
|
||||||
|
if(file.inFolder("advanced"), "advanced",
|
||||||
|
if(file.inFolder("plugins"), "plugin",
|
||||||
|
if(file.inFolder("getting-started"), "getting-started",
|
||||||
|
if(file.inFolder("cli"), "cli", "guide")))))))))
|
||||||
|
last_modified: file.mtime.relative()
|
||||||
|
section: |
|
||||||
|
if(file.inFolder("plugins"), "plugins",
|
||||||
|
if(file.inFolder("features"), "features",
|
||||||
|
if(file.inFolder("advanced"), "advanced",
|
||||||
|
if(file.inFolder("getting-started"), "getting-started",
|
||||||
|
if(file.inFolder("cli"), "cli",
|
||||||
|
if(file.inFolder("tags"), "tags", "core"))))))
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
displayName: Title
|
||||||
|
formula.doc_type:
|
||||||
|
displayName: Type
|
||||||
|
formula.last_modified:
|
||||||
|
displayName: Updated
|
||||||
|
formula.section:
|
||||||
|
displayName: Section
|
||||||
|
views:
|
||||||
|
- type: table
|
||||||
|
name: All Documentation
|
||||||
|
groupBy:
|
||||||
|
property: formula.section
|
||||||
|
direction: ASC
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.doc_type
|
||||||
|
- formula.section
|
||||||
|
- formula.last_modified
|
||||||
|
sort:
|
||||||
|
- property: formula.doc_type
|
||||||
|
direction: ASC
|
||||||
|
- property: file.name
|
||||||
|
direction: ASC
|
||||||
|
columnSize:
|
||||||
|
file.name: 185
|
||||||
|
note.title: 268
|
||||||
|
formula.doc_type: 146
|
||||||
|
formula.section: 276
|
||||||
|
- type: table
|
||||||
|
name: Plugins
|
||||||
|
filters:
|
||||||
|
or:
|
||||||
|
- file.hasTag("plugin/transformer")
|
||||||
|
- file.hasTag("plugin/emitter")
|
||||||
|
- file.hasTag("plugin/filter")
|
||||||
|
groupBy:
|
||||||
|
property: formula.doc_type
|
||||||
|
direction: ASC
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.doc_type
|
||||||
|
- formula.last_modified
|
||||||
|
- type: table
|
||||||
|
name: Components & Features
|
||||||
|
filters:
|
||||||
|
or:
|
||||||
|
- file.hasTag("component")
|
||||||
|
- file.inFolder("features")
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.doc_type
|
||||||
|
- formula.last_modified
|
||||||
|
- type: list
|
||||||
|
name: Recently Updated
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- formula.last_modified
|
||||||
|
limit: 15
|
||||||
|
- type: table
|
||||||
|
name: Core Guides
|
||||||
|
filters:
|
||||||
|
not:
|
||||||
|
- file.inFolder("plugins")
|
||||||
|
- file.inFolder("features")
|
||||||
|
- file.inFolder("advanced")
|
||||||
|
- file.inFolder("getting-started")
|
||||||
|
- file.inFolder("cli")
|
||||||
|
- file.inFolder("tags")
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.last_modified
|
||||||
|
- type: board
|
||||||
|
name: By Type (Board)
|
||||||
|
groupBy:
|
||||||
|
property: formula.doc_type
|
||||||
|
direction: ASC
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.last_modified
|
||||||
|
- type: gallery
|
||||||
|
name: Gallery
|
||||||
|
order:
|
||||||
|
- title
|
||||||
|
- formula.doc_type
|
||||||
|
- formula.section
|
||||||
|
limit: 30
|
||||||
|
- type: cards
|
||||||
|
name: Cards
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
- title
|
||||||
|
- formula.doc_type
|
||||||
|
- formula.section
|
||||||
|
- formula.last_modified
|
||||||
|
limit: 24
|
||||||
|
- type: cards
|
||||||
|
name: Image Cards
|
||||||
|
filters:
|
||||||
|
and:
|
||||||
|
- file.folder == "plugins"
|
||||||
|
- "!image.isEmpty()"
|
||||||
|
order:
|
||||||
|
- file.name
|
||||||
|
image: note.image
|
||||||
|
cardSize: 220
|
||||||
|
imageAspectRatio: 1
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "group-node-types",
|
||||||
|
"type": "group",
|
||||||
|
"x": -30,
|
||||||
|
"y": 260,
|
||||||
|
"width": 1220,
|
||||||
|
"height": 460,
|
||||||
|
"color": "6",
|
||||||
|
"label": "Node Types"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "group-config",
|
||||||
|
"type": "group",
|
||||||
|
"x": -30,
|
||||||
|
"y": 1180,
|
||||||
|
"width": 1220,
|
||||||
|
"height": 360,
|
||||||
|
"color": "2",
|
||||||
|
"label": "Configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "group-colors",
|
||||||
|
"type": "group",
|
||||||
|
"x": -30,
|
||||||
|
"y": 790,
|
||||||
|
"width": 1220,
|
||||||
|
"height": 320,
|
||||||
|
"color": "4",
|
||||||
|
"label": "Preset Colors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "group-edges",
|
||||||
|
"type": "group",
|
||||||
|
"x": -30,
|
||||||
|
"y": 1610,
|
||||||
|
"width": 1220,
|
||||||
|
"height": 320,
|
||||||
|
"color": "3",
|
||||||
|
"label": "Edges & Connections"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "title",
|
||||||
|
"type": "text",
|
||||||
|
"text": "# CanvasPage Plugin\n\nThis plugin renders [JSON Canvas](https://jsoncanvas.org) (`.canvas`) files as interactive, pannable and zoomable canvas pages. It supports the full [JSON Canvas 1.0 spec](https://jsoncanvas.org/spec/1.0/).\n\nInstall: `npx quartz plugin add github:quartz-community/canvas-page`",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 560,
|
||||||
|
"height": 200,
|
||||||
|
"color": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "text-node-demo",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## Text Nodes\n\nText nodes render **Markdown** content with GFM support:\n\n- **Bold** and *italic* text\n- ~~Strikethrough~~ text\n- [External links](https://jsoncanvas.org)\n- `Inline code` blocks\n- Lists (like this one)\n\n### Headings Work Too\n\nAll standard Markdown syntax is rendered at build time.",
|
||||||
|
"x": 0,
|
||||||
|
"y": 300,
|
||||||
|
"width": 360,
|
||||||
|
"height": 280,
|
||||||
|
"color": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "file-node-info",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## File Nodes\n\nFile nodes reference other pages in your vault. They appear as clickable links and support **popover previews** on hover.\n\nThe node below links to the CanvasPage documentation:",
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"width": 360,
|
||||||
|
"height": 160,
|
||||||
|
"color": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "file-node-demo",
|
||||||
|
"type": "file",
|
||||||
|
"file": "plugins/CanvasPage.md",
|
||||||
|
"x": 400,
|
||||||
|
"y": 500,
|
||||||
|
"width": 360,
|
||||||
|
"height": 80,
|
||||||
|
"color": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link-node-info",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## Link Nodes\n\nLink nodes reference external URLs. The node below links to the JSON Canvas specification:",
|
||||||
|
"x": 800,
|
||||||
|
"y": 300,
|
||||||
|
"width": 360,
|
||||||
|
"height": 120,
|
||||||
|
"color": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link-node-demo",
|
||||||
|
"type": "link",
|
||||||
|
"url": "https://jsoncanvas.org/spec/1.0/",
|
||||||
|
"x": 800,
|
||||||
|
"y": 460,
|
||||||
|
"width": 360,
|
||||||
|
"height": 80,
|
||||||
|
"color": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-1",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 1** — Red",
|
||||||
|
"x": 0,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-2",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 2** — Orange",
|
||||||
|
"x": 200,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-3",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 3** — Yellow",
|
||||||
|
"x": 400,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-4",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 4** — Green",
|
||||||
|
"x": 600,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-5",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 5** — Cyan",
|
||||||
|
"x": 800,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-6",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Color 6** — Purple",
|
||||||
|
"x": 1000,
|
||||||
|
"y": 830,
|
||||||
|
"width": 180,
|
||||||
|
"height": 80,
|
||||||
|
"color": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "color-custom",
|
||||||
|
"type": "text",
|
||||||
|
"text": "**Custom hex color** — `#ff6600`",
|
||||||
|
"x": 400,
|
||||||
|
"y": 950,
|
||||||
|
"width": 380,
|
||||||
|
"height": 80,
|
||||||
|
"color": "#ff6600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "config-options",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## Configuration Options\n\n- `enableInteraction` — Enable pan and zoom. Default: `true`\n- `initialZoom` — Initial zoom level. Default: `1`\n- `minZoom` — Minimum zoom level. Default: `0.1`\n- `maxZoom` — Maximum zoom level. Default: `5`\n- `defaultFullscreen` — Start in fullscreen mode. Default: `false`\n\nConfigure in `quartz.config.yaml`:\n\n```\nCanvasPage({ defaultFullscreen: false, initialZoom: 1 })\n```",
|
||||||
|
"x": 0,
|
||||||
|
"y": 1220,
|
||||||
|
"width": 560,
|
||||||
|
"height": 280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "config-fullscreen",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## Fullscreen Mode\n\nClick the **expand button** (top-right corner) to toggle fullscreen mode. The canvas fills the entire viewport.\n\n- Press **Escape** to exit fullscreen\n- Set `defaultFullscreen: true` to start in fullscreen\n- The toggle button switches between expand and collapse icons\n\n## Quartz Integration\n\n- **Popover previews**: Hover over file nodes to see a preview\n- **Internal links**: File nodes link to pages in your vault\n- **Dark mode**: Canvas adapts to your theme settings",
|
||||||
|
"x": 600,
|
||||||
|
"y": 1220,
|
||||||
|
"width": 560,
|
||||||
|
"height": 280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-source",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## Edges\n\nEdges connect nodes with SVG paths. They support **labels**, **arrows**, and **colors**.",
|
||||||
|
"x": 0,
|
||||||
|
"y": 1650,
|
||||||
|
"width": 300,
|
||||||
|
"height": 120,
|
||||||
|
"color": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-labeled",
|
||||||
|
"type": "text",
|
||||||
|
"text": "This edge has a **label** and an arrow marker.",
|
||||||
|
"x": 450,
|
||||||
|
"y": 1650,
|
||||||
|
"width": 260,
|
||||||
|
"height": 80,
|
||||||
|
"color": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-colored",
|
||||||
|
"type": "text",
|
||||||
|
"text": "This edge has a **custom color** (`#ff6600`).",
|
||||||
|
"x": 450,
|
||||||
|
"y": 1780,
|
||||||
|
"width": 260,
|
||||||
|
"height": 80,
|
||||||
|
"color": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-preset",
|
||||||
|
"type": "text",
|
||||||
|
"text": "Edges can use the same **preset colors** (1–6) as nodes, or custom **hex colors** like `#ff6600`.",
|
||||||
|
"x": 850,
|
||||||
|
"y": 1650,
|
||||||
|
"width": 300,
|
||||||
|
"height": 120,
|
||||||
|
"color": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api-info",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## API\n\n- **Category**: Page Type\n- **Function name**: `ExternalPlugin.CanvasPage()`\n- **Source**: [quartz-community/canvas-page](https://github.com/quartz-community/canvas-page)\n- **Install**: `npx quartz plugin add github:quartz-community/canvas-page`",
|
||||||
|
"x": 0,
|
||||||
|
"y": 2000,
|
||||||
|
"width": 560,
|
||||||
|
"height": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "spec-info",
|
||||||
|
"type": "text",
|
||||||
|
"text": "## JSON Canvas Spec\n\nThis plugin implements the [JSON Canvas 1.0](https://jsoncanvas.org/spec/1.0/) specification — an open file format for infinite canvas data.\n\nCanvas files use the `.canvas` extension and are standard JSON. They are natively supported by [Obsidian](https://obsidian.md).",
|
||||||
|
"x": 600,
|
||||||
|
"y": 2000,
|
||||||
|
"width": 560,
|
||||||
|
"height": 180
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "edge-title-to-types",
|
||||||
|
"fromNode": "title",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "group-node-types",
|
||||||
|
"toSide": "top",
|
||||||
|
"label": "supports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-info-to-file",
|
||||||
|
"fromNode": "file-node-info",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "file-node-demo",
|
||||||
|
"toSide": "top",
|
||||||
|
"color": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-info-to-link",
|
||||||
|
"fromNode": "link-node-info",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "link-node-demo",
|
||||||
|
"toSide": "top",
|
||||||
|
"color": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-types-to-colors",
|
||||||
|
"fromNode": "group-node-types",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "group-colors",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-colors-to-config",
|
||||||
|
"fromNode": "group-colors",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "group-config",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-config-to-edges",
|
||||||
|
"fromNode": "group-config",
|
||||||
|
"fromSide": "bottom",
|
||||||
|
"toNode": "group-edges",
|
||||||
|
"toSide": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-labeled-demo",
|
||||||
|
"fromNode": "edge-source",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toNode": "edge-labeled",
|
||||||
|
"toSide": "left",
|
||||||
|
"label": "labeled edge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-colored-demo",
|
||||||
|
"fromNode": "edge-source",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toNode": "edge-colored",
|
||||||
|
"toSide": "left",
|
||||||
|
"color": "#ff6600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "edge-preset-demo",
|
||||||
|
"fromNode": "edge-labeled",
|
||||||
|
"fromSide": "right",
|
||||||
|
"toNode": "edge-preset",
|
||||||
|
"toSide": "left",
|
||||||
|
"color": "6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
title: Architecture
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz is a static site generator. How does it work?
|
||||||
|
|
||||||
|
This question is best answered by tracing what happens when a user (you!) runs `npx quartz build` in the command line:
|
||||||
|
|
||||||
|
## On the server
|
||||||
|
|
||||||
|
1. After running `npx quartz build`, npm will look at `package.json` to find the `bin` entry for `quartz` which points at `./quartz/bootstrap-cli.mjs`.
|
||||||
|
2. This file has a [shebang](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) line at the top which tells npm to execute it using Node.
|
||||||
|
3. `bootstrap-cli.mjs` is responsible for a few things:
|
||||||
|
1. Parsing the command-line arguments using [yargs](http://yargs.js.org/). The `plugin` subcommand is also handled here for managing external plugins.
|
||||||
|
2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare using a custom `esbuild` plugin that runs another instance of `esbuild` which bundles for the browser instead of `node`. Modules of both types are imported as plain text.
|
||||||
|
3. Running the local preview server if `--serve` is set. This starts two servers:
|
||||||
|
1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration).
|
||||||
|
2. An HTTP file-server on a user defined port (normally 8080) to serve the actual website files.
|
||||||
|
4. If the `--serve` flag is set, it also starts a file watcher to detect source-code changes (e.g. anything that is `.ts`, `.tsx`, `.scss`, or packager files). On a change, we rebuild the module (step 2 above) using esbuild's [rebuild API](https://esbuild.github.io/api/#rebuild) which drastically reduces the build times.
|
||||||
|
5. After transpiling the main Quartz build module (`quartz/build.ts`), we write it to a cache file `.quartz-cache/transpiled-build.mjs` and then dynamically import this using `await import(cacheFile)`. However, we need to be pretty smart about how to bust Node's [import cache](https://github.com/nodejs/modules/issues/307) so we add a random query string to fake Node into thinking it's a new module. This does, however, cause memory leaks so we just hope that the user doesn't hot-reload their configuration too many times in a single session :)) (it leaks about ~350kB memory on each reload). After importing the module, we then invoke it, passing in the command line arguments we parsed earlier along with a callback function to signal the client to refresh.
|
||||||
|
4. In `build.ts`, we start by installing source map support manually to account for the query string cache busting hack we introduced earlier. Then, we start processing content:
|
||||||
|
1. Clean the output directory.
|
||||||
|
2. Recursively glob all files in the `content` folder, respecting the `.gitignore`.
|
||||||
|
3. Parse the Markdown files.
|
||||||
|
1. Quartz detects the number of threads available and chooses to spawn worker threads if there are >128 pieces of content to parse (rough heuristic). If it needs to spawn workers, it will invoke esbuild again to transpile the worker script `quartz/worker.ts`. Then, a work-stealing [workerpool](https://www.npmjs.com/package/workerpool) is then created and batches of 128 files are assigned to workers.
|
||||||
|
2. Each worker (or just the main thread if there is no concurrency) creates a [unified](https://github.com/unifiedjs/unified) parser based off of the plugins defined in the [[configuration]].
|
||||||
|
3. Parsing has three steps:
|
||||||
|
1. Read the file into a [vfile](https://github.com/vfile/vfile).
|
||||||
|
2. Applied plugin-defined text transformations over the content.
|
||||||
|
3. Slugify the file path and store it in the data for the file. See the page on [[paths]] for more details about how path logic works in Quartz (spoiler: its complicated).
|
||||||
|
4. Markdown parsing using [remark-parse](https://www.npmjs.com/package/remark-parse) (text to [mdast](https://github.com/syntax-tree/mdast)).
|
||||||
|
5. Apply plugin-defined Markdown-to-Markdown transformations.
|
||||||
|
6. Convert Markdown into HTML using [remark-rehype](https://github.com/remarkjs/remark-rehype) ([mdast](https://github.com/syntax-tree/mdast) to [hast](https://github.com/syntax-tree/hast)).
|
||||||
|
7. Apply plugin-defined HTML-to-HTML transformations.
|
||||||
|
4. Filter out unwanted content using plugins.
|
||||||
|
5. Emit files using plugins.
|
||||||
|
1. Gather all the static resources (e.g. external CSS, JS modules, etc.) each emitter plugin declares.
|
||||||
|
2. Emitters that emit HTML files do a bit of extra work here as they need to transform the [hast](https://github.com/syntax-tree/hast) produced in the parse step to JSX. This is done using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) with the [Preact](https://preactjs.com/) runtime. Finally, the JSX is rendered to HTML using [preact-render-to-string](https://github.com/preactjs/preact-render-to-string) which statically renders the JSX to HTML (i.e. doesn't care about `useState`, `useEffect`, or any other React/Preact interactive bits). Here, we also do a bunch of fun stuff like assemble the page [[layout]] from `quartz.config.yaml`, assemble all the inline scripts that actually get shipped to the client, and all the transpiled styles. The bulk of this logic can be found in `quartz/components/renderPage.tsx`. Other fun things of note:
|
||||||
|
1. CSS is minified and transformed using [Lightning CSS](https://github.com/parcel-bundler/lightningcss) to add vendor prefixes and do syntax lowering.
|
||||||
|
2. Scripts are split into `beforeDOMLoaded` and `afterDOMLoaded` and are inserted in the `<head>` and `<body>` respectively.
|
||||||
|
3. Finally, each emitter plugin is responsible for emitting and writing it's own emitted files to disk.
|
||||||
|
6. If the `--serve` flag was detected, we also set up another file watcher to detect content changes (only `.md` files). We keep a content map that tracks the parsed AST and plugin data for each slug and update this on file changes. Newly added or modified paths are rebuilt and added to the content map. Then, all the filters and emitters are run over the resulting content map. This file watcher is debounced with a threshold of 250ms. On success, we send a client refresh signal using the passed in callback function.
|
||||||
|
|
||||||
|
## On the client
|
||||||
|
|
||||||
|
1. The browser opens a Quartz page and loads the HTML. The `<head>` also links to page styles (emitted to `public/index.css`) and page-critical JS (emitted to `public/prescript.js`)
|
||||||
|
2. Then, once the body is loaded, the browser loads the non-critical JS (emitted to `public/postscript.js`)
|
||||||
|
3. Once the page is done loading, the page will then dispatch a custom synthetic browser event `"nav"`. This is used so client-side scripts declared by components can 'setup' anything that requires access to the page DOM.
|
||||||
|
1. If the [[SPA Routing|enableSPA option]] is enabled in the [[configuration]], this `"nav"` event is also fired on any client-navigation to allow for components to unregister and reregister any event handlers and state.
|
||||||
|
2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts.
|
||||||
|
3. A separate `"render"` event can be dispatched when the DOM is updated in-place without a full navigation (e.g. after content decryption). Components that attach listeners to content elements should listen for both `"nav"` and `"render"`.
|
||||||
|
|
||||||
|
## Community Package Layering
|
||||||
|
|
||||||
|
Quartz v5 separates shared code into three community packages, each with a distinct responsibility:
|
||||||
|
|
||||||
|
- **`@quartz-community/types`** — Type definitions, interfaces, and the canonical `vfile` DataMap augmentation. This is the "contract" between Quartz and plugins. It has no runtime dependencies.
|
||||||
|
- **`@quartz-community/utils`** — Shared utility functions (path manipulation, DOM helpers, sorting, date formatting, JSX conversion, etc.). Depends on `@quartz-community/types`.
|
||||||
|
- **`@quartz-community/runtime`** — Browser-only utilities for client-side scripts (event handling, navigation, storage, script loading). Depends on both `types` and `utils`.
|
||||||
|
|
||||||
|
```
|
||||||
|
types (no deps)
|
||||||
|
↑
|
||||||
|
utils (depends on types)
|
||||||
|
↑
|
||||||
|
runtime (depends on types + utils)
|
||||||
|
↑
|
||||||
|
plugins (depend on any combination)
|
||||||
|
```
|
||||||
|
|
||||||
|
Plugins should import types from `@quartz-community/types`, utility functions from `@quartz-community/utils`, and browser utilities from `@quartz-community/runtime`. This layering ensures plugins don't depend on Quartz core.
|
||||||
|
|
||||||
|
## Plugin System
|
||||||
|
|
||||||
|
Page types define how a category of pages is rendered. They are configured in the `pageTypes` array in `quartz.config.yaml`.
|
||||||
|
|
||||||
|
Quartz v5 introduces a community plugin system. Plugins are standalone Git repositories that are cloned into `.quartz/plugins/` and re-exported through an auto-generated index file at `.quartz/plugins/index.ts`.
|
||||||
|
|
||||||
|
### Plugin Types
|
||||||
|
|
||||||
|
There are now four plugin categories:
|
||||||
|
|
||||||
|
- **Transformers**: Map over content (parse frontmatter, generate descriptions, syntax highlighting)
|
||||||
|
- **Filters**: Filter content (remove drafts, explicit publish)
|
||||||
|
- **Emitters**: Reduce over content (generate RSS, sitemaps, alias redirects, OG images)
|
||||||
|
- **Page Types**: Define how pages are rendered. Each page type handles a specific kind of page (content notes, folder listings, tag listings, 404). The `PageTypeDispatcher` emitter routes pages to the appropriate page type plugin based on the content.
|
||||||
|
- **Bases Views**: Custom view renderers for the `bases-page` plugin's database-like view system. Plugins can register new view types (e.g., timeline, kanban) via the `ViewRegistry`. See [[making plugins#Bases Views]] for details.
|
||||||
|
|
||||||
|
Note that plugin types are **not mutually exclusive** — a single plugin can be a transformer AND provide components (e.g., `obsidian-flavored-markdown`), or be a page type AND provide custom frames (e.g., `canvas-page`).
|
||||||
|
|
||||||
|
### Plugin Resolution
|
||||||
|
|
||||||
|
When `npx quartz plugin add github:quartz-community/explorer` is run:
|
||||||
|
|
||||||
|
1. The repository is cloned into `.quartz/plugins/explorer/`
|
||||||
|
2. The plugin is built using `tsup` (defined in each plugin's `tsup.config.ts`)
|
||||||
|
3. An auto-generated `.quartz/plugins/index.ts` re-exports all installed plugins
|
||||||
|
4. The plugin's commit hash is recorded in `quartz.lock.json`
|
||||||
|
|
||||||
|
### Plugin CLI Commands
|
||||||
|
|
||||||
|
- `npx quartz plugin add github:quartz-community/<name>` — Install a community plugin
|
||||||
|
- `npx quartz plugin install --latest` — Update all plugins to latest commits
|
||||||
|
- `npx quartz plugin install --clean` — Restore plugins from locked commits in `quartz.lock.json` (used in CI/CD)
|
||||||
|
- `npx quartz plugin remove <name>` — Remove an installed plugin
|
||||||
|
|
||||||
|
### Plugin Structure
|
||||||
|
|
||||||
|
Each community plugin repository contains:
|
||||||
|
|
||||||
|
- `src/index.ts` — Plugin entry point exporting the plugin function
|
||||||
|
- `tsup.config.ts` — Build configuration using tsup
|
||||||
|
- `package.json` — Declares dependencies on `@quartz-community/types` and `@quartz-community/utils`
|
||||||
|
|
||||||
|
The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|creating plugins]].
|
||||||
|
|
||||||
|
## Page Frames
|
||||||
|
|
||||||
|
Page frames control the inner HTML structure of each page. While the outer shell (`<html>`, `<head>`, `<body>`, `#quartz-root`) is always the same (required for [[SPA Routing]]), the frame determines how layout slots are arranged inside the page.
|
||||||
|
|
||||||
|
The frame system lives in `quartz/components/frames/` and consists of:
|
||||||
|
|
||||||
|
- `types.ts` — Defines the `PageFrame` and `PageFrameProps` interfaces
|
||||||
|
- `DefaultFrame.tsx` — Three-column layout (left sidebar, center, right sidebar, footer)
|
||||||
|
- `FullWidthFrame.tsx` — No sidebars, single center column
|
||||||
|
- `MinimalFrame.tsx` — No sidebars, no header/beforeBody, just content and footer
|
||||||
|
- `registry.ts` — `FrameRegistry` singleton for plugin-registered frames
|
||||||
|
- `index.ts` — `resolveFrame()` function and built-in frame registry
|
||||||
|
|
||||||
|
### Frame Registry
|
||||||
|
|
||||||
|
The `FrameRegistry` (`quartz/components/frames/registry.ts`) is a singleton that stores frames registered by community plugins. It mirrors the design of the `ComponentRegistry`. Plugins declare frames in their `package.json` manifest under the `"quartz"."frames"` field, and these are loaded by `quartz/plugins/loader/frameLoader.ts` during plugin initialization.
|
||||||
|
|
||||||
|
### Frame Resolution
|
||||||
|
|
||||||
|
The rendering pipeline in `quartz/components/renderPage.tsx` delegates to the resolved frame's `render()` function. Frame resolution happens in the `PageTypeDispatcher` emitter (`quartz/plugins/pageTypes/dispatcher.ts`) using this priority:
|
||||||
|
|
||||||
|
1. YAML config: `layout.byPageType.<name>.template`
|
||||||
|
2. Plugin-registered frame: looked up by name in the `FrameRegistry`
|
||||||
|
3. Built-in frame: looked up by name in the `builtinFrames` map
|
||||||
|
4. Fallback: `"default"`
|
||||||
|
|
||||||
|
The active frame name is set as a `data-frame` attribute on the `.page` element, enabling frame-specific CSS overrides in `quartz/styles/base.scss`.
|
||||||
|
|
||||||
|
### Plugin-Provided Frames
|
||||||
|
|
||||||
|
Community plugins can ship their own frames by exporting them from a `./frames` subpath and declaring them in the plugin manifest. For example, the `canvas-page` plugin provides a `"canvas"` frame with a fullscreen layout and togglable sidebar. See [[making plugins#Providing Custom Frames]] for implementation details.
|
||||||
|
|
||||||
|
See [[layout#Page Frames]] for user-facing documentation and [[making plugins#Page Types]] for how to set frames in page type plugins.
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
---
|
||||||
|
title: Creating Component Plugins
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> This guide assumes you have experience writing JavaScript and are familiar with TypeScript.
|
||||||
|
|
||||||
|
Normally on the web, we write layout code using HTML which looks something like the following:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article>
|
||||||
|
<h1>An article header</h1>
|
||||||
|
<p>Some content</p>
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
This piece of HTML represents an article with a leading header that says "An article header" and a paragraph that contains the text "Some content". This is combined with CSS to style the page and JavaScript to add interactivity.
|
||||||
|
|
||||||
|
However, HTML doesn't let you create reusable templates. If you wanted to create a new page, you would need to copy and paste the above snippet and edit the header and content yourself. This isn't great if we have a lot of content on our site that shares a lot of similar layout. The smart people who created React also had similar complaints and invented the concept of Components -- JavaScript functions that return JSX -- to solve the code duplication problem.
|
||||||
|
|
||||||
|
In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. **While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.**
|
||||||
|
|
||||||
|
## Community Component Plugins
|
||||||
|
|
||||||
|
In v5, most components are community plugins — standalone repositories that export a `QuartzComponent`. These plugins are decoupled from the core Quartz repository, allowing for easier maintenance and sharing.
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
To create a new component plugin, you can use the official plugin template:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/quartz-community/plugin-template.git my-component
|
||||||
|
cd my-component
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Structure
|
||||||
|
|
||||||
|
A component plugin's `src/index.ts` typically exports a function (a constructor) that returns a `QuartzComponent`. This allows users to pass configuration options to your component.
|
||||||
|
|
||||||
|
```tsx title="src/index.ts"
|
||||||
|
import {
|
||||||
|
QuartzComponent,
|
||||||
|
QuartzComponentConstructor,
|
||||||
|
QuartzComponentProps,
|
||||||
|
} from "@quartz-community/types"
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
favouriteNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
favouriteNumber: 42,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyComponent: QuartzComponentConstructor<Options> = (userOpts?: Options) => {
|
||||||
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
|
|
||||||
|
const Component: QuartzComponent = (props: QuartzComponentProps) => {
|
||||||
|
if (opts.favouriteNumber < 0) return null
|
||||||
|
return <p>My favourite number is {opts.favouriteNumber}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
return Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyComponent
|
||||||
|
```
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
All Quartz components accept the same set of props:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export type QuartzComponentProps = {
|
||||||
|
fileData: QuartzPluginData
|
||||||
|
cfg: GlobalConfiguration
|
||||||
|
tree: Node<QuartzPluginData>
|
||||||
|
allFiles: QuartzPluginData[]
|
||||||
|
displayClass?: "mobile-only" | "desktop-only"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fileData`: Any metadata plugins may have added to the current page.
|
||||||
|
- `fileData.slug`: slug of the current page.
|
||||||
|
- `fileData.frontmatter`: any frontmatter parsed.
|
||||||
|
- `cfg`: The `configuration` field in `quartz.config.yaml`.
|
||||||
|
- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file.
|
||||||
|
- `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure.
|
||||||
|
- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting.
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
In community plugins, styles are bundled with the plugin. You can define styles using the `.css` property on the component:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
Component.css = `
|
||||||
|
.my-component { color: red; }
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
For SCSS, you can import it and assign it to the `.css` property. The build system will handle the transformation:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import styles from "./styles.scss"
|
||||||
|
Component.css = styles
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Quartz does not use CSS modules so any styles you declare here apply _globally_. If you only want it to apply to your component, make sure you use specific class names and selectors.
|
||||||
|
|
||||||
|
### Internationalization
|
||||||
|
|
||||||
|
Component plugins should use the i18n pattern for any user-facing strings. See [[making plugins#Internationalization (i18n)]] for the full setup guide.
|
||||||
|
|
||||||
|
Quick reference:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { i18n } from "../i18n"
|
||||||
|
|
||||||
|
const MyComponent: QuartzComponent = ({ cfg }) => {
|
||||||
|
const t = i18n(cfg.locale ?? "en-US").components.myComponent
|
||||||
|
return <h2>{t.title}</h2>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Always provide at least an `en-US` locale as the fallback. Additional locales are optional but encouraged for international reach.
|
||||||
|
|
||||||
|
### Scripts and Interactivity
|
||||||
|
|
||||||
|
For interactivity, you can declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties on the component. These should be strings containing the JavaScript to be executed in the browser.
|
||||||
|
|
||||||
|
- `.beforeDOMLoaded`: Executed _before_ the page is done loading. Used for prefetching or early initialization.
|
||||||
|
- `.afterDOMLoaded`: Executed once the page has been completely loaded.
|
||||||
|
|
||||||
|
If you need to create an `afterDOMLoaded` script that depends on page-specific elements that may change when navigating, listen for the `"nav"` event:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
document.addEventListener("nav", () => {
|
||||||
|
// do page specific logic here
|
||||||
|
const toggleSwitch = document.querySelector("#switch") as HTMLInputElement
|
||||||
|
if (toggleSwitch) {
|
||||||
|
toggleSwitch.addEventListener("change", switchTheme)
|
||||||
|
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the `"prenav"` event, which fires before the page is replaced during SPA navigation.
|
||||||
|
|
||||||
|
The `"render"` event fires when the DOM has been updated in-place without a full navigation — for example, after content decryption or dynamic DOM modifications by other plugins. If your component attaches event listeners to content elements, listen for `"render"` in addition to `"nav"` to ensure re-initialization:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function setupMyComponent() {
|
||||||
|
const elements = document.querySelectorAll(".my-interactive")
|
||||||
|
for (const el of elements) {
|
||||||
|
el.addEventListener("click", handleClick)
|
||||||
|
window.addCleanup(() => el.removeEventListener("click", handleClick))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("nav", setupMyComponent)
|
||||||
|
document.addEventListener("render", setupMyComponent)
|
||||||
|
```
|
||||||
|
|
||||||
|
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks during SPA navigation.
|
||||||
|
|
||||||
|
#### Importing Code
|
||||||
|
|
||||||
|
In community plugins, TypeScript scripts should be transpiled at build time. The plugin template includes an `inlineScriptPlugin` in `tsup.config.ts` that automatically transpiles `.inline.ts` files imported as text:
|
||||||
|
|
||||||
|
```tsx title="src/index.ts"
|
||||||
|
import script from "./script.inline.ts"
|
||||||
|
|
||||||
|
const Component: QuartzComponent = (props) => {
|
||||||
|
return <button id="btn">Click me</button>
|
||||||
|
}
|
||||||
|
Component.afterDOMLoaded = script
|
||||||
|
```
|
||||||
|
|
||||||
|
The `inlineScriptPlugin` handles transpiling TypeScript to browser-compatible JavaScript during the build step, allowing you to write type-safe client-side code.
|
||||||
|
|
||||||
|
### Installing Your Component
|
||||||
|
|
||||||
|
Once your component is published (e.g., to GitHub or npm), users can install it using the Quartz CLI:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:your-username/my-component
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, they can add it to their `quartz.config.yaml`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:your-username/my-component
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
favouriteNumber: 42
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 60
|
||||||
|
```
|
||||||
|
|
||||||
|
For advanced usage via the TS override in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
import Plugin from "./.quartz/plugins"
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig()
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout({
|
||||||
|
byPageType: {
|
||||||
|
content: {
|
||||||
|
left: [Plugin.MyComponent({ favouriteNumber: 42 })],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Receiving YAML Options in Component-Only Plugins
|
||||||
|
|
||||||
|
Component plugins that also belong to a processing category (transformer, filter, emitter, page type) receive options through their factory function automatically. However, **component-only plugins** — those whose manifest declares only `"category": ["component"]` — are loaded via side-effect import and don't go through the factory path.
|
||||||
|
|
||||||
|
To receive YAML options in a component-only plugin, export an `init` function from your entry point:
|
||||||
|
|
||||||
|
```ts title="src/index.ts"
|
||||||
|
export function init(options?: Record<string, unknown>): void {
|
||||||
|
// options contains merged defaultOptions + user's YAML options
|
||||||
|
const myFlag = (options?.myFlag as boolean) ?? false
|
||||||
|
// Use options to configure registrations, global state, etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Quartz's config-loader calls `init()` after importing the module, passing the merged result of your manifest's `defaultOptions` and the user's `options` from `quartz.config.yaml`. The merge follows the same `{ ...defaultOptions, ...userOptions }` pattern used for processing plugins — user values take precedence.
|
||||||
|
|
||||||
|
Declare your defaults in `package.json`:
|
||||||
|
|
||||||
|
```json title="package.json"
|
||||||
|
{
|
||||||
|
"quartz": {
|
||||||
|
"category": ["component"],
|
||||||
|
"defaultOptions": {
|
||||||
|
"myFlag": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If your plugin does not export `init`, it continues to work as a pure side-effect import — this is fully backward compatible.
|
||||||
|
|
||||||
|
## Internal Components
|
||||||
|
|
||||||
|
Quartz also has internal components that provide layout utilities. These live in `quartz/components/` and are primarily used for structural purposes:
|
||||||
|
|
||||||
|
- `Component.Head()` — renders the `<head>` tag
|
||||||
|
- `Component.Spacer()` — adds flexible space
|
||||||
|
- `Component.Flex()` — flexible layout container
|
||||||
|
- `Component.MobileOnly()` — shows component only on mobile
|
||||||
|
- `Component.DesktopOnly()` — shows component only on desktop
|
||||||
|
- `Component.ConditionalRender()` — conditionally renders based on page data
|
||||||
|
|
||||||
|
See [[layout-components]] for more details on these utilities.
|
||||||
|
|
||||||
|
> [!hint]
|
||||||
|
> Look at existing community plugins like [Explorer](https://github.com/quartz-community/explorer) or [Darkmode](https://github.com/quartz-community/darkmode) for real-world examples.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Advanced"
|
||||||
|
---
|
||||||
|
|
||||||
|
This section covers advanced topics for users who want to extend or deeply customize Quartz.
|
||||||
|
|
||||||
|
- **[[architecture]]** — How Quartz works under the hood: the parse, filter, and emit pipeline
|
||||||
|
- **[[making plugins]]** — Build your own transformer, filter, emitter, or component plugin
|
||||||
|
- **[[creating components]]** — Create custom layout components with JSX
|
||||||
|
- **[[paths]]** — How Quartz resolves and transforms file paths
|
||||||
@@ -0,0 +1,748 @@
|
|||||||
|
---
|
||||||
|
title: Making your own plugins
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> This part of the documentation will assume you have working knowledge in TypeScript and will include code snippets that describe the interface of what Quartz plugins should look like.
|
||||||
|
|
||||||
|
Quartz's plugins are a series of transformations over content. This is illustrated in the diagram of the processing pipeline below:
|
||||||
|
|
||||||
|
![[quartz transform pipeline.png]]
|
||||||
|
|
||||||
|
All plugins are defined as a function that takes in a single parameter for options `type OptionType = object | undefined` and return an object that corresponds to the type of plugin it is.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type OptionType = object | undefined
|
||||||
|
type QuartzPlugin<Options extends OptionType = undefined> = (opts?: Options) => QuartzPluginInstance
|
||||||
|
type QuartzPluginInstance =
|
||||||
|
| QuartzTransformerPluginInstance
|
||||||
|
| QuartzFilterPluginInstance
|
||||||
|
| QuartzEmitterPluginInstance
|
||||||
|
| QuartzPageTypePluginInstance
|
||||||
|
```
|
||||||
|
|
||||||
|
The following sections will go into detail for what methods can be implemented for each plugin type. Before we do that, let's clarify a few more ambiguous types:
|
||||||
|
|
||||||
|
- `BuildCtx` is defined in `@quartz-community/types`. It consists of
|
||||||
|
- `argv`: The command line arguments passed to the Quartz [[build]] command
|
||||||
|
- `cfg`: The full Quartz [[configuration]]
|
||||||
|
- `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a slug is)
|
||||||
|
- `StaticResources` is defined in `@quartz-community/types`. It consists of
|
||||||
|
- `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type. It accepts either a source URL or the inline content of the stylesheet.
|
||||||
|
- `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script.
|
||||||
|
- `additionalHead`: a list of JSX elements or functions that return JSX elements to be added to the `<head>` tag of the page. Functions receive the page's data as an argument and can conditionally render elements.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
In v5, plugins are standalone repositories. The easiest way to create one is using the plugin template:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Use the plugin template to create a new repository on GitHub
|
||||||
|
# Then clone it locally
|
||||||
|
git clone https://github.com/your-username/my-plugin.git
|
||||||
|
cd my-plugin
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
The template provides the build configuration (`tsup.config.ts`), TypeScript setup, and correct package structure.
|
||||||
|
|
||||||
|
## Plugin Structure
|
||||||
|
|
||||||
|
The basic file structure of a plugin is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-plugin/
|
||||||
|
├── src/
|
||||||
|
│ └── index.ts # Plugin entry point
|
||||||
|
├── tsup.config.ts # Build configuration
|
||||||
|
├── package.json # Dependencies and exports
|
||||||
|
└── tsconfig.json # TypeScript configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
The plugin's `package.json` should declare dependencies on `@quartz-community/types` (for type definitions) and optionally `@quartz-community/utils` (for shared utilities).
|
||||||
|
|
||||||
|
## Plugin Types
|
||||||
|
|
||||||
|
## Choosing a Plugin Type
|
||||||
|
|
||||||
|
Quartz supports six plugin capabilities. A single plugin can combine multiple types.
|
||||||
|
|
||||||
|
| I want to... | Plugin Type |
|
||||||
|
| ------------------------------------------------ | ----------- |
|
||||||
|
| Transform Markdown/HTML content | Transformer |
|
||||||
|
| Decide which pages to publish | Filter |
|
||||||
|
| Generate output files (RSS, sitemaps, manifests) | Emitter |
|
||||||
|
| Define how a category of pages renders | Page Type |
|
||||||
|
| Add a UI component to the layout | Component |
|
||||||
|
| Add a custom view to the Bases database system | Bases View |
|
||||||
|
|
||||||
|
These are **not mutually exclusive**. For example:
|
||||||
|
|
||||||
|
- `obsidian-flavored-markdown` is both a **transformer** (processes OFM syntax) and provides **components** (mermaid rendering)
|
||||||
|
- `canvas-page` is a **page type** that also provides a custom **frame**
|
||||||
|
- A plugin could be a **transformer** that adds metadata AND a **component** that displays it
|
||||||
|
|
||||||
|
### Transformers
|
||||||
|
|
||||||
|
Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type QuartzTransformerPluginInstance = {
|
||||||
|
name: string
|
||||||
|
textTransform?: (ctx: BuildCtx, src: string) => string
|
||||||
|
markdownPlugins?: (ctx: BuildCtx) => PluggableList
|
||||||
|
htmlPlugins?: (ctx: BuildCtx) => PluggableList
|
||||||
|
externalResources?: (ctx: BuildCtx) => Partial<StaticResources>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All transformer plugins must define at least a `name` field to register the plugin and a few optional functions that allow you to hook into various parts of transforming a single Markdown file.
|
||||||
|
|
||||||
|
- `textTransform` performs a text-to-text transformation _before_ a file is parsed into the [Markdown AST](https://github.com/syntax-tree/mdast).
|
||||||
|
- `markdownPlugins` defines a list of [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md). `remark` is a tool that transforms Markdown to Markdown in a structured way.
|
||||||
|
- `htmlPlugins` defines a list of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md). Similar to how `remark` works, `rehype` is a tool that transforms HTML to HTML in a structured way.
|
||||||
|
- `externalResources` defines any external resources the plugin may need to load on the client-side for it to work properly.
|
||||||
|
|
||||||
|
Normally for both `remark` and `rehype`, you can find existing plugins that you can use. If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library).
|
||||||
|
|
||||||
|
A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[plugins/Latex|Latex]] plugin:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import remarkMath from "remark-math"
|
||||||
|
import rehypeKatex from "rehype-katex"
|
||||||
|
import rehypeMathjax from "rehype-mathjax/svg"
|
||||||
|
import { QuartzTransformerPlugin } from "@quartz-community/types"
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
renderEngine: "katex" | "mathjax"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Latex: QuartzTransformerPlugin<Options> = (opts?: Options) => {
|
||||||
|
const engine = opts?.renderEngine ?? "katex"
|
||||||
|
return {
|
||||||
|
name: "Latex",
|
||||||
|
markdownPlugins() {
|
||||||
|
return [remarkMath]
|
||||||
|
},
|
||||||
|
htmlPlugins() {
|
||||||
|
if (engine === "katex") {
|
||||||
|
// if you need to pass options into a plugin, you
|
||||||
|
// can use a tuple of [plugin, options]
|
||||||
|
return [[rehypeKatex, { output: "html" }]]
|
||||||
|
} else {
|
||||||
|
return [rehypeMathjax]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
externalResources() {
|
||||||
|
if (engine === "katex") {
|
||||||
|
return {
|
||||||
|
css: [
|
||||||
|
{
|
||||||
|
// base css
|
||||||
|
content: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
js: [
|
||||||
|
{
|
||||||
|
// fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md
|
||||||
|
src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js",
|
||||||
|
loadTime: "afterDOMReady",
|
||||||
|
contentType: "external",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Another common thing that transformer plugins will do is parse a file and add extra data for that file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { QuartzTransformerPlugin } from "@quartz-community/types"
|
||||||
|
|
||||||
|
export const AddWordCount: QuartzTransformerPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: "AddWordCount",
|
||||||
|
markdownPlugins() {
|
||||||
|
return [
|
||||||
|
() => {
|
||||||
|
return (tree, file) => {
|
||||||
|
// tree is an `mdast` root element
|
||||||
|
// file is a `vfile`
|
||||||
|
const text = file.value
|
||||||
|
const words = text.split(" ").length
|
||||||
|
file.data.wordcount = words
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tell typescript about our custom data fields we are adding
|
||||||
|
// other plugins will then also be aware of this data field
|
||||||
|
declare module "vfile" {
|
||||||
|
interface DataMap {
|
||||||
|
wordcount: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, you can also perform transformations over Markdown or HTML ASTs using the `visit` function from the `unist-util-visit` package or the `findAndReplace` function from the `mdast-util-find-and-replace` package.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { visit } from "unist-util-visit"
|
||||||
|
import { findAndReplace } from "mdast-util-find-and-replace"
|
||||||
|
import { QuartzTransformerPlugin } from "@quartz-community/types"
|
||||||
|
import { Link } from "mdast"
|
||||||
|
|
||||||
|
export const TextTransforms: QuartzTransformerPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: "TextTransforms",
|
||||||
|
markdownPlugins() {
|
||||||
|
return [
|
||||||
|
() => {
|
||||||
|
return (tree, file) => {
|
||||||
|
// replace _text_ with the italics version
|
||||||
|
findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => {
|
||||||
|
// inner is the text inside of the () of the regex
|
||||||
|
const [inner] = capture
|
||||||
|
// return an mdast node
|
||||||
|
// https://github.com/syntax-tree/mdast
|
||||||
|
return {
|
||||||
|
type: "emphasis",
|
||||||
|
children: [{ type: "text", value: inner }],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove all links (replace with just the link content)
|
||||||
|
// match by 'type' field on an mdast node
|
||||||
|
// https://github.com/syntax-tree/mdast#link in this example
|
||||||
|
visit(tree, "link", (link: Link) => {
|
||||||
|
return {
|
||||||
|
type: "paragraph",
|
||||||
|
children: [{ type: "text", value: link.title }],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A parting word: transformer plugins are quite complex so don't worry if you don't get them right away. Take a look at the built in transformers and see how they operate over content to get a better sense for how to accomplish what you are trying to do.
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
|
||||||
|
Filters **filter** content, taking the output of all the transformers and determining what files to actually keep and what to discard.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
|
||||||
|
opts?: Options,
|
||||||
|
) => QuartzFilterPluginInstance
|
||||||
|
|
||||||
|
export type QuartzFilterPluginInstance = {
|
||||||
|
name: string
|
||||||
|
shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A filter plugin must define a `name` field and a `shouldPublish` function that takes in a piece of content that has been processed by all the transformers and returns a `true` or `false` depending on whether it should be passed to the emitter plugins or not.
|
||||||
|
|
||||||
|
For example, here is the built-in plugin for removing drafts:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { QuartzFilterPlugin } from "@quartz-community/types"
|
||||||
|
|
||||||
|
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
|
||||||
|
name: "RemoveDrafts",
|
||||||
|
shouldPublish(_ctx, [_tree, vfile]) {
|
||||||
|
// uses frontmatter parsed from transformers
|
||||||
|
const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false
|
||||||
|
return !draftFlag
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emitters
|
||||||
|
|
||||||
|
Emitters **reduce** over content, taking in a list of all the transformed and filtered content and creating output files.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
|
||||||
|
opts?: Options,
|
||||||
|
) => QuartzEmitterPluginInstance
|
||||||
|
|
||||||
|
export type QuartzEmitterPluginInstance = {
|
||||||
|
name: string
|
||||||
|
emit(
|
||||||
|
ctx: BuildCtx,
|
||||||
|
content: ProcessedContent[],
|
||||||
|
resources: StaticResources,
|
||||||
|
): Promise<FilePath[]> | AsyncGenerator<FilePath>
|
||||||
|
partialEmit?(
|
||||||
|
ctx: BuildCtx,
|
||||||
|
content: ProcessedContent[],
|
||||||
|
resources: StaticResources,
|
||||||
|
changeEvents: ChangeEvent[],
|
||||||
|
): Promise<FilePath[]> | AsyncGenerator<FilePath> | null
|
||||||
|
getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. It can optionally implement a `partialEmit` function for incremental builds.
|
||||||
|
|
||||||
|
- `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created.
|
||||||
|
- `partialEmit` is an optional function that enables incremental builds. It receives information about which files have changed (`changeEvents`) and can selectively rebuild only the necessary files. This is useful for optimizing build times in development mode. If `partialEmit` is undefined, it will default to the `emit` function.
|
||||||
|
- `getQuartzComponents` declares which Quartz components the emitter uses to construct its pages.
|
||||||
|
|
||||||
|
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `@quartz-community/utils` if you are creating files that contain text. `write` has the following signature:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type WriteOptions = (data: {
|
||||||
|
// the build context
|
||||||
|
ctx: BuildCtx
|
||||||
|
// the name of the file to emit (not including the file extension)
|
||||||
|
slug: FullSlug
|
||||||
|
// the file extension
|
||||||
|
ext: `.${string}` | ""
|
||||||
|
// the file content to add
|
||||||
|
content: string
|
||||||
|
}) => Promise<FilePath>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a thin wrapper around writing to the appropriate output folder and ensuring that intermediate directories exist. If you choose to use the native Node `fs` APIs, ensure you emit to the `argv.output` folder as well.
|
||||||
|
|
||||||
|
If you are creating an emitter plugin that needs to render components, there are three more things to be aware of:
|
||||||
|
|
||||||
|
- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information.
|
||||||
|
- You can use the `renderPage` function defined in `@quartz-community/utils` to render Quartz components into HTML.
|
||||||
|
- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `@quartz-community/utils`.
|
||||||
|
|
||||||
|
For example, the following is a simplified version of the content page plugin that renders every single page.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { QuartzEmitterPlugin, FullPageLayout, QuartzComponentProps } from "@quartz-community/types"
|
||||||
|
import { renderPage, canonicalizeServer, pageResources, write } from "@quartz-community/utils"
|
||||||
|
|
||||||
|
export const ContentPage: QuartzEmitterPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: "ContentPage",
|
||||||
|
getQuartzComponents(ctx) {
|
||||||
|
const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = ctx.cfg.layout
|
||||||
|
return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer]
|
||||||
|
},
|
||||||
|
async emit(ctx, content, resources): Promise<FilePath[]> {
|
||||||
|
const cfg = ctx.cfg.configuration
|
||||||
|
const fps: FilePath[] = []
|
||||||
|
const allFiles = content.map((c) => c[1].data)
|
||||||
|
for (const [tree, file] of content) {
|
||||||
|
const slug = canonicalizeServer(file.data.slug!)
|
||||||
|
const externalResources = pageResources(slug, file.data, resources)
|
||||||
|
const componentData: QuartzComponentProps = {
|
||||||
|
fileData: file.data,
|
||||||
|
externalResources,
|
||||||
|
cfg,
|
||||||
|
children: [],
|
||||||
|
tree,
|
||||||
|
allFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = renderPage(cfg, slug, componentData, {}, externalResources)
|
||||||
|
const fp = await write({
|
||||||
|
ctx,
|
||||||
|
content,
|
||||||
|
slug: file.data.slug!,
|
||||||
|
ext: ".html",
|
||||||
|
})
|
||||||
|
|
||||||
|
fps.push(fp)
|
||||||
|
}
|
||||||
|
return fps
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Page types define how a category of pages is rendered. They are the primary way to add support for new file types or virtual pages in Quartz.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface QuartzPageTypePluginInstance {
|
||||||
|
name: string
|
||||||
|
priority?: number
|
||||||
|
fileExtensions?: string[]
|
||||||
|
match: PageMatcher
|
||||||
|
generate?: PageGenerator
|
||||||
|
layout: string
|
||||||
|
frame?: string
|
||||||
|
body: QuartzComponentConstructor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`: A unique identifier for this page type.
|
||||||
|
- `priority`: Controls matching order when multiple page types could match a slug. Higher priority page types are checked first. Default: `0`.
|
||||||
|
- `fileExtensions`: Array of file extensions this page type handles (e.g. `[".canvas"]`, `[".base"]`). Content files (`.md`) are handled by the default content page type.
|
||||||
|
- `match`: A function that determines whether a given slug/file should be rendered by this page type.
|
||||||
|
- `generate`: An optional function that produces virtual pages (pages not backed by files on disk, such as folder listings or tag indices).
|
||||||
|
- `layout`: The layout configuration key (e.g. `"content"`, `"folder"`, `"tag"`). This determines which `byPageType` entry in `quartz.config.yaml` provides the layout overrides for this page type.
|
||||||
|
- `frame`: The [[layout#Page Frames|page frame]] to use for this page type. Controls the overall HTML structure (e.g. `"default"`, `"full-width"`, `"minimal"`, or a custom frame provided by your plugin). If not set, defaults to `"default"`. Can be overridden per-page-type via `layout.byPageType.<name>.template` in `quartz.config.yaml`.
|
||||||
|
- `body`: The Quartz component constructor that renders the page body content.
|
||||||
|
|
||||||
|
### Providing Custom Frames
|
||||||
|
|
||||||
|
Plugins can ship their own [[layout#Page Frames|page frames]] — custom page layouts that control how the HTML structure (sidebars, header, content area, footer) is arranged. This is useful for page types that need fundamentally different layouts (e.g. a fullscreen canvas, a presentation mode, a dashboard).
|
||||||
|
|
||||||
|
To provide a custom frame:
|
||||||
|
|
||||||
|
**1. Create the frame file:**
|
||||||
|
|
||||||
|
```tsx title="src/frames/MyFrame.tsx"
|
||||||
|
import type { PageFrame, PageFrameProps } from "@quartz-community/types"
|
||||||
|
import type { ComponentChildren } from "preact"
|
||||||
|
|
||||||
|
export const MyFrame: PageFrame = {
|
||||||
|
name: "my-frame",
|
||||||
|
css: `
|
||||||
|
.page[data-frame="my-frame"] > #quartz-body {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas: "center";
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
render({ componentData, pageBody: Content, footer: Footer }: PageFrameProps): unknown {
|
||||||
|
const renderSlot = (C: (props: typeof componentData) => unknown): ComponentChildren =>
|
||||||
|
C(componentData) as ComponentChildren
|
||||||
|
return (
|
||||||
|
<div class="center">
|
||||||
|
{(Content as any)(componentData)}
|
||||||
|
{(Footer as any)(componentData)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key requirements:
|
||||||
|
|
||||||
|
- `name`: A unique string identifier. This is what page types and YAML config reference.
|
||||||
|
- `render()`: Receives all layout slots (header, sidebars, content, footer) and returns JSX for the inner page structure.
|
||||||
|
- `css` (optional): Frame-specific CSS. Scope it with `.page[data-frame="my-frame"]` selectors to avoid conflicts.
|
||||||
|
|
||||||
|
**2. Re-export the frame:**
|
||||||
|
|
||||||
|
```ts title="src/frames/index.ts"
|
||||||
|
export { MyFrame } from "./MyFrame"
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Declare the frame in `package.json`:**
|
||||||
|
|
||||||
|
```json title="package.json"
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"./frames": {
|
||||||
|
"import": "./dist/frames/index.js",
|
||||||
|
"types": "./dist/frames/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quartz": {
|
||||||
|
"frames": {
|
||||||
|
"MyFrame": { "exportName": "MyFrame" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"frames"` field in the `"quartz"` manifest maps export names to frame metadata. The key (e.g. `"MyFrame"`) must match the export name in `src/frames/index.ts`.
|
||||||
|
|
||||||
|
**4. Add the frame entry point to your build config:**
|
||||||
|
|
||||||
|
```ts title="tsup.config.ts"
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ["src/index.ts", "src/frames/index.ts"],
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Reference the frame in your page type:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const MyPageType: QuartzPageTypePlugin = () => ({
|
||||||
|
name: "MyPageType",
|
||||||
|
frame: "my-frame", // References the frame by its name property
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When a user installs your plugin, Quartz automatically loads the frame from the `./frames` export and registers it in the Frame Registry. The frame is then available by name in any page type or YAML config override.
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> See the [`canvas-page`](https://github.com/quartz-community/canvas-page) plugin for a complete real-world example of a plugin-provided frame.
|
||||||
|
|
||||||
|
### Bases Views
|
||||||
|
|
||||||
|
The `bases-page` plugin provides a database-like view system similar to Obsidian Bases. Other plugins can register custom view types via the `ViewRegistry`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { viewRegistry } from "@quartz-community/bases-page";
|
||||||
|
import type { ViewTypeRegistration } from "@quartz-community/bases-page";
|
||||||
|
|
||||||
|
viewRegistry.register({
|
||||||
|
id: "timeline",
|
||||||
|
name: "Timeline",
|
||||||
|
icon: "git-branch",
|
||||||
|
render: ({ entries, view, slug, allSlugs }) => (
|
||||||
|
<div class="bases-timeline">
|
||||||
|
{entries.map(entry => <div>{entry.properties.title}</div>)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
css: `.bases-timeline { display: flex; flex-direction: column; }`,
|
||||||
|
afterDOMLoaded: `document.addEventListener("nav", () => { /* setup */ })`,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Each view registration includes:
|
||||||
|
|
||||||
|
- `id`: Unique identifier (e.g., `"timeline"`, `"kanban"`)
|
||||||
|
- `name`: Display name shown in the view selector
|
||||||
|
- `icon`: Optional Lucide icon name
|
||||||
|
- `render`: Function that receives `ViewRendererProps` and returns Preact JSX
|
||||||
|
- `css`: Optional CSS string (deduplicated by view ID)
|
||||||
|
- `afterDOMLoaded`: Optional client-side script (same lifecycle as component scripts)
|
||||||
|
- `options`: Optional configuration passed to every render invocation
|
||||||
|
|
||||||
|
The `ViewRegistry` is a global singleton (via `Symbol.for`) ensuring all copies of the module share the same registry.
|
||||||
|
|
||||||
|
## Building and Distribution
|
||||||
|
|
||||||
|
Quartz v5 plugins ship pre-built `dist/` in their repositories. When a user installs your plugin, Quartz detects the pre-built output and skips the install/build cycle entirely — making installation near-instant.
|
||||||
|
|
||||||
|
### Build Configuration
|
||||||
|
|
||||||
|
The plugin template's `tsup.config.ts` bundles all dependencies by default. Only **singleton externals** — packages that must be the same instance across all plugins — are left unbundled:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const SINGLETON_EXTERNALS = [
|
||||||
|
"preact",
|
||||||
|
"preact/hooks",
|
||||||
|
"preact/jsx-runtime",
|
||||||
|
"preact/compat",
|
||||||
|
"@jackyzha0/quartz",
|
||||||
|
"@jackyzha0/quartz/*",
|
||||||
|
"vfile",
|
||||||
|
"vfile/*",
|
||||||
|
"unified",
|
||||||
|
]
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ...
|
||||||
|
noExternal: [/.*/], // Bundle everything
|
||||||
|
external: SINGLETON_EXTERNALS, // Except singletons
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This means your plugin's `dist/index.js` is self-contained — no `npm install` needed at install time.
|
||||||
|
|
||||||
|
### Shipping Pre-built Output
|
||||||
|
|
||||||
|
Your plugin's `dist/` directory should be committed to the repository:
|
||||||
|
|
||||||
|
1. **Do NOT add `dist/` to `.gitignore`**
|
||||||
|
2. Run `npm run build` before committing
|
||||||
|
3. The CI workflow verifies `dist/` is up to date on every push
|
||||||
|
|
||||||
|
If `dist/` is missing or gitignored, Quartz falls back to the full install/build cycle (useful during local development with symlinked plugins).
|
||||||
|
|
||||||
|
### Plugins with Native Dependencies
|
||||||
|
|
||||||
|
Plugins that require native packages (e.g. `sharp` for image processing) cannot bundle those. For these plugins:
|
||||||
|
|
||||||
|
1. Set `"requiresInstall": true` in your `package.json` quartz manifest
|
||||||
|
2. Declare the native package as a `peerDependency`
|
||||||
|
3. Quartz will install it into the host project at build time
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Build the plugin
|
||||||
|
npm run build
|
||||||
|
# or
|
||||||
|
npx tsup
|
||||||
|
```
|
||||||
|
|
||||||
|
## What to Import from Where
|
||||||
|
|
||||||
|
| You need... | Import from |
|
||||||
|
| ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||||
|
| Type definitions (`QuartzTransformerPlugin`, `QuartzComponent`, etc.) | `@quartz-community/types` |
|
||||||
|
| Path utilities (`simplifySlug`, `resolveRelative`, `pathToRoot`) | `@quartz-community/utils/path` |
|
||||||
|
| DOM utilities (`removeAllChildren`, `registerEscapeHandler`) | `@quartz-community/utils/dom` |
|
||||||
|
| JSX conversion (`htmlToJsx`) | `@quartz-community/utils/jsx` |
|
||||||
|
| Language utilities (`classNames`, `capitalize`) | `@quartz-community/utils/lang` |
|
||||||
|
| Date/sort utilities (`formatDate`, `getDate`, `byDateAndAlphabetical`) | `@quartz-community/utils/date` and `@quartz-community/utils/sort` |
|
||||||
|
| HTML escaping (`escapeHTML`, `unescapeHTML`) | `@quartz-community/utils/escape` |
|
||||||
|
| Emoji utilities (`getIconCode`) | `@quartz-community/utils/emoji` |
|
||||||
|
| Browser runtime (`onNav`, `onRender`, `fetchContentIndex`) | `@quartz-community/runtime` |
|
||||||
|
|
||||||
|
Do **not** import from `@jackyzha0/quartz` or from `vfile` directly. Use the community packages instead.
|
||||||
|
|
||||||
|
## Internationalization (i18n)
|
||||||
|
|
||||||
|
Plugins should provide their own translations for user-facing strings. Do **not** hardcode strings in components.
|
||||||
|
|
||||||
|
### Setting Up i18n
|
||||||
|
|
||||||
|
Create the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/i18n/
|
||||||
|
├── index.ts
|
||||||
|
└── locales/
|
||||||
|
└── en-US.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src/i18n/locales/en-US.ts`** (required base locale):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
myPlugin: {
|
||||||
|
title: "My Plugin",
|
||||||
|
description: "A description",
|
||||||
|
itemCount: ({ count }: { count: number }) => (count === 1 ? "1 item" : `${count} items`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src/i18n/index.ts`**:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import enUS from "./locales/en-US"
|
||||||
|
|
||||||
|
const locales: Record<string, typeof enUS> = {
|
||||||
|
"en-US": enUS,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function i18n(locale: string) {
|
||||||
|
return locales[locale] || enUS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using i18n in Components
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { i18n } from "../i18n"
|
||||||
|
|
||||||
|
const MyComponent: QuartzComponent = ({ cfg }) => {
|
||||||
|
const locale = cfg.locale ?? "en-US"
|
||||||
|
const t = i18n(locale).components.myPlugin
|
||||||
|
return <h2>{t.title}</h2>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Translations
|
||||||
|
|
||||||
|
To add a new locale, copy `en-US.ts`, translate the strings, and register it:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/i18n/locales/fr-FR.ts
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
myPlugin: {
|
||||||
|
title: "Mon Plugin",
|
||||||
|
description: "Une description",
|
||||||
|
itemCount: ({ count }: { count: number }) =>
|
||||||
|
count === 1 ? "1 élément" : `${count} éléments`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/i18n/index.ts
|
||||||
|
import enUS from "./locales/en-US"
|
||||||
|
import frFR from "./locales/fr-FR"
|
||||||
|
|
||||||
|
const locales: Record<string, typeof enUS> = {
|
||||||
|
"en-US": enUS,
|
||||||
|
"fr-FR": frFR,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag) locale codes (e.g., `en-US`, `de-DE`, `ja-JP`, `zh-CN`). For dynamic content, use function-based translations as shown with `itemCount` above.
|
||||||
|
|
||||||
|
## Installing Your Plugin
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# In your Quartz project
|
||||||
|
npx quartz plugin add github:your-username/my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
This clones the plugin and adds it to both `quartz.config.yaml` and `quartz.lock.json`. If the plugin ships pre-built `dist/` (recommended), installation completes in seconds with no build step. You can then configure it in your config:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:your-username/my-plugin
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
For options that require JavaScript callback functions (not expressible in YAML), use the TS override in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
|
||||||
|
// Must be placed before loadQuartzConfig()
|
||||||
|
ExternalPlugin.MyPlugin({
|
||||||
|
customFn: (data) => {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Options set via `quartz.ts` are merged with YAML options at instantiation time, with `quartz.ts` overrides taking precedence. These calls must be placed **before** `loadQuartzConfig()` in your `quartz.ts`.
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
During plugin development, you'll frequently install and uninstall your plugin to test changes. The following commands help manage this cycle:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Remove your plugin and clean up
|
||||||
|
npx quartz plugin remove my-plugin
|
||||||
|
|
||||||
|
# Re-add after making changes
|
||||||
|
npx quartz plugin add github:your-username/my-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
If you've updated your `quartz.config.yaml` to reference a plugin that isn't installed yet, you can install it without manually running `add`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Install all config-referenced plugins missing from the lockfile
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
|
||||||
|
# Preview first without making changes
|
||||||
|
npx quartz plugin install --from-config --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
To clean up plugins that are installed but no longer referenced in your config:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Remove orphaned plugins
|
||||||
|
npx quartz plugin prune
|
||||||
|
|
||||||
|
# Preview first without making changes
|
||||||
|
npx quartz plugin prune --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> Both `resolve` and `prune` fall back to `quartz.config.default.yaml` if no `quartz.config.yaml` is present. This is useful for CI environments where the default config is the source of truth. See [[cli/plugin#prune|prune]] and [[cli/plugin#resolve|resolve]] for full details.
|
||||||
|
|
||||||
|
## Component Plugins
|
||||||
|
|
||||||
|
For plugins that provide visual components (like Explorer, Graph, Search), see the [[creating components|creating component plugins]] guide.
|
||||||
|
|
||||||
|
Component-only plugins (those with `"category": ["component"]` in their manifest) are loaded via side-effect import rather than a factory function. If your component-only plugin needs to receive user options from `quartz.config.yaml`, export an `init(options)` function — see [[creating components#Receiving YAML Options in Component-Only Plugins|receiving YAML options]] for details.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: Paths in Quartz
|
||||||
|
---
|
||||||
|
|
||||||
|
Paths are pretty complex to reason about because, especially for a static site generator, they can come from so many places.
|
||||||
|
|
||||||
|
A full file path to a piece of content? Also a path. What about a slug for a piece of content? Yet another path.
|
||||||
|
|
||||||
|
It would be silly to type these all as `string` and call it a day as it's pretty common to accidentally mistake one type of path for another. Unfortunately, TypeScript does not have [nominal types](https://en.wikipedia.org/wiki/Nominal_type_system) for type aliases meaning even if you made custom types of a server-side slug or a client-slug slug, you can still accidentally assign one to another and TypeScript wouldn't catch it.
|
||||||
|
|
||||||
|
Luckily, we can mimic nominal typing using [brands](https://www.typescriptlang.org/play#example/nominal-typing).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// instead of
|
||||||
|
type FullSlug = string
|
||||||
|
|
||||||
|
// we do
|
||||||
|
type FullSlug = string & { __brand: "full" }
|
||||||
|
|
||||||
|
// that way, the following will fail typechecking
|
||||||
|
const slug: FullSlug = "some random string"
|
||||||
|
```
|
||||||
|
|
||||||
|
While this prevents most typing mistakes _within_ our nominal typing system (e.g. mistaking a server slug for a client slug), it doesn't prevent us from _accidentally_ mistaking a string for a client slug when we forcibly cast it.
|
||||||
|
|
||||||
|
Thus, we still need to be careful when casting from a string to one of these nominal types in the 'entrypoints', illustrated with hexagon shapes in the diagram below.
|
||||||
|
|
||||||
|
The following diagram draws the relationships between all the path sources, nominal path types, and what functions in `quartz/path.ts` convert between them.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Browser{{Browser}} --> Window{{Body}} & LinkElement{{Link Element}}
|
||||||
|
Window --"getFullSlug()"--> FullSlug[Full Slug]
|
||||||
|
LinkElement --".href"--> Relative[Relative URL]
|
||||||
|
FullSlug --"simplifySlug()" --> SimpleSlug[Simple Slug]
|
||||||
|
SimpleSlug --"pathToRoot()"--> Relative
|
||||||
|
SimpleSlug --"resolveRelative()" --> Relative
|
||||||
|
MD{{Markdown File}} --> FilePath{{File Path}} & Links[Markdown links]
|
||||||
|
Links --"transformLink()"--> Relative
|
||||||
|
FilePath --"slugifyFilePath()"--> FullSlug[Full Slug]
|
||||||
|
style FullSlug stroke-width:4px
|
||||||
|
```
|
||||||
|
|
||||||
|
Here are the main types of slugs with a rough description of each type of path:
|
||||||
|
|
||||||
|
- `FilePath`: a real file path to a file on disk. Cannot be relative and must have a file extension.
|
||||||
|
- `FullSlug`: cannot be relative and may not have leading or trailing slashes. It can have `index` as it's last segment. Use this wherever possible is it's the most 'general' interpretation of a slug.
|
||||||
|
- `SimpleSlug`: cannot be relative and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path.
|
||||||
|
- `RelativeURL`: must start with `.` or `..` to indicate it's a relative URL. Shouldn't have `/index` as an ending or a file extension but can contain a trailing slash.
|
||||||
|
|
||||||
|
To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/util/path.test.ts`.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
title: quartz build
|
||||||
|
aliases:
|
||||||
|
- build
|
||||||
|
---
|
||||||
|
|
||||||
|
The `build` command transforms your Markdown content into a static HTML website. It processes your files through the configured plugins and outputs the final site to a directory of your choice.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Shorthand | Description | Default |
|
||||||
|
| ----------------- | --------- | --------------------------------------------------------- | ----------------- |
|
||||||
|
| `--directory` | `-d` | The directory containing your Quartz project | Current directory |
|
||||||
|
| `--verbose` | `-v` | Enable detailed logging for debugging | `false` |
|
||||||
|
| `--output` | `-o` | The directory where the built site will be saved | `public` |
|
||||||
|
| `--serve` | | Start a local development server | `false` |
|
||||||
|
| `--watch` | | Rebuild the site when files change | `false` |
|
||||||
|
| `--port` | | The port for the development server | `8080` |
|
||||||
|
| `--wsPort` | | The port for the WebSocket hot-reload server | `3001` |
|
||||||
|
| `--baseDir` | | Set a base directory for the site (e.g. for GitHub Pages) | `/` |
|
||||||
|
| `--remoteDevHost` | | The hostname to use for the development server | `localhost` |
|
||||||
|
| `--bundleInfo` | | Output a JSON file with bundle size information | `false` |
|
||||||
|
| `--concurrency` | `-c` | Number of worker threads to use for building | CPU core count |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic Build
|
||||||
|
|
||||||
|
Generate your site into the `public` folder.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Mode
|
||||||
|
|
||||||
|
Start a local server and watch for changes. This is the most common way to preview your site while writing.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz build --serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Output and Port
|
||||||
|
|
||||||
|
Build to a specific folder and run the server on a different port.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz build --serve --output dist --port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Tuning
|
||||||
|
|
||||||
|
If you have a very large vault, you can limit the number of concurrent workers to save memory.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz build --concurrency 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Serve vs Watch
|
||||||
|
|
||||||
|
The `--serve` and `--watch` flags control different behaviors:
|
||||||
|
|
||||||
|
- **`--serve`** starts a local development server AND automatically watches for changes (implies `--watch`). This is the recommended mode for local development.
|
||||||
|
- **`--watch`** only watches for file changes and rebuilds automatically, without starting a server. This is useful for CI pipelines or custom server setups where you want automatic rebuilds but handle serving separately.
|
||||||
|
|
||||||
|
In most cases, you want `--serve`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz build --serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
The `--serve` flag starts a local web server. This server is intended for development and previewing only. It is not designed for production use. For information on how to deploy your site, see [[hosting]].
|
||||||
|
|
||||||
|
### Hot Reloading
|
||||||
|
|
||||||
|
When running with `--serve`, Quartz automatically enables `--watch`. It uses a WebSocket connection (on the port specified by `--wsPort`) to notify your browser when a file has changed. The browser will then automatically refresh to show the latest version of your content.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
title: quartz create
|
||||||
|
---
|
||||||
|
|
||||||
|
The `create` command initializes a new Quartz project. It helps you set up your content folder, choose a configuration template, set your site's base URL, and configure how Quartz should handle your Markdown files.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Shorthand | Description |
|
||||||
|
| ------------- | --------- | --------------------------------------------------------------------- |
|
||||||
|
| `--template` | `-t` | Configuration template (`default`, `obsidian`, `ttrpg`, or `blog`) |
|
||||||
|
| `--directory` | `-d` | The directory where Quartz will be initialized |
|
||||||
|
| `--source` | `-s` | The source directory of your Markdown files |
|
||||||
|
| `--strategy` | `-X` | How to handle the source files (`new`, `copy`, or `symlink`) |
|
||||||
|
| `--links` | `-l` | How to resolve internal links (`absolute`, `shortest`, or `relative`) |
|
||||||
|
| `--baseUrl` | `-b` | Base URL for your site (e.g. `mysite.github.io/quartz`) |
|
||||||
|
| `--verbose` | `-v` | Enable detailed logging |
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
When you run `quartz create`, you can choose a configuration template that pre-configures Quartz for your use case. The selected template always overwrites `quartz.config.yaml`, even if one already exists. After applying the template, Quartz automatically runs plugin resolution to install any plugins the template requires and remove any that are no longer referenced.
|
||||||
|
|
||||||
|
- **Default**: A clean Quartz setup with sensible defaults. Best for starting from scratch.
|
||||||
|
- **Obsidian**: Optimized for Obsidian vaults with full Obsidian Flavored Markdown support (wikilinks, callouts, mermaid diagrams, etc.). Automatically sets link resolution to `shortest` and skips the link resolution prompt.
|
||||||
|
- **TTRPG**: Builds on the Obsidian template with the addition of the [Leaflet bases plugin](https://github.com/Requiae/quartz-leaflet-bases-plugin) and [ITS Theme](https://github.com/saberzero1/quartz-themes) (`its-theme.ttrpg-dnd`). Great for D&D and TTRPG wikis. Also skips the link resolution prompt.
|
||||||
|
- **Blog**: A blog-focused setup with [recent notes](https://github.com/quartz-community/recent-notes) enabled (showing the 5 most recent posts with tags) and [comments](https://github.com/quartz-community/comments) enabled via giscus. You'll need to fill in the `TODO:` placeholder values in `quartz.config.yaml` with your own giscus repository details.
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
During setup, Quartz will ask for the base URL of your site. This is the URL where your site will be deployed (e.g. `mysite.github.io/quartz`).
|
||||||
|
|
||||||
|
- Do **not** include the protocol (`https://`) — if you do, it will be automatically stripped.
|
||||||
|
- Trailing slashes are also removed automatically.
|
||||||
|
- See [[configuration]] for more details on how `baseUrl` is used.
|
||||||
|
|
||||||
|
## Strategies
|
||||||
|
|
||||||
|
When you run `quartz create`, you must choose a strategy for your content:
|
||||||
|
|
||||||
|
- **new**: Creates a fresh, empty content folder. Use this if you are starting a new project from scratch.
|
||||||
|
- **copy**: Copies all files from your source directory into the Quartz content folder. This is the safest option for existing vaults as it doesn't touch your original files.
|
||||||
|
- **symlink**: Creates a symbolic link from the Quartz content folder to your source directory. Any changes you make in your source directory (e.g. in Obsidian) will be immediately reflected in Quartz.
|
||||||
|
|
||||||
|
## Link Resolution
|
||||||
|
|
||||||
|
Quartz needs to know how to interpret the internal links in your Markdown files:
|
||||||
|
|
||||||
|
- **shortest**: Resolves links to the closest matching file name. This is the default for Obsidian.
|
||||||
|
- **absolute**: Resolves links relative to the root of your content folder.
|
||||||
|
- **relative**: Resolves links relative to the current file's location.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> When using the **Obsidian** or **TTRPG** templates, link resolution is automatically set to `shortest` and the prompt is skipped.
|
||||||
|
|
||||||
|
## Interactive Walkthrough
|
||||||
|
|
||||||
|
If you run `npx quartz create` without any arguments, it will guide you through an interactive setup:
|
||||||
|
|
||||||
|
1. **Choose a template**: Select a configuration template (`Default`, `Obsidian`, `TTRPG`, or `Blog`).
|
||||||
|
2. **Select a strategy**: Choose between `new`, `copy`, or `symlink`.
|
||||||
|
3. **Enter base URL**: Provide the URL where your site will be hosted.
|
||||||
|
4. **Select link resolution**: Choose how your links are formatted (skipped for Obsidian and TTRPG templates).
|
||||||
|
5. **Finish**: Quartz will set up the directory structure, create your configuration, and automatically install any plugins referenced in the template.
|
||||||
|
|
||||||
|
## Example: Importing an Obsidian Vault
|
||||||
|
|
||||||
|
To create a Quartz project that links directly to an existing Obsidian vault:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz create --template obsidian --strategy symlink --source ~/Documents/MyVault
|
||||||
|
```
|
||||||
|
|
||||||
|
This command tells Quartz to use the Obsidian template (with full OFM support and shortest link resolution), look at your vault in `~/Documents/MyVault`, and use symbolic links so changes are synced.
|
||||||
|
|
||||||
|
## Example: Setting Up a Blog
|
||||||
|
|
||||||
|
To quickly set up a blog with recent notes and comments:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz create --template blog --strategy new --baseUrl myblog.github.io
|
||||||
|
```
|
||||||
|
|
||||||
|
After setup, edit `quartz.config.yaml` to fill in your giscus repository details in the comments plugin section.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: CLI Reference
|
||||||
|
---
|
||||||
|
|
||||||
|
The Quartz CLI is the primary way to interact with your Quartz project. It provides commands for creating new projects, building static sites, syncing with GitHub, and managing plugins.
|
||||||
|
|
||||||
|
You can run the CLI using `npx quartz`.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Command | Description | Example |
|
||||||
|
| --------- | ------------------------------------------------------- | ------------------------ |
|
||||||
|
| `create` | Initialize a new Quartz project with template selection | `npx quartz create` |
|
||||||
|
| `build` | Generate static HTML files | `npx quartz build` |
|
||||||
|
| `sync` | Sync content with GitHub | `npx quartz sync` |
|
||||||
|
| `upgrade` | Upgrade Quartz to the latest version (alias: `update`) | `npx quartz upgrade` |
|
||||||
|
| `plugin` | Manage Quartz plugins (install, add, remove, etc.) | `npx quartz plugin list` |
|
||||||
|
| `tui` | Launch the interactive plugin manager | `npx quartz tui` |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- [[create|create]]: Initialize a new Quartz project with a choice of templates (default, obsidian, ttrpg, blog) and base URL configuration.
|
||||||
|
- [[build|build]]: Build your Quartz site into static HTML. Includes a development server.
|
||||||
|
- [[sync|sync]]: Push and pull changes between your local machine and GitHub.
|
||||||
|
- [[upgrade|upgrade]]: Upgrade the Quartz framework to the latest version. Also available as `npx quartz update`.
|
||||||
|
- [[restore|restore]]: Recover your content folder from the local cache.
|
||||||
|
- [[cli/plugin|plugin]]: Install, add, remove, prune, and configure plugins. Use `plugin install` with flags for lockfile/config sync, updates, and checks.
|
||||||
|
- [[tui|tui]]: Use a terminal interface to manage plugins and layout.
|
||||||
|
|
||||||
|
## Global Flags
|
||||||
|
|
||||||
|
These flags are accepted by every Quartz command:
|
||||||
|
|
||||||
|
| Flag | Shorthand | Description | Default |
|
||||||
|
| --------------- | --------- | ------------------------------------------------------------------------------------------------------- | -------------- |
|
||||||
|
| `--directory` | `-d` | The directory containing your Quartz project | `content` |
|
||||||
|
| `--verbose` | `-v` | Enable detailed logging for debugging | `false` |
|
||||||
|
| `--concurrency` | `-c` | Max parallel workers for operations that run in parallel (e.g. `build`, `plugin install`, `plugin add`) | CPU core count |
|
||||||
|
|
||||||
|
Commands that don't perform parallel work accept `-c` as a no-op, so it's always safe to pass. See [[build#Performance Tuning|build]] and [[cli/plugin#Installing on low-end hardware|plugin]] for practical examples.
|
||||||
|
|
||||||
|
## Help and Versioning
|
||||||
|
|
||||||
|
To see a full list of available flags for any command, use the `--help` flag.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz --help
|
||||||
|
npx quartz build --help
|
||||||
|
```
|
||||||
|
|
||||||
|
To check which version of Quartz you are currently running, use the `--version` flag.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz --version
|
||||||
|
```
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
---
|
||||||
|
title: quartz plugin
|
||||||
|
---
|
||||||
|
|
||||||
|
The `plugin` command is the heart of the Quartz v5 plugin management system. it allows you to install, configure, and update plugins directly from the command line.
|
||||||
|
|
||||||
|
All plugins are stored in the `.quartz/plugins/` directory, and their versions are tracked in `quartz.lock.json`.
|
||||||
|
|
||||||
|
## Subcommands
|
||||||
|
|
||||||
|
### list
|
||||||
|
|
||||||
|
List all currently installed plugins and their versions.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin list
|
||||||
|
```
|
||||||
|
|
||||||
|
### add
|
||||||
|
|
||||||
|
Add a new plugin from a Git repository.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:username/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
To install from a specific branch or ref, append `#ref` to the source:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:username/repo#my-branch
|
||||||
|
npx quartz plugin add git+https://github.com/username/repo.git#my-branch
|
||||||
|
npx quartz plugin add https://github.com/username/repo.git#my-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also add a plugin from a local directory. This is useful for local development or airgapped environments:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add ./path/to/my-plugin
|
||||||
|
npx quartz plugin add ../sibling-plugin
|
||||||
|
npx quartz plugin add /absolute/path/to/plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
Local plugins are symlinked into `.quartz/plugins/`, so any changes you make to the source directory are reflected immediately without re-installing.
|
||||||
|
|
||||||
|
When a branch is specified, it is stored in the lockfile. All subsequent commands (`install`, `prune`) will respect that branch automatically. Use `install --latest` to fetch the latest commit from that branch.
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> `plugin add` also accepts `--concurrency` / `-c` to limit how many remote repositories are cloned and built at the same time. This is the same flag documented under [[#install]] and is useful when adding several plugins at once on low-end hardware.
|
||||||
|
|
||||||
|
### remove
|
||||||
|
|
||||||
|
Remove an installed plugin.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin remove plugin-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### install
|
||||||
|
|
||||||
|
Install plugins for your Quartz project. By default, this installs all plugins listed in your `quartz.lock.json` file.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
|
||||||
|
- `--from-config`: Synchronize plugins with `quartz.config.yaml` instead of the lockfile. This will install missing plugins and prune orphaned ones.
|
||||||
|
- `--latest`: Fetch the latest version of plugins from their remote sources instead of using the version in the lockfile.
|
||||||
|
- `--clean`: Skip existing directories and perform a fresh installation.
|
||||||
|
- `--dry-run`: Preview the changes without actually installing or removing any files.
|
||||||
|
- `--concurrency`, `-c`: Maximum number of plugins to clone, fetch, and build in parallel. Defaults to the number of CPU cores. Lower this (e.g. `-c 1` or `-c 2`) on memory- or CPU-constrained machines where the default parallelism causes failures, OOMs, or hangs. See [[#Installing on low-end hardware]] below.
|
||||||
|
|
||||||
|
#### Positional Arguments
|
||||||
|
|
||||||
|
- `[names..]`: Optional list of specific plugin names to install or update.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Update specific plugins to latest
|
||||||
|
npx quartz plugin install --latest plugin-a plugin-b
|
||||||
|
|
||||||
|
# Preview what would be installed from config
|
||||||
|
npx quartz plugin install --from-config --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### enable / disable
|
||||||
|
|
||||||
|
Toggle a plugin's status in your `quartz.config.yaml` without removing its files.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin enable plugin-name
|
||||||
|
npx quartz plugin disable plugin-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### config
|
||||||
|
|
||||||
|
View or modify the configuration for a specific plugin.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# View config
|
||||||
|
npx quartz plugin config plugin-name
|
||||||
|
|
||||||
|
# Set a value
|
||||||
|
npx quartz plugin config plugin-name --set key=value
|
||||||
|
```
|
||||||
|
|
||||||
|
### prune
|
||||||
|
|
||||||
|
Remove installed plugins that are no longer referenced in your `quartz.config.yaml`. This is useful for cleaning up after removing plugin entries from your configuration.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Running `plugin install --from-config` also removes orphaned plugins as part of its synchronization. Use `prune` when you only want to clean up without installing anything new.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin prune
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--dry-run` to preview which plugins would be removed without making changes:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin prune --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Adding and Enabling a Plugin
|
||||||
|
|
||||||
|
To add a new plugin and start using it:
|
||||||
|
|
||||||
|
1. Add the plugin: `npx quartz plugin add github:quartz-community/example`
|
||||||
|
2. Enable it: `npx quartz plugin enable example`
|
||||||
|
|
||||||
|
### Updating Everything
|
||||||
|
|
||||||
|
To keep your plugins fresh:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin install --latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing on low-end hardware
|
||||||
|
|
||||||
|
By default, `plugin install` and `plugin add` clone, fetch, and build plugins in parallel across all your CPU cores. On memory-constrained machines (low-end laptops, Raspberry Pi, small VPS instances, restrictive CI runners) this can exhaust RAM or overwhelm the system because each worker may kick off its own `npm install` / `npm run build` at the same time.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Most community plugins now ship with a pre-built `dist/` directory. When Quartz finds this, it skips the installation and build steps entirely, making the process much faster and lighter on resources. This section is primarily relevant for plugins in development or those that don't provide pre-built distribution.
|
||||||
|
|
||||||
|
If `plugin install` fails, hangs, or OOMs on your machine, lower the concurrency with `--concurrency` / `-c`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Install one plugin at a time (safest, slowest)
|
||||||
|
npx quartz plugin install --latest -c 1
|
||||||
|
|
||||||
|
# Two at a time — usually a good balance on 4 GB machines
|
||||||
|
npx quartz plugin install --latest --concurrency 2
|
||||||
|
```
|
||||||
|
|
||||||
|
The same flag works on `plugin add` and the other plugin subcommands that perform parallel work:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:quartz-community/some-plugin -c 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managing Configuration
|
||||||
|
|
||||||
|
If you want to change a plugin setting without opening the YAML file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin config explorer --set useSavedState=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleaning Up Unused Plugins
|
||||||
|
|
||||||
|
If you've removed plugins from your config and want to clean up leftover files:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin prune --dry-run # preview first
|
||||||
|
npx quartz plugin prune # remove orphaned plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Up from Config
|
||||||
|
|
||||||
|
When setting up on a new machine or in CI, `install --from-config` ensures your installed plugins match your config — installing missing plugins and removing any that are no longer referenced:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with Branches
|
||||||
|
|
||||||
|
If a plugin author has a fix or feature on a separate branch, you can install it directly without waiting for a release to the default branch:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Install from a feature branch
|
||||||
|
npx quartz plugin add github:username/repo#fix/some-bug
|
||||||
|
|
||||||
|
# Later, switch back to the default branch by re-adding without a ref
|
||||||
|
npx quartz plugin remove repo
|
||||||
|
npx quartz plugin add github:username/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
The branch ref is tracked in `quartz.lock.json`, so `install --latest` will continue to follow the specified branch until the plugin is re-added without one.
|
||||||
|
|
||||||
|
Both `prune` and `install --from-config` will fall back to `quartz.config.default.yaml` if no `quartz.config.yaml` is present.
|
||||||
|
|
||||||
|
### Local Plugin Development
|
||||||
|
|
||||||
|
For local plugin development or airgapped environments, you can add a plugin from a local directory:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add ./my-local-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
Local plugins are symlinked into `.quartz/plugins/`, so changes reflect immediately. When you run `install --latest`, local plugins are rebuilt (npm install + npm run build) without any git operations.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Local symlinked plugins typically use this build-on-install fallback because the `dist/` directory is usually gitignored during development.
|
||||||
|
|
||||||
|
The `install --latest --dry-run` command will show local plugins with a "local" status instead of checking for remote updates.
|
||||||
|
|
||||||
|
To switch a local plugin back to a git source:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin remove my-local-plugin
|
||||||
|
npx quartz plugin add github:username/my-local-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subdirectory (Monorepo) Plugins
|
||||||
|
|
||||||
|
Some plugins live in a subdirectory of a larger repository rather than at the root. For these, you can specify the plugin source as an object in `quartz.config.yaml` with a `subdir` field:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source:
|
||||||
|
repo: "https://github.com/username/monorepo.git"
|
||||||
|
subdir: plugin
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells Quartz to clone the full repository but install only the contents of the specified subdirectory.
|
||||||
|
|
||||||
|
You can combine `subdir` with `ref` to pin a branch or tag, and `name` to override the plugin directory name:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source:
|
||||||
|
repo: "https://github.com/username/monorepo.git"
|
||||||
|
subdir: packages/my-plugin
|
||||||
|
ref: v2.0
|
||||||
|
name: my-plugin
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
See [[configuration#Advanced Source Options|Advanced Source Options]] for the full reference on object source fields.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> The `plugin add` CLI command works with string sources. To use the object source format with `subdir`, edit `quartz.config.yaml` directly, then run `npx quartz plugin install --from-config` to install it.
|
||||||
|
|
||||||
|
## Migration from Deprecated Commands
|
||||||
|
|
||||||
|
| Old command | New equivalent |
|
||||||
|
| ------------------------------------- | --------------------------------------------------- |
|
||||||
|
| `npx quartz plugin restore` | `npx quartz plugin install --clean` |
|
||||||
|
| `npx quartz plugin update` | `npx quartz plugin install --latest` |
|
||||||
|
| `npx quartz plugin update my-plugin` | `npx quartz plugin install --latest my-plugin` |
|
||||||
|
| `npx quartz plugin check` | `npx quartz plugin install --latest --dry-run` |
|
||||||
|
| `npx quartz plugin resolve` | `npx quartz plugin install --from-config` |
|
||||||
|
| `npx quartz plugin resolve --dry-run` | `npx quartz plugin install --from-config --dry-run` |
|
||||||
|
| `npx quartz update` | `npx quartz plugin install --latest` |
|
||||||
|
|
||||||
|
The old commands still work as hidden aliases but will print a deprecation warning.
|
||||||
|
|
||||||
|
## Plugin Status
|
||||||
|
|
||||||
|
Running the plugin command without any subcommand shows a status dashboard of all installed plugins, including whether updates are available:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
This displays each plugin with its source, commit, enabled/disabled status, and checks for available updates in parallel. For the full interactive management interface, use [[tui|npx quartz tui]] instead.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: quartz restore
|
||||||
|
---
|
||||||
|
|
||||||
|
The `restore` command is a safety mechanism that allows you to recover your **content folder** from a local cache. This command only affects your Markdown files and does not restore plugins or configuration.
|
||||||
|
|
||||||
|
To restore plugins to a specific state, use [[cli/plugin|npx quartz plugin install]].
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
You should use `restore` if:
|
||||||
|
|
||||||
|
- A `quartz upgrade` failed and corrupted your content.
|
||||||
|
- You accidentally deleted files in your content folder.
|
||||||
|
- You encountered complex merge conflicts that you want to undo.
|
||||||
|
|
||||||
|
## How it Works
|
||||||
|
|
||||||
|
Quartz maintains a hidden cache of your content folder. Every time you run certain commands, Quartz ensures that a backup of your Markdown files exists. The `restore` command simply copies these files back into your main content directory.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz restore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
If an update fails and leaves your project in a broken state:
|
||||||
|
|
||||||
|
1. **Restore**: Run `npx quartz restore` to bring back your content.
|
||||||
|
2. **Clean**: Use Git to reset any other broken code files.
|
||||||
|
3. **Retry**: Attempt the update again or manually apply the changes you need.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: quartz sync
|
||||||
|
---
|
||||||
|
|
||||||
|
The `sync` command automates the process of pushing your local changes to GitHub and pulling updates from your remote repository. It simplifies the Git workflow for users who want to keep their site updated without running manual Git commands.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Shorthand | Description | Default |
|
||||||
|
| ------------- | --------- | ------------------------------------ | ----------------- |
|
||||||
|
| `--directory` | `-d` | The directory of your Quartz project | Current directory |
|
||||||
|
| `--verbose` | `-v` | Enable detailed logging | `false` |
|
||||||
|
| `--commit` | | Whether to commit changes | `true` |
|
||||||
|
| `--no-commit` | | Skip committing changes | `false` |
|
||||||
|
| `--message` | `-m` | Custom commit message | `update content` |
|
||||||
|
| `--push` | | Whether to push changes to remote | `true` |
|
||||||
|
| `--no-push` | | Skip pushing changes | `false` |
|
||||||
|
| `--pull` | | Whether to pull changes from remote | `true` |
|
||||||
|
| `--no-pull` | | Skip pulling changes | `false` |
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
When you run `npx quartz sync`, Quartz performs the following steps:
|
||||||
|
|
||||||
|
1. **Pull**: It fetches and merges changes from your remote GitHub repository.
|
||||||
|
2. **Add**: It stages all new and modified files in your project.
|
||||||
|
3. **Commit**: It creates a new commit with your changes.
|
||||||
|
4. **Push**: It sends your new commit to GitHub.
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Regular Sync
|
||||||
|
|
||||||
|
The most common usage is to simply run the command with no flags. This pulls, commits, and pushes everything.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### First Sync
|
||||||
|
|
||||||
|
If you have just set up a new repository and haven't pushed anything yet, you might want to skip the pull step.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz sync --no-pull
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Commit Message
|
||||||
|
|
||||||
|
You can provide a more descriptive message for your changes.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz sync --message "add new notes about gardening"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sync from Another Device
|
||||||
|
|
||||||
|
If you are working on a different computer and just want to get the latest changes without pushing anything back yet.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz sync --no-push --no-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Git Buffer
|
||||||
|
|
||||||
|
If you have a very large number of changes, Git might occasionally fail due to buffer limits. If this happens, try syncing smaller batches of files or increasing your Git post buffer size.
|
||||||
|
|
||||||
|
### Autostash
|
||||||
|
|
||||||
|
Quartz uses `git pull --rebase --autostash` internally. This means if you have unstaged changes when you run `sync`, Quartz will temporarily hide them, pull the remote changes, and then bring your changes back. If a conflict occurs during this process, you will need to resolve it manually using standard Git tools.
|
||||||
|
|
||||||
|
For more information on initial setup, see [[installation]].
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: quartz tui
|
||||||
|
---
|
||||||
|
|
||||||
|
The `tui` command launches an interactive terminal user interface for managing your Quartz project. It provides a visual way to manage plugins, arrange your site layout, and edit general settings.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To use the TUI, you must have the following:
|
||||||
|
|
||||||
|
1. **Bun**: The TUI requires the Bun runtime. You can find installation instructions at [bun.sh](https://bun.sh/docs/installation).
|
||||||
|
2. **TUI Plugin**: You must install the TUI plugin in your Quartz project.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Run the following command to add the TUI plugin:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:quartz-community/tui
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interface Panels
|
||||||
|
|
||||||
|
The TUI is divided into three main panels that you can navigate between.
|
||||||
|
|
||||||
|
### Plugins Panel
|
||||||
|
|
||||||
|
This panel allows you to browse all available and installed plugins. You can:
|
||||||
|
|
||||||
|
- Enable or disable plugins with a single keystroke.
|
||||||
|
- Configure plugin-specific settings.
|
||||||
|
- Install new plugins from the community or remove existing ones.
|
||||||
|
|
||||||
|
### Layout Panel
|
||||||
|
|
||||||
|
The Layout panel is where you define where components appear on your site. You can:
|
||||||
|
|
||||||
|
- Move components between different sections (e.g. `left`, `right`, `beforeBody`).
|
||||||
|
- Reorder components within a section to change their vertical stack.
|
||||||
|
- Set priorities for components to control their placement.
|
||||||
|
|
||||||
|
### Settings Panel
|
||||||
|
|
||||||
|
This panel provides a central place to edit your `quartz.config.yaml` settings. You can update:
|
||||||
|
|
||||||
|
- `pageTitle`
|
||||||
|
- Theme colors and fonts
|
||||||
|
- Analytics configuration
|
||||||
|
- Deployment settings
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
The TUI uses standard terminal navigation keys:
|
||||||
|
|
||||||
|
- **Arrow Keys**: Move between items and panels.
|
||||||
|
- **Enter**: Select an item or confirm a change.
|
||||||
|
- **Esc**: Go back or cancel an action.
|
||||||
|
- **Tab**: Cycle through different interface elements.
|
||||||
|
|
||||||
|
## Important Note
|
||||||
|
|
||||||
|
All changes made within the TUI are written directly to your `quartz.config.yaml` file. It is a good practice to have a clean Git state before using the TUI so you can easily review and undo any changes it makes.
|
||||||
|
|
||||||
|
For command-line based plugin management, see [[cli/plugin|quartz plugin]].
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: quartz upgrade
|
||||||
|
---
|
||||||
|
|
||||||
|
The `upgrade` command upgrades the Quartz framework itself to the latest version by pulling changes from the official Quartz repository.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
## How it Works
|
||||||
|
|
||||||
|
When you run `npx quartz upgrade`, Quartz performs the following steps:
|
||||||
|
|
||||||
|
1. **Backs up your content** — your content folder is cached locally to prevent data loss.
|
||||||
|
2. **Pulls the latest Quartz code** — fetches and merges from the official upstream repository (`upstream/v5`) using Git.
|
||||||
|
3. **Shows version changes** — displays the version transition (e.g., `v5.0.0 → v5.1.0`) or confirms you're already up to date.
|
||||||
|
4. **Updates dependencies** — runs `npm install` to ensure all packages match the new version.
|
||||||
|
5. **Restores plugins** — reinstalls plugins from `quartz.lock.json` to ensure compatibility.
|
||||||
|
6. **Checks plugin compatibility** — verifies that installed plugins are compatible with the new Quartz version.
|
||||||
|
|
||||||
|
## Handling Conflicts
|
||||||
|
|
||||||
|
Because Quartz allows you to customize almost every part of the code, upgrades can sometimes result in merge conflicts. This happens if you have modified a file that the Quartz team has also updated.
|
||||||
|
|
||||||
|
Quartz automatically handles merge conflicts in `quartz.lock.json` by backing up your lockfile before pulling and restoring it afterward. This prevents the most common source of conflicts during upgrades.
|
||||||
|
|
||||||
|
For other files, if a conflict occurs:
|
||||||
|
|
||||||
|
1. Git will mark the conflicting sections in the affected files.
|
||||||
|
2. You will need to open these files and manually choose which changes to keep.
|
||||||
|
3. After resolving the conflicts, you can commit the changes.
|
||||||
|
|
||||||
|
## Recovery
|
||||||
|
|
||||||
|
If an upgrade goes wrong or leaves your project in an unusable state, you can use the [[restore|restore]] command to recover your content from the local cache.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
The `upgrade` command supports the standard [[cli/index|common flags]] (`--directory`, `--verbose`).
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [[cli/plugin|quartz plugin install --latest]] — update installed plugins
|
||||||
|
- [[upgrading|Upgrading Quartz]] — detailed upgrading guide
|
||||||
|
- [[restore|quartz restore]] — recover content from cache
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Community
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz has a vibrant community of users and contributors. This page highlights community-created plugins, tools, and resources that extend Quartz.
|
||||||
|
|
||||||
|
> [!tip] Contributing
|
||||||
|
> Know of a great community resource? Submit a pull request to add it to this page!
|
||||||
|
|
||||||
|
## Community Plugins
|
||||||
|
|
||||||
|
Third-party plugins that extend Quartz functionality. Install them with the [[cli/plugin|plugin CLI]]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin add <github-url>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Add community plugins here as they become available -->
|
||||||
|
<!-- Format: - **[Plugin Name](link)** — Brief description -->
|
||||||
|
|
||||||
|
_No community plugins listed yet. Be the first to share yours!_
|
||||||
|
|
||||||
|
## Tools & Integrations
|
||||||
|
|
||||||
|
Tools, scripts, and integrations built by the community to work with Quartz.
|
||||||
|
|
||||||
|
<!-- Add tools here -->
|
||||||
|
<!-- Format: - **[Tool Name](link)** — Brief description -->
|
||||||
|
|
||||||
|
_No community tools listed yet._
|
||||||
|
|
||||||
|
## Templates & Themes
|
||||||
|
|
||||||
|
Custom themes, CSS snippets, and starter templates for Quartz sites.
|
||||||
|
|
||||||
|
<!-- Add templates/themes here -->
|
||||||
|
<!-- Format: - **[Name](link)** — Brief description -->
|
||||||
|
|
||||||
|
_No community templates listed yet._
|
||||||
|
|
||||||
|
## Guides & Tutorials
|
||||||
|
|
||||||
|
Community-written guides, blog posts, and tutorials about using Quartz.
|
||||||
|
|
||||||
|
<!-- Add guides here -->
|
||||||
|
<!-- Format: - **[Title](link)** by Author — Brief description -->
|
||||||
|
|
||||||
|
_No community guides listed yet._
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
Projects and tools in the digital garden / PKM ecosystem that pair well with Quartz.
|
||||||
|
|
||||||
|
- **[Obsidian](https://obsidian.md/)** — Knowledge base and note-taking app (recommended editor for Quartz content)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Looking to see sites built with Quartz? Check out the [[showcase|Quartz Showcase]].
|
||||||
|
|
||||||
|
Want to chat with other Quartz users? [Join the Discord community](https://discord.gg/cRFFHYye7t).
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
---
|
||||||
|
title: Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz is meant to be extremely configurable, even if you don't know any coding. Most of the configuration you should need can be done by just editing `quartz.config.yaml`.
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> If you edit `quartz.config.yaml` using a text-editor with YAML language support like VSCode, it will warn you when you've made an error in your configuration, helping you avoid configuration mistakes!
|
||||||
|
|
||||||
|
The configuration of Quartz can be broken down into two main parts:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
configuration:
|
||||||
|
pageTitle: "My Site"
|
||||||
|
# ... general configuration
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/some-plugin
|
||||||
|
enabled: true
|
||||||
|
# ... plugin entries
|
||||||
|
```
|
||||||
|
|
||||||
|
## General Configuration
|
||||||
|
|
||||||
|
This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure:
|
||||||
|
|
||||||
|
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
|
||||||
|
- `pageTitleSuffix`: a string added to the end of the page title. This only applies to the browser tab title, not the title shown at the top of the page.
|
||||||
|
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
|
||||||
|
- `enablePopovers`: whether to enable [[popover previews]] on your site.
|
||||||
|
- `analytics`: what to use for analytics on your site. Values can be
|
||||||
|
- `null`: don't use analytics;
|
||||||
|
- `{ provider: 'google', tagId: '<your-google-tag>' }`: use Google Analytics;
|
||||||
|
- `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: 'https://<your-plausible-host>' }` (self-hosted, make sure to include the `https://` protocol prefix): use [Plausible](https://plausible.io/);
|
||||||
|
- `{ provider: 'umami', host: '<your-umami-host>', websiteId: '<your-umami-website-id>' }`: use [Umami](https://umami.is/);
|
||||||
|
- `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com);
|
||||||
|
- `{ provider: 'posthog', apiKey: '<your-posthog-project-apiKey>', host: '<your-posthog-host>' }`: use [Posthog](https://posthog.com/);
|
||||||
|
- `{ provider: 'tinylytics', siteId: '<your-site-id>' }`: use [Tinylytics](https://tinylytics.app/);
|
||||||
|
- `{ provider: 'cabin' }` or `{ provider: 'cabin', host: 'https://cabin.example.com' }` (custom domain): use [Cabin](https://withcabin.com);
|
||||||
|
- `{provider: 'clarity', projectId: '<your-clarity-id-code' }`: use [Microsoft clarity](https://clarity.microsoft.com/). The project id can be found on top of the overview page.
|
||||||
|
- `{ provider: 'matomo', siteId: '<your-matomo-id-code', host: 'matomo.example.com' }`: use [Matomo](https://matomo.org/), without protocol.
|
||||||
|
- `{ provider: 'vercel' }`: use [Vercel Web Analytics](https://vercel.com/docs/concepts/analytics).
|
||||||
|
- `{ provider: 'rybbit', siteId: 'my-rybbit-id' }` (managed) or `{ provider: 'rybbit', siteId: 'my-rybbit-id', host: 'my-rybbit-domain.com' }` (self-hosted) use [Rybbit](https://rybbit.com);
|
||||||
|
- `locale`: used for [[i18n]] and date formatting
|
||||||
|
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
||||||
|
- You will be prompted to set this during [[create|`npx quartz create`]]. The CLI automatically strips any `https://` or `http://` protocol prefixes and trailing slashes for you.
|
||||||
|
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
||||||
|
- Note that Quartz 5 will avoid using this as much as possible and use relative URLs whenever it can to make sure your site works no matter _where_ you end up actually deploying it.
|
||||||
|
- `ignorePatterns`: a list of [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details.
|
||||||
|
- `theme`: configure how the site looks.
|
||||||
|
- `fontOrigin`: where to load fonts from.
|
||||||
|
- `"googleFonts"` (default): loads fonts from Google Fonts API. Fastest option, especially with CDN caching enabled.
|
||||||
|
- `"local"`: downloads fonts and serves them from your site. Fully self-contained with no external requests.
|
||||||
|
- `cdnCaching`: if `true` (default), use Google CDN to cache the fonts. This will generally be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
|
||||||
|
- `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here.
|
||||||
|
- `title`: font for the title of the site (optional, same as `header` by default)
|
||||||
|
- `header`: font to use for headers
|
||||||
|
- `code`: font for inline and block quotes
|
||||||
|
- `body`: font for everything
|
||||||
|
- `colors`: controls the theming of the site.
|
||||||
|
- `light`: page background
|
||||||
|
- `lightgray`: borders
|
||||||
|
- `gray`: graph links, heavier borders
|
||||||
|
- `darkgray`: body text
|
||||||
|
- `dark`: header text and icons
|
||||||
|
- `secondary`: link colour, current [[graph view|graph]] node
|
||||||
|
- `tertiary`: hover states and visited [[graph view|graph]] nodes
|
||||||
|
- `highlight`: internal link background, highlighted text, [[syntax highlighting|highlighted lines of code]]
|
||||||
|
- `textHighlight`: markdown highlighted text background
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
You can think of Quartz plugins as a series of transformations over content.
|
||||||
|
|
||||||
|
![[quartz transform pipeline.png]]
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/created-modified-date
|
||||||
|
enabled: true
|
||||||
|
order: 10 # controls execution order
|
||||||
|
- source: github:quartz-community/syntax-highlighting
|
||||||
|
enabled: true
|
||||||
|
order: 20
|
||||||
|
# ... more plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
Plugins are categorized by their type (transformer, filter, emitter, pageType) based on their manifest. The `order` field controls execution order within each category.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For advanced TS override of plugin configuration, you can modify `quartz.ts`:
|
||||||
|
>
|
||||||
|
> ```ts title="quartz.ts"
|
||||||
|
> import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
>
|
||||||
|
> const config = await loadQuartzConfig({
|
||||||
|
> // override any configuration field here
|
||||||
|
> })
|
||||||
|
> export default config
|
||||||
|
> export const layout = await loadQuartzLayout()
|
||||||
|
> ```
|
||||||
|
|
||||||
|
- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description)
|
||||||
|
- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts)
|
||||||
|
- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag)
|
||||||
|
- **Page Types** define how different types of pages are rendered (content pages, folder listings, tag listings). Each page type can use a different [[layout#Page Frames|page frame]] to control its overall HTML structure.
|
||||||
|
|
||||||
|
The `layout.byPageType` section in `quartz.config.yaml` can also set a `template` field to override the page frame for a specific page type:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
layout:
|
||||||
|
byPageType:
|
||||||
|
canvas:
|
||||||
|
template: minimal # Override the page frame for canvas pages
|
||||||
|
```
|
||||||
|
|
||||||
|
See [[layout#Page Frames]] for details on available frames and how frame resolution works.
|
||||||
|
|
||||||
|
### Internal vs External Plugins
|
||||||
|
|
||||||
|
Quartz distinguishes between internal plugins that are bundled with Quartz and community plugins that are installed separately.
|
||||||
|
|
||||||
|
In `quartz.config.yaml`, community plugins are referenced by their GitHub source:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
- source: github:quartz-community/syntax-highlighting
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
theme:
|
||||||
|
light: github-light
|
||||||
|
dark: github-dark
|
||||||
|
```
|
||||||
|
|
||||||
|
Internal plugins (like `FrontMatter`) are bundled with Quartz. Community plugins are installed separately and referenced by their `github:org/repo` source.
|
||||||
|
|
||||||
|
### Community Plugins
|
||||||
|
|
||||||
|
To install a community plugin, you can use the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin add github:quartz-community/explorer
|
||||||
|
```
|
||||||
|
|
||||||
|
This adds the plugin to `quartz.config.yaml` and installs it to `.quartz/plugins/`.
|
||||||
|
|
||||||
|
To install all plugins referenced in your config that aren't yet installed (useful when cloning a project or setting up CI):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
```
|
||||||
|
|
||||||
|
To remove installed plugins that are no longer in your config:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin prune
|
||||||
|
```
|
||||||
|
|
||||||
|
Both commands support `--dry-run` to preview changes. See [[cli/plugin|the plugin CLI reference]] for full details.
|
||||||
|
|
||||||
|
### Advanced Source Options
|
||||||
|
|
||||||
|
The `source` field for a plugin can be either a simple string or an object with additional options. The string form is the most common:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
For plugins that live in a subdirectory of a repository (monorepo-style), or when you need to pin to a specific branch or tag, use the object form:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source:
|
||||||
|
repo: "https://github.com/user/repo.git"
|
||||||
|
subdir: plugin
|
||||||
|
ref: main
|
||||||
|
name: my-plugin
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
The object form supports the following fields:
|
||||||
|
|
||||||
|
| Field | Required | Description |
|
||||||
|
| -------- | :------: | --------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `repo` | ✅ | Git repository URL (e.g. `https://github.com/user/repo.git`). |
|
||||||
|
| `subdir` | ❌ | Subdirectory within the repository that contains the plugin. Used for monorepo-style plugin repositories. |
|
||||||
|
| `ref` | ❌ | Git ref (branch or tag) to pin to. Equivalent to the `#ref` suffix on string sources. |
|
||||||
|
| `name` | ❌ | Override the directory name used in `.quartz/plugins/`. Defaults to the repository name. |
|
||||||
|
|
||||||
|
> [!example] Real-world example
|
||||||
|
> The [quartz-themes](https://github.com/saberzero1/quartz-themes) plugin lives in the `plugin/` subdirectory of its repository. To install it:
|
||||||
|
>
|
||||||
|
> ```yaml title="quartz.config.yaml"
|
||||||
|
> plugins:
|
||||||
|
> - source:
|
||||||
|
> name: quartz-themes
|
||||||
|
> repo: "https://github.com/saberzero1/quartz-themes.git"
|
||||||
|
> subdir: plugin
|
||||||
|
> enabled: true
|
||||||
|
> options:
|
||||||
|
> theme: "tokyo-night"
|
||||||
|
> mode: both
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> The string form `github:user/repo#branch` and the object form `{ repo, ref }` are equivalent ways to specify a branch. Use the object form when you also need `subdir` or `name`, or when you prefer a more readable configuration.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
You can customize the behaviour of Quartz by adding, removing and reordering plugins in `quartz.config.yaml`. Each plugin entry specifies its source, whether it's enabled, execution order, and any options:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/note-properties
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
includeAll: false
|
||||||
|
includedProperties:
|
||||||
|
- description
|
||||||
|
- tags
|
||||||
|
- aliases
|
||||||
|
order: 5
|
||||||
|
- source: github:quartz-community/created-modified-date
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
priority:
|
||||||
|
- frontmatter
|
||||||
|
- git
|
||||||
|
- filesystem
|
||||||
|
order: 10
|
||||||
|
- source: github:quartz-community/latex
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
renderEngine: katex
|
||||||
|
order: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Some plugin options require JavaScript callback functions (e.g. custom sort, filter, or map functions) that can't be expressed in YAML. For these, use the TS override in `quartz.ts`:
|
||||||
|
>
|
||||||
|
> ```ts title="quartz.ts"
|
||||||
|
> import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
> import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
>
|
||||||
|
> ExternalPlugin.Explorer({
|
||||||
|
> mapFn: (node) => {
|
||||||
|
> node.displayName = node.displayName.toUpperCase()
|
||||||
|
> return node
|
||||||
|
> },
|
||||||
|
> })
|
||||||
|
>
|
||||||
|
> const config = await loadQuartzConfig()
|
||||||
|
> export default config
|
||||||
|
> export const layout = await loadQuartzLayout()
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Options set in `quartz.ts` are merged with YAML options and take precedence. Plugin overrides must be placed **before** `loadQuartzConfig()` so they are applied when components are instantiated during config loading. See the plugin-specific documentation for available callback options.
|
||||||
|
|
||||||
|
You can see a list of all plugins and their configuration options [[tags/plugin|here]].
|
||||||
|
|
||||||
|
If you'd like to make your own plugins, see the [[making plugins|making custom plugins]] guide.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
Fonts can be specified as a simple string or with advanced options in `quartz.config.yaml`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
configuration:
|
||||||
|
theme:
|
||||||
|
typography:
|
||||||
|
title: Schibsted Grotesk # optional, defaults to header font
|
||||||
|
header: Schibsted Grotesk
|
||||||
|
body: Source Sans Pro
|
||||||
|
code: IBM Plex Mono
|
||||||
|
```
|
||||||
|
|
||||||
|
For more control over font weights and italics, use the TS override in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts"
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig({
|
||||||
|
theme: {
|
||||||
|
typography: {
|
||||||
|
header: {
|
||||||
|
name: "Schibsted Grotesk",
|
||||||
|
weights: [400, 700],
|
||||||
|
includeItalic: true,
|
||||||
|
},
|
||||||
|
body: "Source Sans Pro",
|
||||||
|
code: "IBM Plex Mono",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout()
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> For per-heading font control, self-hosted fonts, or Obsidian theme font bridging, see the [[plugins/Fonts|Fonts]] plugin. It can download Google Fonts at build time and serve them locally with `fontOrigin: selfHosted`, making your site fully self-contained.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: Bases Support
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz supports rendering [Obsidian Bases](https://obsidian.md/changelog/2025-04-15-desktop-v1.8.0/) (`.base` files) as interactive database-like views. Bases files define queries over your vault's notes and display the results in configurable views such as tables, lists, cards, galleries, and boards.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
Bases support is provided by the [[BasesPage]] plugin. See the plugin page for configuration options, built-in views, the expression engine, and how to extend with custom views.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
![[Base.base]]
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/bases-page`
|
||||||
|
- Source: [`quartz-community/bases-page`](https://github.com/quartz-community/bases-page)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: Canvas Support
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz supports rendering [JSON Canvas](https://jsoncanvas.org) (`.canvas`) files as interactive, pannable and zoomable canvas pages. This brings your Obsidian canvas files to the web, preserving text nodes, file references, link nodes, group nodes, and edges with full visual fidelity.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
Canvas support is provided by the [[CanvasPage]] plugin. See the plugin page for configuration options and a full list of supported features.
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
![[Canvas.canvas]]
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/canvas-page`
|
||||||
|
- Source: [`quartz-community/canvas-page`](https://github.com/quartz-community/canvas-page)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: Citations
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz uses [rehype-citation](https://github.com/timlrx/rehype-citation) to support parsing of a BibTex bibliography file.
|
||||||
|
|
||||||
|
Under the default configuration, a citation key `[@templeton2024scaling]` will be exported as `(Templeton et al., 2024)`.
|
||||||
|
|
||||||
|
> [!example]- BibTex file
|
||||||
|
>
|
||||||
|
> ```bib title="bibliography.bib"
|
||||||
|
> @article{templeton2024scaling,
|
||||||
|
> title={Scaling Monosemanticity: Extracting Interpretable Features from Claude 3 Sonnet},
|
||||||
|
> author={Templeton, Adly and Conerly, Tom and Marcus, Jonathan and Lindsey, Jack and Bricken, Trenton and Chen, Brian and Pearce, Adam and Citro, Craig and Ameisen, Emmanuel and Jones, Andy and Cunningham, Hoagy and Turner, Nicholas L and McDougall, Callum and MacDiarmid, Monte and Freeman, C. Daniel and Sumers, Theodore R. and Rees, Edward and Batson, Joshua and Jermyn, Adam and Carter, Shan and Olah, Chris and Henighan, Tom},
|
||||||
|
> year={2024},
|
||||||
|
> journal={Transformer Circuits Thread},
|
||||||
|
> url={https://transformer-circuits.pub/2024/scaling-monosemanticity/index.html}
|
||||||
|
> }
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> [!note] Behaviour of references
|
||||||
|
>
|
||||||
|
> By default, the references will be included at the end of the file. To control where the references to be included, uses `[^ref]`
|
||||||
|
>
|
||||||
|
> Refer to `rehype-citation` docs for more information.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Citation parsing is a functionality of the [[plugins/Citations|Citation]] plugin. **This plugin is not enabled by default**. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "Docker Support"
|
||||||
|
tags:
|
||||||
|
- feature
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz comes shipped with a Docker image that will allow you to preview your Quartz locally without installing Node.
|
||||||
|
|
||||||
|
You can run the below one-liner to run Quartz in Docker.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm -itp 8080:8080 -p 3001:3001 -v ./content:/usr/src/app/content $(docker build -q .)
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!warning] Not to be used for production
|
||||||
|
> Serve mode is intended for local previews only.
|
||||||
|
> For production workloads, see the page on [[hosting]].
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: LaTeX
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
### Block Math
|
||||||
|
|
||||||
|
Block math can be rendered by delimiting math expression with `$$`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$$
|
||||||
|
f(x) = \int_{-\infty}^\infty
|
||||||
|
f\hat(\xi),e^{2 \pi i \xi x}
|
||||||
|
\,d\xi
|
||||||
|
$$
|
||||||
|
```
|
||||||
|
|
||||||
|
$$
|
||||||
|
f(x) = \int_{-\infty}^\infty
|
||||||
|
f\hat(\xi),e^{2 \pi i \xi x}
|
||||||
|
\,d\xi
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{aligned}
|
||||||
|
a &= b + c \\ &= e + f \\
|
||||||
|
\end{aligned}
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{bmatrix}
|
||||||
|
1 & 2 & 3 \\
|
||||||
|
a & b & c
|
||||||
|
\end{bmatrix}
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{array}{rll}
|
||||||
|
E \psi &= H\psi & \text{Expanding the Hamiltonian Operator} \\
|
||||||
|
&= -\frac{\hbar^2}{2m}\frac{\partial^2}{\partial x^2} \psi + \frac{1}{2}m\omega x^2 \psi & \text{Using the ansatz $\psi(x) = e^{-kx^2}f(x)$, hoping to cancel the $x^2$ term} \\
|
||||||
|
&= -\frac{\hbar^2}{2m} [4k^2x^2f(x)+2(-2kx)f'(x) + f''(x)]e^{-kx^2} + \frac{1}{2}m\omega x^2 f(x)e^{-kx^2} &\text{Removing the $e^{-kx^2}$ term from both sides} \\
|
||||||
|
& \Downarrow \\
|
||||||
|
Ef(x) &= -\frac{\hbar^2}{2m} [4k^2x^2f(x)-4kxf'(x) + f''(x)] + \frac{1}{2}m\omega x^2 f(x) & \text{Choosing $k=\frac{im}{2}\sqrt{\frac{\omega}{\hbar}}$ to cancel the $x^2$ term, via $-\frac{\hbar^2}{2m}4k^2=\frac{1}{2}m \omega$} \\
|
||||||
|
&= -\frac{\hbar^2}{2m} [-4kxf'(x) + f''(x)] \\
|
||||||
|
\end{array}
|
||||||
|
$$
|
||||||
|
|
||||||
|
> [!warn]
|
||||||
|
> Due to limitations in the [underlying parsing library](https://github.com/remarkjs/remark-math), block math in Quartz requires the `$$` delimiters to be on newlines like above.
|
||||||
|
|
||||||
|
### Inline Math
|
||||||
|
|
||||||
|
Similarly, inline math can be rendered by delimiting math expression with a single `$`. For example, `$e^{i\pi} = -1$` produces $e^{i\pi} = -1$
|
||||||
|
|
||||||
|
### Escaping symbols
|
||||||
|
|
||||||
|
There will be cases where you may have more than one `$` in a paragraph at once which may accidentally trigger MathJax/Katex.
|
||||||
|
|
||||||
|
To get around this, you can escape the dollar sign by doing `\$` instead.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- Incorrect: `I have $1 and you have $2` produces I have $1 and you have $2
|
||||||
|
- Correct: `I have \$1 and you have \$2` produces I have \$1 and you have \$2
|
||||||
|
|
||||||
|
### Using mhchem
|
||||||
|
|
||||||
|
If you are using the community Latex plugin, you can add `mhchem` support by forking the plugin repository and adding the following import to the top of `src/index.ts` (before all the other imports):
|
||||||
|
|
||||||
|
```ts title="src/index.ts"
|
||||||
|
import "katex/contrib/mhchem"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Latex parsing is a functionality of the [[plugins/Latex|Latex]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Mermaid Diagrams"
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz supports Mermaid which allows you to add diagrams and charts to your notes. Mermaid supports a range of diagrams, such as [flow charts](https://mermaid.js.org/syntax/flowchart.html), [sequence diagrams](https://mermaid.js.org/syntax/sequenceDiagram.html), and [timelines](https://mermaid.js.org/syntax/timeline.html). This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin.
|
||||||
|
|
||||||
|
By default, Quartz will render Mermaid diagrams to match the site theme.
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]].
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
To add a Mermaid diagram, create a mermaid code block.
|
||||||
|
|
||||||
|
````
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->>+John: Hello John, how are you?
|
||||||
|
Alice->>+John: John, can you hear me?
|
||||||
|
John-->>-Alice: Hi Alice, I can hear you!
|
||||||
|
John-->>-Alice: I feel great!
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
Alice->>+John: Hello John, how are you?
|
||||||
|
Alice->>+John: John, can you hear me?
|
||||||
|
John-->>-Alice: Hi Alice, I can hear you!
|
||||||
|
John-->>-Alice: I feel great!
|
||||||
|
```
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
title: "Obsidian Compatibility"
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz was originally designed as a tool to publish Obsidian vaults as websites. Even as the scope of Quartz has widened over time, it hasn't lost the ability to seamlessly interoperate with Obsidian.
|
||||||
|
|
||||||
|
By default, Quartz ships with the [[ObsidianFlavoredMarkdown]] plugin, which is a transformer plugin that adds support for [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown). This includes support for features like [[wikilinks]] and [[Mermaid diagrams]].
|
||||||
|
|
||||||
|
It also ships with support for [frontmatter parsing](https://help.obsidian.md/Editing+and+formatting/Properties) with the same fields that Obsidian uses through the [[Frontmatter]] transformer plugin.
|
||||||
|
|
||||||
|
Finally, Quartz also provides [[CrawlLinks]] plugin, which allows you to customize Quartz's link resolution behaviour to match Obsidian.
|
||||||
|
|
||||||
|
## Supported Features
|
||||||
|
|
||||||
|
### Wikilinks
|
||||||
|
|
||||||
|
Internal links using the `[[page]]` syntax are converted to regular links. See [[wikilinks]] for more details. All variations are supported:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
[[Page]] Link to a page
|
||||||
|
[[Page|Custom text]] Link with alias
|
||||||
|
[[Page#Heading]] Link to a heading
|
||||||
|
[[Page#Heading|Custom text]] Link to a heading with alias
|
||||||
|
[[Page#^block-id]] Link to a block reference
|
||||||
|
![[Page]] Embed (transclude) a page
|
||||||
|
![[image.png]] Embed an image
|
||||||
|
![[image.png|alt 100x200]] Embed with alt text and dimensions
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside tables, pipes in wikilinks can be escaped with a backslash:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
| Column |
|
||||||
|
| --------------- |
|
||||||
|
| [[page\|alias]] |
|
||||||
|
```
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
Wrap text in `==` to highlight it:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
This is ==highlighted text== in a sentence.
|
||||||
|
```
|
||||||
|
|
||||||
|
This renders as: This is ==highlighted text== in a sentence.
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
|
||||||
|
Obsidian-style comments are stripped from the output:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
This is visible. %%This is a comment and won't appear.%%
|
||||||
|
```
|
||||||
|
|
||||||
|
This renders as: This is visible. %%This is a comment and won't appear.%%
|
||||||
|
|
||||||
|
Multi-line comments are also supported:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
%%
|
||||||
|
This entire block
|
||||||
|
is a comment.
|
||||||
|
%%
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
Tags starting with `#` are parsed and linked to tag pages:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
#tag #nested/tag #tag-with-dashes
|
||||||
|
```
|
||||||
|
|
||||||
|
For example: #feature/transformer
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Pure numeric tags like `#123` are ignored, matching Obsidian behaviour.
|
||||||
|
|
||||||
|
### Callouts
|
||||||
|
|
||||||
|
[[callouts|Obsidian callouts]] are fully supported, including collapsible variants:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> [!note]
|
||||||
|
> This is a note callout.
|
||||||
|
|
||||||
|
> [!warning]- Collapsed by default
|
||||||
|
> This content is hidden initially.
|
||||||
|
|
||||||
|
> [!tip]+ Expanded by default
|
||||||
|
> This content is visible initially.
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!example] Live example
|
||||||
|
> This is a live callout rendered from Obsidian-flavored Markdown.
|
||||||
|
|
||||||
|
All built-in callout types are supported: `note`, `abstract`, `info`, `todo`, `tip`, `success`, `question`, `warning`, `failure`, `danger`, `bug`, `example`, and `quote`, along with their aliases.
|
||||||
|
|
||||||
|
### Task Lists and Custom Task Characters
|
||||||
|
|
||||||
|
Standard checkboxes work out of the box. With `enableCheckbox: true`, you also get support for custom task characters that are popular in the Obsidian community:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] Unchecked
|
||||||
|
- [x] Checked
|
||||||
|
- [?] Question
|
||||||
|
- [!] Important
|
||||||
|
- [>] Forwarded
|
||||||
|
- [/] In progress
|
||||||
|
- [-] Cancelled
|
||||||
|
- [s] Special
|
||||||
|
```
|
||||||
|
|
||||||
|
Each custom character is preserved as a `data-task` attribute on the rendered element, allowing CSS-based styling per character.
|
||||||
|
|
||||||
|
- [ ] Unchecked
|
||||||
|
- [x] Checked
|
||||||
|
- [?] Question
|
||||||
|
- [!] Important
|
||||||
|
|
||||||
|
### Mermaid Diagrams
|
||||||
|
|
||||||
|
[[Mermaid diagrams|Mermaid]] code blocks are rendered as diagrams:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Start] --> B{Decision}
|
||||||
|
B -->|Yes| C[OK]
|
||||||
|
B -->|No| D[Cancel]
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Start] --> B{Decision}
|
||||||
|
B -->|Yes| C[OK]
|
||||||
|
B -->|No| D[Cancel]
|
||||||
|
```
|
||||||
|
|
||||||
|
### YouTube Embeds
|
||||||
|
|
||||||
|
YouTube videos can be embedded using standard image syntax with a YouTube URL:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
For example, the following embed is rendered from ``:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Tweet Embeds
|
||||||
|
|
||||||
|
Tweets from Twitter/X are embedded as static blockquotes with a link to the original:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
For example, the following embed is rendered from ``:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Block References
|
||||||
|
|
||||||
|
Block references allow linking to specific blocks within a page:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Content paragraph. ^my-block
|
||||||
|
|
||||||
|
[[Page#^my-block]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Obsidian URI Links
|
||||||
|
|
||||||
|
Links using the `obsidian://` protocol are marked with a CSS class (`obsidian-uri`) and a `data-obsidian-uri` attribute, so you can style them differently from regular links.
|
||||||
|
|
||||||
|
### Video Embeds
|
||||||
|
|
||||||
|
Video files can be embedded using standard image syntax:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|

|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
### Embed in HTML
|
||||||
|
|
||||||
|
By default, Obsidian does not render its Markdown syntax inside HTML blocks. Quartz extends this with the `enableInHtmlEmbed` option, which parses wikilinks, highlights, and tags inside raw HTML nodes.
|
||||||
|
|
||||||
|
### Footnotes
|
||||||
|
|
||||||
|
Footnotes using the `[^1]` syntax are fully supported through the [[GitHubFlavoredMarkdown]] plugin:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Here is a sentence with a footnote.[^1]
|
||||||
|
|
||||||
|
[^1]: This is the footnote content.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Obsidian Community Plugin Support
|
||||||
|
|
||||||
|
Quartz focuses on supporting Obsidian's core features. Functionality from Obsidian community plugins is handled by Quartz community plugins:
|
||||||
|
|
||||||
|
| Obsidian Plugin | Quartz Support |
|
||||||
|
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Dataview | Supported via [Quartz Syncer](https://community.obsidian.md/plugins/quartz-syncer) — exports Dataview queries as static content during sync |
|
||||||
|
| Excalidraw | Supported via the `obsidian-plugin-excalidraw` community plugin |
|
||||||
|
| Leaflet Maps | Supported via the `obsidian-plugin-leaflet` community plugin |
|
||||||
|
| Style Settings | Supported via the `quartz-themes` community plugin |
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> As a general rule: Obsidian core features are supported by Quartz directly, while Obsidian community plugin features are supported by corresponding Quartz community plugins. Not all Obsidian community plugins will have Quartz equivalents, but popular ones are likely to be supported by the community.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This functionality is provided by the [[ObsidianFlavoredMarkdown]], [[Frontmatter]] and [[CrawlLinks]] plugins. See the plugin pages for customization options.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: "OxHugo Compatibility"
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
[org-roam](https://www.orgroam.com/) is a plain-text personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown.
|
||||||
|
|
||||||
|
Because the Markdown generated by ox-hugo is not pure Markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by the [[OxHugoFlavoredMarkdown]] plugin. Even though this plugin was written with `ox-hugo` in mind, it should work for any Hugo specific Markdown.
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/obsidian-flavored-markdown
|
||||||
|
enabled: true
|
||||||
|
order: 30
|
||||||
|
- source: github:quartz-community/ox-hugo
|
||||||
|
enabled: true
|
||||||
|
order: 25 # must come before obsidian-flavored-markdown
|
||||||
|
- source: github:quartz-community/github-flavored-markdown
|
||||||
|
enabled: true
|
||||||
|
order: 40
|
||||||
|
- source: github:quartz-community/note-properties
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
delimiters: "+++"
|
||||||
|
language: toml # if using toml frontmatter
|
||||||
|
order: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
For the TS override approach, place overrides before `loadQuartzConfig()` in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
|
||||||
|
ExternalPlugin.NoteProperties({ delims: "+++", language: "toml" })
|
||||||
|
ExternalPlugin.OxHugoFlavouredMarkdown()
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> In YAML, plugin execution order is controlled by the `order` field. Lower numbers execute first. Ensure `ox-hugo` has a lower `order` than `obsidian-flavored-markdown`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Quartz by default doesn't understand `org-roam` files as they aren't Markdown. You're responsible for using an external tool like `ox-hugo` to export the `org-roam` files as Markdown content to Quartz and managing the static assets so that they're available in the final output.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This functionality is provided by the [[OxHugoFlavoredMarkdown]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
Quartz emits an RSS feed for all the content on your site by generating an `index.xml` file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> After deploying, the generated RSS link will be available at `https://${baseUrl}/index.xml` by default.
|
||||||
|
>
|
||||||
|
> The `index.xml` path can be customized by passing the `rssSlug` option to the [[ContentIndex]] plugin.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: "Roam Research Compatibility"
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
[Roam Research](https://roamresearch.com) is a note-taking tool that organizes your knowledge graph in a unique and interconnected way.
|
||||||
|
|
||||||
|
Quartz supports transforming the special Markdown syntax from Roam Research (like `{{[[components]]}}` and other formatting) into
|
||||||
|
regular Markdown via the [[RoamFlavoredMarkdown]] plugin.
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/roam
|
||||||
|
enabled: true
|
||||||
|
order: 25 # must come before obsidian-flavored-markdown
|
||||||
|
- source: github:quartz-community/obsidian-flavored-markdown
|
||||||
|
enabled: true
|
||||||
|
order: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
For the TS override approach, place overrides before `loadQuartzConfig()` in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
|
||||||
|
ExternalPlugin.RoamFlavoredMarkdown()
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> In YAML, plugin execution order is controlled by the `order` field. Ensure `roam` has a lower `order` value than `obsidian-flavored-markdown` so it runs first.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
This functionality is provided by the [[RoamFlavoredMarkdown]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: "SPA Routing"
|
||||||
|
tags:
|
||||||
|
- feature
|
||||||
|
---
|
||||||
|
|
||||||
|
Single-page-app style rendering. This prevents flashes of unstyled content and improves the smoothness of Quartz.
|
||||||
|
|
||||||
|
Under the hood, this is done by hijacking page navigations and instead fetching the HTML via a `GET` request and then diffing and selectively replacing parts of the page using [micromorph](https://github.com/natemoo-re/micromorph). This allows us to change the content of the page without fully refreshing the page, reducing the amount of content that the browser needs to load.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Disable SPA Routing: set the `enableSPA` field of the [[configuration]] in `quartz.config.yaml` to be `false`.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: Backlinks
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
A backlink for a note is a link from another note to that note. Links in the backlink pane also feature rich [[popover previews]] if you have that feature enabled.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
- Removing backlinks: remove the `backlinks` entry from `quartz.config.yaml` or set `enabled: false`.
|
||||||
|
- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, set `hideWhenEmpty: false` in the plugin options in `quartz.config.yaml`.
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/backlinks`
|
||||||
|
- Source: [`quartz-community/backlinks`](https://github.com/quartz-community/backlinks)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: "Breadcrumbs"
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Breadcrumbs provide a way to navigate a hierarchy of pages within your site using a list of its parent folders.
|
||||||
|
|
||||||
|
By default, the element at the very top of your page is the breadcrumb navigation bar (can also be seen at the top on this page!).
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Most configuration can be done via the `options` section of the breadcrumbs plugin entry in `quartz.config.yaml`.
|
||||||
|
|
||||||
|
For example, here's what the default configuration looks like:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/breadcrumbs
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
spacerSymbol: "❯"
|
||||||
|
rootName: Home
|
||||||
|
resolveFrontmatterTitle: true
|
||||||
|
showCurrentPage: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
For the TS override approach:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
// Must be placed before loadQuartzConfig()
|
||||||
|
ExternalPlugin.Breadcrumbs({
|
||||||
|
spacerSymbol: "❯",
|
||||||
|
rootName: "Home",
|
||||||
|
resolveFrontmatterTitle: true,
|
||||||
|
showCurrentPage: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
|
||||||
|
|
||||||
|
You can also adjust where the breadcrumbs will be displayed by changing the `layout.position` field in the plugin entry in `quartz.config.yaml` (see [[layout]]).
|
||||||
|
|
||||||
|
Want to customize it even more?
|
||||||
|
|
||||||
|
- Removing breadcrumbs: remove the `breadcrumbs` entry from `quartz.config.yaml` or set `enabled: false`.
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/breadcrumbs`
|
||||||
|
- Source: [`quartz-community/breadcrumbs`](https://github.com/quartz-community/breadcrumbs)
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
title: Callouts
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz supports the same Admonition-callout syntax as Obsidian.
|
||||||
|
|
||||||
|
This includes
|
||||||
|
|
||||||
|
- 12 Distinct callout types (each with several aliases)
|
||||||
|
- Collapsable callouts
|
||||||
|
|
||||||
|
```
|
||||||
|
> [!info] Title
|
||||||
|
> This is a callout!
|
||||||
|
```
|
||||||
|
|
||||||
|
See [documentation on supported types and syntax here](https://help.obsidian.md/Editing+and+formatting/Callouts).
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]].
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
The callouts are a functionality of the [[ObsidianFlavoredMarkdown]] plugin. See the plugin page for how to enable or disable them.
|
||||||
|
|
||||||
|
You can edit the icons by customizing `quartz/styles/callouts.scss`.
|
||||||
|
|
||||||
|
### Add custom callouts
|
||||||
|
|
||||||
|
By default, custom callouts are handled by applying the `note` style. To make fancy ones, you have to add these lines to `custom.scss`.
|
||||||
|
|
||||||
|
```scss title="quartz/styles/custom.scss"
|
||||||
|
.callout {
|
||||||
|
&[data-callout="custom"] {
|
||||||
|
--color: #customcolor;
|
||||||
|
--border: #custombordercolor;
|
||||||
|
--bg: #custombg;
|
||||||
|
--callout-icon: url("data:image/svg+xml; utf8, <custom formatted svg>"); //SVG icon code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that.
|
||||||
|
|
||||||
|
## Showcase
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> Default title
|
||||||
|
|
||||||
|
> [!question]+ Can callouts be _nested_?
|
||||||
|
>
|
||||||
|
> > [!todo]- Yes!, they can. And collapsed!
|
||||||
|
> >
|
||||||
|
> > > [!example] You can even use multiple layers of nesting.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Aliases: "note"
|
||||||
|
|
||||||
|
> [!abstract]
|
||||||
|
> Aliases: "abstract", "summary", "tldr"
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> Aliases: "info"
|
||||||
|
|
||||||
|
> [!todo]
|
||||||
|
> Aliases: "todo"
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> Aliases: "tip", "hint", "important"
|
||||||
|
|
||||||
|
> [!success]
|
||||||
|
> Aliases: "success", "check", "done"
|
||||||
|
|
||||||
|
> [!question]
|
||||||
|
> Aliases: "question", "help", "faq"
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Aliases: "warning", "attention", "caution"
|
||||||
|
|
||||||
|
> [!failure]
|
||||||
|
> Aliases: "failure", "missing", "fail"
|
||||||
|
|
||||||
|
> [!danger]
|
||||||
|
> Aliases: "danger", "error"
|
||||||
|
|
||||||
|
> [!bug]
|
||||||
|
> Aliases: "bug"
|
||||||
|
|
||||||
|
> [!example]
|
||||||
|
> Aliases: "example"
|
||||||
|
|
||||||
|
> [!quote]
|
||||||
|
> Aliases: "quote", "cite"
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
---
|
||||||
|
title: Comments
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz also has the ability to hook into various providers to enable readers to leave comments on your site.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
![[giscus-example.png]]
|
||||||
|
|
||||||
|
As of today, only [Giscus](https://giscus.app/) is supported out of the box but PRs to support other providers are welcome!
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
### Giscus
|
||||||
|
|
||||||
|
First, make sure that the [[setting up your GitHub repository|GitHub]] repository you are using for your Quartz meets the following requirements:
|
||||||
|
|
||||||
|
1. The **repository is [public](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/setting-repository-visibility#making-a-repository-public)**, otherwise visitors will not be able to view the discussion.
|
||||||
|
2. The **[giscus](https://github.com/apps/giscus) app is installed**, otherwise visitors will not be able to comment and react.
|
||||||
|
3. The **Discussions feature is turned on** by [enabling it for your repository](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/enabling-or-disabling-github-discussions-for-a-repository).
|
||||||
|
|
||||||
|
Then, use the [Giscus site](https://giscus.app/#repository) to figure out what your `repoId` and `categoryId` should be. Make sure you select `Announcements` for the Discussion category.
|
||||||
|
|
||||||
|
![[giscus-repo.png]]
|
||||||
|
|
||||||
|
![[giscus-discussion.png]]
|
||||||
|
|
||||||
|
After entering both your repository and selecting the discussion category, Giscus will compute some IDs that you'll need to provide back to Quartz. You won't need to manually add the script yourself as Quartz will handle that part for you but will need these values in the next step!
|
||||||
|
|
||||||
|
![[giscus-results.png]]
|
||||||
|
|
||||||
|
Finally, in `quartz.config.yaml`, add the comments plugin with the following options (using the values you got from above):
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/comments
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
provider: giscus
|
||||||
|
options:
|
||||||
|
repo: jackyzha0/quartz
|
||||||
|
repoId: MDEwOlJlcG9zaXRvcnkzODcyMTMyMDg
|
||||||
|
category: Announcements
|
||||||
|
categoryId: DIC_kwDOFxRnmM4B-Xg6
|
||||||
|
lang: en
|
||||||
|
layout:
|
||||||
|
position: afterBody
|
||||||
|
priority: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
For the TS override approach:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
// If using quartz.ts overrides instead of YAML:
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig()
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout({
|
||||||
|
defaults: {
|
||||||
|
afterBody: [
|
||||||
|
ExternalPlugin.Comments({
|
||||||
|
provider: "giscus",
|
||||||
|
options: {
|
||||||
|
repo: "jackyzha0/quartz",
|
||||||
|
repoId: "MDEwOlJlcG9zaXRvcnkzODcyMTMyMDg",
|
||||||
|
category: "Announcements",
|
||||||
|
categoryId: "DIC_kwDOFxRnmM4B-Xg6",
|
||||||
|
lang: "en",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Install the comments plugin first: `npx quartz plugin add github:quartz-community/comments`
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
Quartz also exposes a few of the other Giscus options as well and you can provide them the same way `repo`, `repoId`, `category`, and `categoryId` are provided.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Options = {
|
||||||
|
provider: "giscus"
|
||||||
|
options: {
|
||||||
|
repo: `${string}/${string}`
|
||||||
|
repoId: string
|
||||||
|
category: string
|
||||||
|
categoryId: string
|
||||||
|
|
||||||
|
// Url to folder with custom themes
|
||||||
|
// defaults to 'https://${cfg.baseUrl}/static/giscus'
|
||||||
|
themeUrl?: string
|
||||||
|
|
||||||
|
// filename for light theme .css file
|
||||||
|
// defaults to 'light'
|
||||||
|
lightTheme?: string
|
||||||
|
|
||||||
|
// filename for dark theme .css file
|
||||||
|
// defaults to 'dark'
|
||||||
|
darkTheme?: string
|
||||||
|
|
||||||
|
// how to map pages -> discussions
|
||||||
|
// defaults to 'url'
|
||||||
|
mapping?: "url" | "title" | "og:title" | "specific" | "number" | "pathname"
|
||||||
|
|
||||||
|
// use strict title matching
|
||||||
|
// defaults to true
|
||||||
|
strict?: boolean
|
||||||
|
|
||||||
|
// whether to enable reactions for the main post
|
||||||
|
// defaults to true
|
||||||
|
reactionsEnabled?: boolean
|
||||||
|
|
||||||
|
// where to put the comment input box relative to the comments
|
||||||
|
// defaults to 'bottom'
|
||||||
|
inputPosition?: "top" | "bottom"
|
||||||
|
|
||||||
|
// set your preference language here
|
||||||
|
// defaults to 'en'
|
||||||
|
lang?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom CSS theme
|
||||||
|
|
||||||
|
Quartz supports custom theme for Giscus. To use a custom CSS theme, place the `.css` file inside the `quartz/static` folder and set the configuration values.
|
||||||
|
|
||||||
|
For example, if you have a light theme `light-theme.css`, a dark theme `dark-theme.css`, and your Quartz site is hosted at `https://example.com/`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/comments
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
provider: giscus
|
||||||
|
options:
|
||||||
|
# Other options...
|
||||||
|
themeUrl: "https://example.com/static/giscus" # corresponds to quartz/static/giscus/
|
||||||
|
lightTheme: light-theme # corresponds to light-theme.css in quartz/static/giscus/
|
||||||
|
darkTheme: dark-theme # corresponds to dark-theme.css in quartz/static/giscus/
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig()
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout({
|
||||||
|
defaults: {
|
||||||
|
afterBody: [
|
||||||
|
ExternalPlugin.Comments({
|
||||||
|
provider: "giscus",
|
||||||
|
options: {
|
||||||
|
// Other options...
|
||||||
|
themeUrl: "https://example.com/static/giscus",
|
||||||
|
lightTheme: "light-theme",
|
||||||
|
darkTheme: "dark-theme",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Conditionally display comments
|
||||||
|
|
||||||
|
Quartz can conditionally display the comment box based on a field `comments` in the frontmatter. By default, all pages will display comments, to disable it for a specific page, set `comments` to `false`.
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: Comments disabled here!
|
||||||
|
comments: false
|
||||||
|
---
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: "Darkmode"
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz supports darkmode out of the box that respects the user's theme preference. Any future manual toggles of the darkmode switch will be saved in the browser's local storage so it can be persisted across future page loads.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
- Removing darkmode: remove the `darkmode` entry from `quartz.config.yaml` or set `enabled: false`.
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/darkmode`
|
||||||
|
- Source: [`quartz-community/darkmode`](https://github.com/quartz-community/darkmode)
|
||||||
|
|
||||||
|
You can also listen to the `themechange` event to perform any custom logic when the theme changes.
|
||||||
|
|
||||||
|
```js
|
||||||
|
document.addEventListener("themechange", (e) => {
|
||||||
|
console.log("Theme changed to " + e.detail.theme) // either "light" or "dark"
|
||||||
|
// your logic here
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
title: "Explorer"
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz features an explorer that allows you to navigate all files and folders on your site. It supports nested folders and is highly customizable.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> The Explorer is now a community plugin. This demonstrates how external plugins can extend Quartz functionality while serving as a reference implementation for plugin developers.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The Explorer is available as a community plugin from GitHub:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install github:quartz-community/explorer --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add it to your `quartz.config.yaml`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
By default, it shows all folders and files on your page. To display the explorer in a different spot, you can edit the [[layout]].
|
||||||
|
|
||||||
|
Display names for folders get determined by the `title` frontmatter field in `folder/index.md` (more detail in [[authoring content | Authoring Content]]). If this file does not exist or does not contain frontmatter, the local folder name will be used instead.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages.
|
||||||
|
>
|
||||||
|
> To clear/delete the explorer state from local storage, delete the `fileTree` entry (guide on how to delete a key from local storage in chromium based browsers can be found [here](https://docs.devolutions.net/kb/general-knowledge-base/clear-browser-local-storage/clear-chrome-local-storage/)). You can disable this by passing `useSavedState: false` as an argument.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Most configuration can be done by passing in options to `Explorer()`.
|
||||||
|
|
||||||
|
For example, here's what the default configuration looks like:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
title: Explorer
|
||||||
|
folderClickBehavior: collapse # "link" to navigate or "collapse" to toggle
|
||||||
|
folderDefaultState: collapsed # "collapsed" or "open"
|
||||||
|
useSavedState: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
For advanced options like custom sort, filter, and map functions, use the TS override in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts"
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
|
||||||
|
// Advanced: pass callback functions that can't be expressed in YAML
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
sortFn: (a, b) => {
|
||||||
|
/* ... */
|
||||||
|
},
|
||||||
|
filterFn: (node) => {
|
||||||
|
/* ... */
|
||||||
|
},
|
||||||
|
mapFn: (node) => {
|
||||||
|
/* ... */
|
||||||
|
},
|
||||||
|
order: ["filter", "map", "sort"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig()
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout()
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!info] How overrides work
|
||||||
|
> When you call `ExternalPlugin.Explorer({...})` in `quartz.ts`, the options are recorded and merged with the YAML configuration when the component is instantiated during the build. Options set in `quartz.ts` take precedence over those in `quartz.config.yaml`, following this order: `plugin defaults < YAML options < quartz.ts overrides`.
|
||||||
|
>
|
||||||
|
> If you have two plugins that export the same name (e.g. two different Explorer plugins installed via `--name`), use the `plugins` map to disambiguate:
|
||||||
|
>
|
||||||
|
> ```ts title="quartz.ts"
|
||||||
|
> import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
> ExternalPlugin.plugins["my-explorer"].Explorer({ mapFn: ... })
|
||||||
|
> ```
|
||||||
|
|
||||||
|
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
|
||||||
|
|
||||||
|
Want to customize it even more?
|
||||||
|
|
||||||
|
- Removing explorer: remove the `explorer` entry from `quartz.config.yaml` or set `enabled: false`
|
||||||
|
- (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout
|
||||||
|
- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]]
|
||||||
|
|
||||||
|
## Advanced customization
|
||||||
|
|
||||||
|
This component allows you to fully customize all of its behavior. You can pass a custom `sort`, `filter` and `map` function.
|
||||||
|
All functions you can pass work with the `FileTrieNode` class, which has the following properties:
|
||||||
|
|
||||||
|
```ts title="@quartz-community/explorer"
|
||||||
|
class FileTrieNode {
|
||||||
|
isFolder: boolean
|
||||||
|
children: Array<FileTrieNode>
|
||||||
|
data: ContentDetails | null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type ContentDetails = {
|
||||||
|
slug: FullSlug
|
||||||
|
title: string
|
||||||
|
links: SimpleSlug[]
|
||||||
|
tags: string[]
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every function you can pass is optional. By default, only a `sort` function will be used:
|
||||||
|
|
||||||
|
```ts title="Default sort function"
|
||||||
|
// Sort order: folders first, then files. Sort folders and files alphabetically
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
sortFn: (a, b) => {
|
||||||
|
if ((!a.isFolder && !b.isFolder) || (a.isFolder && b.isFolder)) {
|
||||||
|
return a.displayName.localeCompare(b.displayName, undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!a.isFolder && b.isFolder) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can pass your own functions for `sortFn`, `filterFn` and `mapFn`. All functions will be executed in the order provided by the `order` option (see [[#Customization]]). These functions behave similarly to their `Array.prototype` counterpart, except they modify the entire `FileNode` tree in place instead of returning a new one.
|
||||||
|
|
||||||
|
For more information on how to use `sort`, `filter` and `map`, you can check [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) and [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
|
||||||
|
|
||||||
|
Type definitions look like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type SortFn = (a: FileTrieNode, b: FileTrieNode) => number
|
||||||
|
type FilterFn = (node: FileTrieNode) => boolean
|
||||||
|
type MapFn = (node: FileTrieNode) => void
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic examples
|
||||||
|
|
||||||
|
These examples show the basic usage of `sort`, `map` and `filter`.
|
||||||
|
|
||||||
|
### Use `sort` to put files first
|
||||||
|
|
||||||
|
Using this example, the explorer will alphabetically sort everything.
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
# Simple options go in YAML
|
||||||
|
title: Explorer
|
||||||
|
folderDefaultState: collapsed
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom sort functions require the TS override:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
sortFn: (a, b) => {
|
||||||
|
return a.displayName.localeCompare(b.displayName)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change display names (`map`)
|
||||||
|
|
||||||
|
Using this example, the display names of all `FileNodes` (folders + files) will be converted to full upper case.
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
mapFn: (node) => {
|
||||||
|
node.displayName = node.displayName.toUpperCase()
|
||||||
|
return node
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> The `mapFn`, `filterFn`, and `sortFn` options require JavaScript callback functions and cannot be expressed in YAML. Use the TS override for these.
|
||||||
|
|
||||||
|
### Remove list of elements (`filter`)
|
||||||
|
|
||||||
|
Using this example, you can remove elements from your explorer by providing an array of folders/files to exclude.
|
||||||
|
Note that this example filters on the title but you can also do it via slug or any other field available on `FileTrieNode`.
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
filterFn: (node) => {
|
||||||
|
// set containing names of everything you want to filter out
|
||||||
|
const omit = new Set(["authoring content", "tags", "advanced"])
|
||||||
|
|
||||||
|
// can also use node.slug or by anything on node.data
|
||||||
|
// note that node.data is only present for files that exist on disk
|
||||||
|
// (e.g. implicit folder nodes that have no associated index.md)
|
||||||
|
return !omit.has(node.displayName.toLowerCase())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove files by tag
|
||||||
|
|
||||||
|
You can access the tags of a file by `node.data.tags`.
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
filterFn: (node) => {
|
||||||
|
// exclude files with the tag "explorerexclude"
|
||||||
|
return node.data?.tags?.includes("explorerexclude") !== true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Show every element in explorer
|
||||||
|
|
||||||
|
By default, the explorer will filter out the `tags` folder.
|
||||||
|
To override the default filter function, you can set the filter function to `undefined`.
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
filterFn: undefined, // apply no filter function, every file and folder will visible
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced examples
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> When writing more complicated functions, the `quartz.ts` file can start to look very cramped.
|
||||||
|
> You can fix this by defining your sort functions outside of the component
|
||||||
|
> and passing it in.
|
||||||
|
>
|
||||||
|
> ```ts title="quartz.ts"
|
||||||
|
> import * as ExternalPlugin from "./.quartz/plugins"
|
||||||
|
> import type { ExplorerOptions } from "./.quartz/plugins"
|
||||||
|
>
|
||||||
|
> const mapFn: ExplorerOptions["mapFn"] = (node) => {
|
||||||
|
> // implement your function here
|
||||||
|
> }
|
||||||
|
> const filterFn: ExplorerOptions["filterFn"] = (node) => {
|
||||||
|
> // implement your function here
|
||||||
|
> }
|
||||||
|
> const sortFn: ExplorerOptions["sortFn"] = (a, b) => {
|
||||||
|
> // implement your function here
|
||||||
|
> }
|
||||||
|
>
|
||||||
|
> ExternalPlugin.Explorer({
|
||||||
|
> // ... your other options
|
||||||
|
> mapFn,
|
||||||
|
> filterFn,
|
||||||
|
> sortFn,
|
||||||
|
> })
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### Add emoji prefix
|
||||||
|
|
||||||
|
To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
ExternalPlugin.Explorer({
|
||||||
|
mapFn: (node) => {
|
||||||
|
if (node.isFolder) {
|
||||||
|
node.displayName = "📁 " + node.displayName
|
||||||
|
} else {
|
||||||
|
node.displayName = "📄 " + node.displayName
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Folder and Tag Listings
|
||||||
|
tags:
|
||||||
|
- feature/emitter
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz emits listing pages for any folders and tags you have.
|
||||||
|
|
||||||
|
## Folder Listings
|
||||||
|
|
||||||
|
Quartz will generate an index page for all the pages under that folder. This includes any content that is multiple levels deep.
|
||||||
|
|
||||||
|
Additionally, Quartz will also generate pages for subfolders. Say you have a note in a nested folder `content/abc/def/note.md`. Then Quartz would generate a page for all the notes under `abc` _and_ a page for all the notes under `abc/def`.
|
||||||
|
|
||||||
|
You can link to the folder listing by referencing its name, plus a trailing slash, like this: `[[advanced/]]` (results in [[advanced/]]).
|
||||||
|
|
||||||
|
By default, Quartz will title the page `Folder: <folder name>` and no description. You can override this by creating an `index.md` file in the folder with the `title` [[authoring content#Syntax|frontmatter]] field. Any content you write in this file will also be used in the folder description.
|
||||||
|
|
||||||
|
For example, for the folder `content/posts`, you can add another file `content/posts/index.md` to add a specific description for it.
|
||||||
|
|
||||||
|
## Tag Listings
|
||||||
|
|
||||||
|
Quartz will also create an index page for each unique tag in your vault and render a list of all notes with that tag.
|
||||||
|
|
||||||
|
Quartz also supports tag hierarchies as well (e.g. `plugin/emitter`) and will also render a separate tag page for each level of the tag hierarchy. It will also create a default global tag index page at `/tags` that displays a list of all the tags in your Quartz.
|
||||||
|
|
||||||
|
You can link to the tag listing by referencing its name with a `tag/` prefix, like this: `[[tags/plugin]]` (results in [[tags/plugin]]).
|
||||||
|
|
||||||
|
As with folder listings, you can also provide a description and title for a tag page by creating a file for each tag. For example, if you wanted to create a custom description for the #component tag, you would create a file at `content/tags/component.md` with a title and description.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Quartz allows you to define a custom sort ordering for content on both page types. The folder listings are a functionality of the [[FolderPage]] plugin, the tag listings of the [[TagPage]] plugin. See the plugin pages for customization options.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: Full-text Search
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Full-text search in Quartz is powered by [Flexsearch](https://github.com/nextapps-de/flexsearch). It's fast enough to return search results in under 10ms for Quartzs as large as half a million words.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
It can be opened by either clicking on the search bar or pressing `⌘`/`ctrl` + `K`. The top 5 search results are shown on each query. Matching subterms are highlighted and the most relevant 30 words are excerpted. Clicking on a search result will navigate to that page.
|
||||||
|
|
||||||
|
To search content by tags, you can either press `⌘`/`ctrl` + `shift` + `K` or start your query with `#` (e.g. `#components`).
|
||||||
|
|
||||||
|
This component is also keyboard accessible: Tab and Shift+Tab will cycle forward and backward through search results and Enter will navigate to the highlighted result (first result by default). You are also able to navigate search results using `ArrowUp` and `ArrowDown`.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> Search requires the `ContentIndex` emitter plugin to be present in the [[configuration]].
|
||||||
|
|
||||||
|
### Indexing Behaviour
|
||||||
|
|
||||||
|
By default, it indexes every page on the site with **Markdown syntax removed**. This means link URLs for instance are not indexed.
|
||||||
|
|
||||||
|
It properly tokenizes Chinese, Korean, and Japenese characters and constructs separate indexes for the title, content and tags, weighing title matches above content matches.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
- Removing search: remove the `search` entry from `quartz.config.yaml` or set `enabled: false`.
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/search`
|
||||||
|
- Source: [`quartz-community/search`](https://github.com/quartz-community/search)
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
title: "Graph View"
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz features a graph-view that can show both a local graph view and a global graph view.
|
||||||
|
|
||||||
|
- The local graph view shows files that either link to the current file or are linked from the current file. In other words, it shows all notes that are _at most_ one hop away.
|
||||||
|
- The global graph view can be toggled by clicking the graph icon on the top-right of the local graph view. It shows _all_ the notes in your graph and how they connect to each other.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> The Graph View is now a community plugin. This demonstrates how external plugins can extend Quartz functionality while serving as a reference implementation for plugin developers.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The Graph View is available as a community plugin from GitHub:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install github:quartz-community/graph --legacy-peer-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add it to your `quartz.config.yaml`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/graph
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
By default, the node radius is proportional to the total number of incoming and outgoing internal links from that file.
|
||||||
|
|
||||||
|
Additionally, similar to how browsers highlight visited links a different colour, the graph view will also show nodes that you have visited in a different colour.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> Graph View requires the `ContentIndex` emitter plugin to be present in the [[configuration]].
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Most configuration can be done by passing in options to `Graph()`.
|
||||||
|
|
||||||
|
For example, here's what the default configuration looks like:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/graph
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
localGraph:
|
||||||
|
drag: true
|
||||||
|
zoom: true
|
||||||
|
depth: 1
|
||||||
|
scale: 1.1
|
||||||
|
repelForce: 0.5
|
||||||
|
centerForce: 0.3
|
||||||
|
linkDistance: 30
|
||||||
|
fontSize: 0.6
|
||||||
|
opacityScale: 1
|
||||||
|
removeTags: []
|
||||||
|
showTags: true
|
||||||
|
enableRadial: false
|
||||||
|
globalGraph:
|
||||||
|
drag: true
|
||||||
|
zoom: true
|
||||||
|
depth: -1
|
||||||
|
scale: 0.9
|
||||||
|
repelForce: 0.5
|
||||||
|
centerForce: 0.3
|
||||||
|
linkDistance: 30
|
||||||
|
fontSize: 0.6
|
||||||
|
opacityScale: 1
|
||||||
|
removeTags: []
|
||||||
|
showTags: true
|
||||||
|
focusOnHover: true
|
||||||
|
enableRadial: true
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
|
||||||
|
|
||||||
|
Want to customize it even more?
|
||||||
|
|
||||||
|
- Removing graph view: remove the `graph` entry from `quartz.config.yaml` or set `enabled: false`
|
||||||
|
- Component source: https://github.com/quartz-community/graph
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: Internationalization
|
||||||
|
---
|
||||||
|
|
||||||
|
Internationalization allows users to translate text in the Quartz interface into various supported languages without needing to make extensive code changes. This can be changed via the `locale` [[configuration]] field in `quartz.config.yaml`.
|
||||||
|
|
||||||
|
The locale field generally follows a certain format: `{language}-{REGION}`
|
||||||
|
|
||||||
|
- `{language}` is usually a [2-letter lowercase language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
|
||||||
|
- `{REGION}` is usually a [2-letter uppercase region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
|
|
||||||
|
> [!tip] Interested in contributing?
|
||||||
|
> We [gladly welcome translation PRs](https://github.com/jackyzha0/quartz/tree/v5/quartz/i18n/locales)! To contribute a translation, do the following things:
|
||||||
|
>
|
||||||
|
> 1. In the `quartz/i18n/locales` folder, copy the `en-US.ts` file.
|
||||||
|
> 2. Rename it to `{language}-{REGION}.ts` so it matches a locale of the format shown above.
|
||||||
|
> 3. Fill in the translations!
|
||||||
|
> 4. Add the entry under `TRANSLATIONS` in `quartz/i18n/index.ts`.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: Feature List
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz comes with a wide variety of features out of the box. Most features are powered by [[configuration#Plugins|plugins]] that can be configured, enabled, or disabled via `quartz.config.yaml`.
|
||||||
|
|
||||||
|
## Content Features
|
||||||
|
|
||||||
|
- [[Obsidian compatibility]] — Full support for Obsidian-flavored Markdown
|
||||||
|
- [[wikilinks]] — Link between notes using `[[wikilinks]]` syntax
|
||||||
|
- [[callouts]] — Obsidian-style callout blocks
|
||||||
|
- [[features/Latex|Latex]] — LaTeX math rendering
|
||||||
|
- [[Mermaid diagrams]] — Diagram support via Mermaid
|
||||||
|
- [[syntax highlighting|Syntax highlighting]] — Code block highlighting with themes
|
||||||
|
- [[OxHugo compatibility]] — Support for ox-hugo Markdown
|
||||||
|
- [[Roam Research compatibility]] — Support for Roam Research syntax
|
||||||
|
- [[features/Citations|Citations]] — Academic citation support
|
||||||
|
- [[Canvas]] — Render Obsidian Canvas files as interactive pages
|
||||||
|
- [[Bases]] — Database-like views for your notes (tables, cards, galleries, and more)
|
||||||
|
|
||||||
|
## Navigation & Discovery
|
||||||
|
|
||||||
|
- [[full-text search]] — Search across all your notes
|
||||||
|
- [[graph view]] — Interactive graph visualization of note connections
|
||||||
|
- [[features/backlinks]] — See which notes link to the current page
|
||||||
|
- [[features/explorer]] — File tree sidebar for browsing notes
|
||||||
|
- [[features/breadcrumbs]] — Breadcrumb navigation trail
|
||||||
|
- [[table of contents]] — Per-page table of contents
|
||||||
|
- [[folder and tag listings]] — Browse notes by folder or tag
|
||||||
|
- [[recent notes]] — Display recently modified notes
|
||||||
|
- [[popover previews]] — Hover previews for internal links
|
||||||
|
- [[StackedPages|stacked pages]] — Andy Matuschak-style stacked sliding panes for tracing note connections
|
||||||
|
- [[EncryptedPages|encrypted pages]] — Password-protect individual pages with client-side encryption
|
||||||
|
|
||||||
|
## Appearance & Reading
|
||||||
|
|
||||||
|
- [[features/darkmode]] — Light and dark mode toggle
|
||||||
|
- [[reader mode]] — Distraction-free reading experience
|
||||||
|
- [[features/comments|comments]] — Add comments via Giscus, Utterances, or other providers
|
||||||
|
- [[social images]] — Auto-generated Open Graph images for social sharing
|
||||||
|
|
||||||
|
## Publishing & Deployment
|
||||||
|
|
||||||
|
- [[RSS Feed]] — RSS feed generation for content syndication
|
||||||
|
- [[private pages]] — Control which pages are published
|
||||||
|
- [[SPA Routing]] — Single-page app navigation
|
||||||
|
- [[Docker Support]] — Build and deploy with Docker
|
||||||
|
- [[i18n]] — Internationalization with 30+ supported locales
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: Popover Previews
|
||||||
|
---
|
||||||
|
|
||||||
|
Like Wikipedia, when you hover over a link in Quartz, there is a popup of a page preview that you can scroll to see the entire content. Links to headers will also scroll the popup to show that specific header in view.
|
||||||
|
|
||||||
|
By default, Quartz only fetches previews for pages inside your vault due to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). It does this by selecting all HTML elements with the `popover-hint` class. For most pages, this includes the page title, page metadata like words and time to read, tags, and the actual page content.
|
||||||
|
|
||||||
|
When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover.
|
||||||
|
|
||||||
|
Similar to Obsidian, [[quartz-layout-desktop.png|images referenced using wikilinks]] can also be viewed as popups.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Remove popovers: set the `enablePopovers` field in `quartz.config.yaml` to be `false`.
|
||||||
|
- Style: `quartz/components/styles/popover.scss`
|
||||||
|
- Script: `quartz/components/scripts/popover.inline.ts`
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
title: Private Pages
|
||||||
|
tags:
|
||||||
|
- feature/filter
|
||||||
|
---
|
||||||
|
|
||||||
|
There may be some notes you want to avoid publishing as a website. Quartz supports this through two mechanisms which can be used in conjunction:
|
||||||
|
|
||||||
|
## Filter Plugins
|
||||||
|
|
||||||
|
[[making plugins#Filters|Filter plugins]] are plugins that filter out content based off of certain criteria. By default, Quartz uses the [[RemoveDrafts]] plugin which filters out any note that has `draft: true` in the frontmatter.
|
||||||
|
|
||||||
|
If you'd like to only publish a select number of notes, you can instead use [[ExplicitPublish]] which will filter out all notes except for any that have `publish: true` in the frontmatter.
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc.
|
||||||
|
|
||||||
|
## `ignorePatterns`
|
||||||
|
|
||||||
|
This is a field in `quartz.config.yaml` under the main [[configuration]] which allows you to specify a list of patterns to effectively exclude from parsing all together. Any valid [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Bash's glob syntax is slightly different from fast-glob's and using bash's syntax may lead to unexpected results.
|
||||||
|
|
||||||
|
Common examples include:
|
||||||
|
|
||||||
|
- `some/folder`: exclude the entire of `some/folder`
|
||||||
|
- `*.md`: exclude all files with a `.md` extension
|
||||||
|
- `!(*.md)` exclude all files that _don't_ have a `.md` extension. Note that negations _must_ parenthesize the rest of the pattern!
|
||||||
|
- `**/private`: exclude any files or folders named `private` at any level of nesting
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Marking something as private via either a plugin or through the `ignorePatterns` pattern will only prevent a page from being included in the final built site. If your GitHub repository is public, also be sure to include an ignore for those in the `.gitignore` of your Quartz. See the `git` [documentation](https://git-scm.com/docs/gitignore#_pattern_format) for more information.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Reader Mode
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
---
|
||||||
|
|
||||||
|
Reader Mode is a feature that allows users to focus on the content by hiding the sidebars and other UI elements. When enabled, it provides a clean, distraction-free reading experience.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Reader Mode is enabled by default. To disable it, set `enabled: false` in your `quartz.config.yaml`:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/reader-mode
|
||||||
|
enabled: false
|
||||||
|
```
|
||||||
|
|
||||||
|
Or remove the plugin entirely:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin remove github:quartz-community/reader-mode
|
||||||
|
```
|
||||||
|
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/reader-mode`
|
||||||
|
- Source: [`quartz-community/reader-mode`](https://github.com/quartz-community/reader-mode)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The Reader Mode toggle appears as a button with a book icon. When clicked:
|
||||||
|
|
||||||
|
- Sidebars are hidden
|
||||||
|
- Hovering over the content area reveals the sidebars temporarily
|
||||||
|
|
||||||
|
Unlike Dark Mode, Reader Mode state is not persisted between page reloads but is maintained during SPA navigation within the site.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
You can customize the appearance of Reader Mode through CSS variables and styles. The component uses the following classes:
|
||||||
|
|
||||||
|
- `.readermode`: The toggle button
|
||||||
|
- `.readerIcon`: The book icon
|
||||||
|
- `[reader-mode="on"]`: Applied to the root element when Reader Mode is active
|
||||||
|
|
||||||
|
Example customization in your custom CSS:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.readermode {
|
||||||
|
// Customize the button
|
||||||
|
svg {
|
||||||
|
stroke: var(--custom-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Recent Notes
|
||||||
|
tags: component
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by installing the plugin and configuring it in `quartz.config.yaml`.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Most options are configured in the `options` section of the plugin entry in `quartz.config.yaml`:
|
||||||
|
|
||||||
|
- Changing the title from "Recent notes": set `title: "Recent writing"` in options
|
||||||
|
- Changing the number of recent notes: set `limit: 5` in options
|
||||||
|
- Display the note's tags (defaults to true): set `showTags: false` in options
|
||||||
|
- Hide generated tag pages from the list (defaults to false): set `hideTagPages: true` in options. This filters out any page whose slug lives under the conventional `tags/` prefix.
|
||||||
|
- Hide generated folder index pages from the list (defaults to false): set `hideFolderPages: true` in options. This filters out any page whose slug matches Quartz's folder-path convention (trailing slash or `index` suffix).
|
||||||
|
- Show a 'see more' link: set `linkToMore: "tags/components"` in options. This field should be a full slug to a page that exists.
|
||||||
|
- Customize filtering: requires a TS override — pass `filter: someFilterFunction` to the plugin constructor in `quartz.ts`. The filter function should have the signature `(f: QuartzPluginData) => boolean`.
|
||||||
|
- Customize sorting: requires a TS override — pass `sort: someSortFunction` to the plugin constructor in `quartz.ts`. By default, Quartz will sort by date and then tie break lexographically. The sort function should have the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`.
|
||||||
|
- Install: `npx quartz plugin add github:quartz-community/recent-notes`
|
||||||
|
- Source: [`quartz-community/recent-notes`](https://github.com/quartz-community/recent-notes)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: "Social Media Preview Cards"
|
||||||
|
---
|
||||||
|
|
||||||
|
A lot of social media platforms can display a rich preview for your website when sharing a link (most notably, a cover image, a title and a description).
|
||||||
|
|
||||||
|
Quartz can also dynamically generate and use new cover images for every page to be used in link previews on social media for you.
|
||||||
|
|
||||||
|
## Showcase
|
||||||
|
|
||||||
|
After enabling the [[CustomOgImages]] emitter plugin, the social media link preview for [[authoring content | Authoring Content]] looks like this:
|
||||||
|
|
||||||
|
| Light | Dark |
|
||||||
|
| ----------------------------------- | ---------------------------------- |
|
||||||
|
| ![[social-image-preview-light.png]] | ![[social-image-preview-dark.png]] |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This functionality is provided by the [[CustomOgImages]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
title: Syntax Highlighting
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Syntax highlighting in Quartz is completely done at build-time. This means that Quartz only ships pre-calculated CSS to highlight the right words so there is no heavy client-side bundle that does the syntax highlighting.
|
||||||
|
|
||||||
|
And, unlike some client-side highlighters, it has a full TextMate parser grammar instead of using Regexes, allowing for highly accurate code highlighting.
|
||||||
|
|
||||||
|
In short, it generates HTML that looks exactly like your code in an editor like VS Code. Under the hood, it's powered by [Rehype Pretty Code](https://rehype-pretty-code.netlify.app/) which uses [Shiki](https://github.com/shikijs/shiki).
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Syntax highlighting does have an impact on build speed if you have a lot of code snippets in your notes.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
Text inside `backticks` on a line will be formatted like code.
|
||||||
|
|
||||||
|
````
|
||||||
|
```ts
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
fp = clientSideSlug(fp)
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
fp = clientSideSlug(fp)
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Titles
|
||||||
|
|
||||||
|
Add a file title to your code block, with text inside double quotes (`""`):
|
||||||
|
|
||||||
|
````
|
||||||
|
```js title="..."
|
||||||
|
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```ts title="quartz/path.ts"
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
fp = clientSideSlug(fp)
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Line highlighting
|
||||||
|
|
||||||
|
Place a numeric range inside `{}`.
|
||||||
|
|
||||||
|
````
|
||||||
|
```js {1-3,4}
|
||||||
|
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```ts {2-3,6}
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
fp = clientSideSlug(fp)
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Word highlighting
|
||||||
|
|
||||||
|
A series of characters, like a literal regex.
|
||||||
|
|
||||||
|
````
|
||||||
|
```js /useState/
|
||||||
|
const [age, setAge] = useState(50);
|
||||||
|
const [name, setName] = useState('Taylor');
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```js /useState/
|
||||||
|
const [age, setAge] = useState(50)
|
||||||
|
const [name, setName] = useState("Taylor")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline Highlighting
|
||||||
|
|
||||||
|
Append {:lang} to the end of inline code to highlight it like a regular code block.
|
||||||
|
|
||||||
|
```
|
||||||
|
This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.
|
||||||
|
```
|
||||||
|
|
||||||
|
This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.
|
||||||
|
|
||||||
|
### Line numbers
|
||||||
|
|
||||||
|
Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`:
|
||||||
|
|
||||||
|
````
|
||||||
|
```js showLineNumbers{number}
|
||||||
|
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
```ts showLineNumbers{20}
|
||||||
|
export function trimPathSuffix(fp: string): string {
|
||||||
|
fp = clientSideSlug(fp)
|
||||||
|
let [cleanPath, anchor] = fp.split("#", 2)
|
||||||
|
anchor = anchor === undefined ? "" : "#" + anchor
|
||||||
|
|
||||||
|
return cleanPath + anchor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Escaping code blocks
|
||||||
|
|
||||||
|
You can format a codeblock inside of a codeblock by wrapping it with another level of backtick fences that has one more backtick than the previous fence.
|
||||||
|
|
||||||
|
`````
|
||||||
|
````
|
||||||
|
```js /useState/
|
||||||
|
const [age, setAge] = useState(50);
|
||||||
|
const [name, setName] = useState('Taylor');
|
||||||
|
```
|
||||||
|
````
|
||||||
|
`````
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Syntax highlighting is a functionality of the [[SyntaxHighlighting]] plugin. See the plugin page for customization options.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: "Table of Contents"
|
||||||
|
tags:
|
||||||
|
- component
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz can automatically generate a table of contents (TOC) from a list of headings on each page. It will also show you your current scrolling position on the page by highlighting headings you've scrolled through with a different color.
|
||||||
|
|
||||||
|
You can hide the TOC on a page by adding `enableToc: false` to the frontmatter for that page.
|
||||||
|
|
||||||
|
By default, the TOC shows all headings from H1 (`# Title`) to H3 (`### Title`) and is only displayed if there is more than one heading on the page.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
The table of contents is a functionality of the [[TableOfContents]] plugin. See the plugin page for more customization options.
|
||||||
|
|
||||||
|
It also needs the `TableOfContents` component, which is displayed in the right sidebar by default. You can change this by customizing the [[layout]]. The TOC component can be configured with the `layout` parameter, which can either be `modern` (default) or `legacy`.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: Wikilinks
|
||||||
|
---
|
||||||
|
|
||||||
|
Wikilinks were pioneered by earlier internet wikis to make it easier to write links across pages without needing to write Markdown or HTML links each time.
|
||||||
|
|
||||||
|
Quartz supports Wikilinks by default and these links are resolved by Quartz using the [[CrawlLinks]] plugin. See the [Obsidian Help page on Internal Links](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for more information on Wikilink syntax.
|
||||||
|
|
||||||
|
This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin.
|
||||||
|
|
||||||
|
Wikilink matching is case-insensitive to mirror Obsidian: `[[My Note]]`, `[[my note]]`, and `[[MY NOTE]]` all resolve to the same file. The generated URL is lowercased (e.g. `my-note`).
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
- `[[Path to file]]`: produces a link to `Path to file.md` (or `Path-to-file.md`) with the text `Path to file`
|
||||||
|
- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override`
|
||||||
|
- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md`
|
||||||
|
- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md`
|
||||||
|
|
||||||
|
### Embeds
|
||||||
|
|
||||||
|
- `![[Path to image]]`: embeds an image into the page
|
||||||
|
- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px
|
||||||
|
- `![[Path to file]]`: transclude an entire page
|
||||||
|
- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor`
|
||||||
|
- `![[Path to file#^b15695]]`: transclude block with ID `^b15695`
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: Authoring Content
|
||||||
|
aliases:
|
||||||
|
- "authoring content"
|
||||||
|
---
|
||||||
|
|
||||||
|
All of the content in your Quartz should go in the `/content` folder. The content for the home page of your Quartz lives in `content/index.md`. If you've followed the [[installation|installation guide]], this folder should already be initialized. Any Markdown in this folder will get processed by Quartz.
|
||||||
|
|
||||||
|
It is recommended that you use [Obsidian](https://obsidian.md/) as a way to edit and maintain your Quartz. It comes with a nice editor and graphical interface to preview, edit, and link your local files and attachments.
|
||||||
|
|
||||||
|
Got everything set up? Preview your site locally with `npx quartz build --serve`, or see the [[build|build reference]] for more options.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
As Quartz uses Markdown files as the main way of writing content, it fully supports Markdown syntax. By default, Quartz also ships with a few syntax extensions like [Github Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (footnotes, strikethrough, tables, tasklists) and [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown) ([[callouts]], [[wikilinks]]).
|
||||||
|
|
||||||
|
Additionally, Quartz also allows you to specify additional metadata in your notes called **frontmatter**.
|
||||||
|
|
||||||
|
```md title="content/note.md"
|
||||||
|
---
|
||||||
|
title: Example Title
|
||||||
|
draft: false
|
||||||
|
tags:
|
||||||
|
- example-tag
|
||||||
|
---
|
||||||
|
|
||||||
|
The rest of your content lives here. You can use **Markdown** here :)
|
||||||
|
```
|
||||||
|
|
||||||
|
Some common frontmatter fields that are natively supported by Quartz:
|
||||||
|
|
||||||
|
- `title`: Title of the page. If it isn't provided, Quartz will use the name of the file as the title.
|
||||||
|
- `description`: Description of the page used for link previews.
|
||||||
|
- `permalink`: A custom URL for the page that will remain constant even if the path to the file changes.
|
||||||
|
- `aliases`: Other names for this note. This is a list of strings.
|
||||||
|
- `tags`: Tags for this note.
|
||||||
|
- `draft`: Whether to publish the page or not. This is one way to make [[private pages|pages private]] in Quartz.
|
||||||
|
- `date`: A string representing the day the note was published. Normally uses `YYYY-MM-DD` format.
|
||||||
|
|
||||||
|
See [[Frontmatter]] for a complete list of frontmatter.
|
||||||
|
|
||||||
|
## Syncing your Content
|
||||||
|
|
||||||
|
When your Quartz is at a point you're happy with, you can save your changes to GitHub.
|
||||||
|
First, make sure you've [[installation#Setting Up Your GitHub Repository|set up your GitHub repository]] and then run `npx quartz sync`.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Frontmatter parsing for `title`, `tags`, `aliases` and `cssclasses` is a functionality of the [[Frontmatter]] plugin, `date` is handled by the [[CreatedModifiedDate]] plugin and `description` by the [[Description]] plugin. See the plugin pages for customization options.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide walks you through setting up Quartz from scratch. If you already ran the [[index#🪴 Get Started|quickstart]] on the home page, you can skip ahead to whichever step you're on.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You need these tools installed before continuing:
|
||||||
|
|
||||||
|
- **[Node.js](https://nodejs.org/) v22 or later** (run `node -v` to check)
|
||||||
|
- **npm v10.9.2 or later** (bundled with Node — run `npm -v` to check)
|
||||||
|
- **[Git](https://git-scm.com/)** (run `git -v` to check)
|
||||||
|
|
||||||
|
> [!warning] Common issues
|
||||||
|
>
|
||||||
|
> - **Linux**: System packages (`apt install nodejs`) often ship much older versions. Use [nvm](https://github.com/nvm-sh/nvm) or the [NodeSource](https://github.com/nodesource/distributions) repository to get Node.js v22.
|
||||||
|
> - **Windows**: When installing Git, make sure **"Git from the command line and also from 3rd-party software"** is selected so that `git` is available in your terminal. If `node -v` or `git -v` shows "command not found", restart your terminal or check your PATH.
|
||||||
|
> - **macOS**: The Xcode command-line tools include Git (`xcode-select --install`). For Node.js, [nvm](https://github.com/nvm-sh/nvm) or the [official installer](https://nodejs.org/) both work.
|
||||||
|
|
||||||
|
## Setup Steps
|
||||||
|
|
||||||
|
Follow these in order:
|
||||||
|
|
||||||
|
1. **[[installation|Installation]]** — Get Quartz (via GitHub template or clone), install dependencies, run the setup wizard (`npx quartz create`), install plugins, and preview your site locally
|
||||||
|
2. **[[authoring-content|Authoring Content]]** — Write and organize your Markdown notes in the `content/` folder
|
||||||
|
3. **[[installation#Setting Up Your GitHub Repository|Push to GitHub]]** — Create a repository and push your site with `npx quartz sync`
|
||||||
|
4. **[[hosting|Deploy]]** — Host your site for free on GitHub Pages, Cloudflare, Netlify, or Vercel
|
||||||
|
|
||||||
|
## Upgrading & Migrating
|
||||||
|
|
||||||
|
- **[[whats-new|What's New in Quartz 5]]** — Overview of new features and changes
|
||||||
|
- **[[upgrading|Upgrading Quartz]]** — Keep your Quartz installation up to date
|
||||||
|
- **[[migrating|Migrating to Quartz 5]]** — Migrate from Quartz 4 or Quartz 3
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
title: "Installation"
|
||||||
|
aliases:
|
||||||
|
- "setting up your GitHub repository"
|
||||||
|
---
|
||||||
|
|
||||||
|
This page walks you through the full Quartz setup: from getting the source code to previewing your site locally, then pushing it to GitHub.
|
||||||
|
|
||||||
|
## 1. Get Quartz
|
||||||
|
|
||||||
|
There are two ways to get started. Pick whichever you prefer:
|
||||||
|
|
||||||
|
### Option A: Use the GitHub Template (Recommended)
|
||||||
|
|
||||||
|
> [!tip] Why this option?
|
||||||
|
> Using the template creates your own repository in one click — no need to reconfigure Git remotes later.
|
||||||
|
|
||||||
|
1. Go to the [Quartz repository](https://github.com/jackyzha0/quartz) and click **Use this template** → **Create a new repository**
|
||||||
|
2. Give your repository a name (e.g. `quartz`, `notes`, `garden`), choose public or private, then click **Create repository**
|
||||||
|
3. Clone **your new repository** and enter the folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/<your-username>/<your-repo>.git
|
||||||
|
cd <your-repo>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Clone Directly
|
||||||
|
|
||||||
|
If you don't use GitHub or prefer a manual setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/jackyzha0/quartz.git
|
||||||
|
cd quartz
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> With this option, you'll need to [[#Connect Your Local Clone|point the `origin` remote]] to your own repository later when you're ready to publish.
|
||||||
|
|
||||||
|
## 2. Install Dependencies
|
||||||
|
|
||||||
|
> [!important]
|
||||||
|
> Quartz requires **Node.js 22** or later. Check your version with `node -v` and upgrade at [nodejs.org](https://nodejs.org/) if needed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> On subsequent clones of your own repository (e.g. on a new machine), use `npm ci` instead for a faster, reproducible install from the lockfile.
|
||||||
|
|
||||||
|
## 3. Initialize Your Site
|
||||||
|
|
||||||
|
Run the interactive setup wizard:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz create
|
||||||
|
```
|
||||||
|
|
||||||
|
This will prompt you for:
|
||||||
|
|
||||||
|
- A **template** (`default`, `obsidian`, `ttrpg`, `blog`) — pick the one that matches your use case. See [[create#Templates]] for details on each.
|
||||||
|
- A **content strategy** — choose how to populate the `content/` folder:
|
||||||
|
- **new**: Start with an empty folder
|
||||||
|
- **copy**: Copy files from an existing folder (e.g. your Obsidian vault)
|
||||||
|
- **symlink**: Link to an existing folder so changes sync automatically
|
||||||
|
- A **base URL** — the URL where your site will be deployed (e.g. `mysite.github.io/quartz`). Don't include `https://`.
|
||||||
|
- A **link resolution** strategy — how to resolve internal links (`shortest`, `absolute`, or `relative`). Skipped for Obsidian and TTRPG templates.
|
||||||
|
|
||||||
|
For non-interactive usage and more details, see the [[create|`quartz create` CLI reference]].
|
||||||
|
|
||||||
|
## 4. Install Plugins
|
||||||
|
|
||||||
|
The template you chose references community plugins that need to be installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads and builds all plugins listed in `quartz.config.yaml` into `.quartz/plugins/`.
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> If some plugins fail to build, try refreshing them to their latest versions:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> npx quartz plugin install --latest
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> See [[troubleshooting#Plugins fail to build on a fresh clone]] for more details.
|
||||||
|
|
||||||
|
## 5. Preview Your Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz build --serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Your site is now running at `http://localhost:8080`. The dev server watches for file changes and reloads automatically.
|
||||||
|
|
||||||
|
At this point you can [[authoring-content|start writing content]] in the `content/` folder. When you're ready to publish, continue below to push your site to GitHub and [[hosting|deploy it]].
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setting Up Your GitHub Repository
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> If you used **Option A** (GitHub Template) in step 1, your repository already exists and `origin` is already set. You can skip straight to [[#Push Your Site]].
|
||||||
|
|
||||||
|
To publish your site, you'll need your own GitHub repository. This section is for **Option B** (direct clone) users.
|
||||||
|
|
||||||
|
### Create the Repository
|
||||||
|
|
||||||
|
Create a new repository on [GitHub.com](https://github.com/new). Do **not** initialize it with a README, license, or `.gitignore` — Quartz already includes these files, and duplicating them will cause merge conflicts on your first push.
|
||||||
|
|
||||||
|
![[github-init-repo-options.png]]
|
||||||
|
|
||||||
|
Copy the repository URL from the Quick Setup page:
|
||||||
|
|
||||||
|
![[github-quick-setup.png]]
|
||||||
|
|
||||||
|
### Connect Your Local Clone
|
||||||
|
|
||||||
|
Point your local Quartz at your new repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check current remotes
|
||||||
|
git remote -v
|
||||||
|
|
||||||
|
# Point origin to your repository
|
||||||
|
git remote set-url origin REMOTE-URL
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> You don't need to add an `upstream` remote manually — `npx quartz create` already configured it for you. The upstream remote is used by `npx quartz upgrade` to pull in future Quartz updates.
|
||||||
|
|
||||||
|
### Push Your Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz sync --no-pull
|
||||||
|
```
|
||||||
|
|
||||||
|
This commits your content and pushes everything to your repository. For subsequent updates, just run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz sync
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!hint] Flags and options
|
||||||
|
> For full help options, you can run `npx quartz sync --help`.
|
||||||
|
>
|
||||||
|
> Most of these have sensible defaults but you can override them if you have a custom setup:
|
||||||
|
>
|
||||||
|
> - `-d` or `--directory`: the content folder. This is normally just `content`
|
||||||
|
> - `-v` or `--verbose`: print out extra logging information
|
||||||
|
> - `--commit` or `--no-commit`: whether to make a `git` commit for your changes
|
||||||
|
> - `--push` or `--no-push`: whether to push updates to your GitHub fork of Quartz
|
||||||
|
> - `--pull` or `--no-pull`: whether to try and pull in any updates from your GitHub fork (i.e. from other devices) before pushing
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- **[[authoring-content|Authoring Content]]** — Write and organize your notes
|
||||||
|
- **[[hosting|Hosting]]** — Deploy your site to GitHub Pages, Cloudflare, Netlify, or Vercel
|
||||||
|
- **[[configuration|Configuration]]** — Customize your site's appearance and behavior
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
---
|
||||||
|
title: "Migrating to Quartz 5"
|
||||||
|
aliases:
|
||||||
|
- "migrating from Quartz 3"
|
||||||
|
- "migrating from Quartz 4"
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide covers migrating to Quartz 5 from previous versions. If you're already on Quartz 5 and want to update to the latest version, see [[upgrading|Upgrading Quartz]] instead.
|
||||||
|
|
||||||
|
If you're new to Quartz entirely, skip this guide and follow the [[installation|installation guide]] instead.
|
||||||
|
|
||||||
|
## Before You Start: Save Your Content
|
||||||
|
|
||||||
|
Before switching branches, make sure your content is safe. Switching to v5 will replace the files in your working directory with the v5 codebase, so your v4 content folder won't be visible until you restore it.
|
||||||
|
|
||||||
|
Copy your content folder somewhere outside the repo before switching:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS / Linux
|
||||||
|
cp -r content /tmp/quartz-content
|
||||||
|
|
||||||
|
# Windows (PowerShell)
|
||||||
|
Copy-Item -Recurse content $env:TEMP\quartz-content
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note] Your old branch is preserved
|
||||||
|
> Switching branches does **not** delete your v4 (or v3/hugo) branch. You can always switch back with `git checkout v4` to access your old content and configuration.
|
||||||
|
|
||||||
|
## Getting the v5 Branch
|
||||||
|
|
||||||
|
Whether you're coming from Quartz 4 or Quartz 3, the first step is the same: get the v5 branch onto your machine and push it to your repository.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add the official Quartz repository as a remote called "upstream" (skip if already set)
|
||||||
|
git remote add upstream https://github.com/jackyzha0/quartz.git
|
||||||
|
|
||||||
|
# Fetch the v5 branch from the official repository
|
||||||
|
git fetch upstream v5
|
||||||
|
|
||||||
|
# Create a local v5 branch from the official one
|
||||||
|
git checkout -b v5 upstream/v5
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm i
|
||||||
|
|
||||||
|
# Push v5 to your GitHub repository
|
||||||
|
git push -u origin v5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting Up Your Site
|
||||||
|
|
||||||
|
Once you're on v5, run the interactive setup to configure your site and import your content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz create
|
||||||
|
```
|
||||||
|
|
||||||
|
This will prompt you for:
|
||||||
|
|
||||||
|
- A **template** (`default`, `obsidian`, `ttrpg`, `blog`) — pick the one closest to your old setup. `obsidian` is recommended if you use an Obsidian vault.
|
||||||
|
- A **content strategy** — choose "Copy" and point it to your backed-up content folder.
|
||||||
|
|
||||||
|
If you skipped the `create` wizard or need to restore your content manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS / Linux
|
||||||
|
cp -r /tmp/quartz-content/* content/
|
||||||
|
|
||||||
|
# Windows (PowerShell)
|
||||||
|
Copy-Item -Recurse $env:TEMP\quartz-content\* content\
|
||||||
|
```
|
||||||
|
|
||||||
|
After running `create`, install all plugins referenced in the generated config:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Changed in v5
|
||||||
|
|
||||||
|
Quartz 5 introduces a community plugin system that fundamentally changes how plugins and components are managed. Most plugins that were built into Quartz 4 are now standalone community plugins maintained under the [quartz-community](https://github.com/quartz-community) organization.
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
|
||||||
|
- **Configuration format**: TypeScript (`quartz.config.ts`, `quartz.layout.ts`) → YAML (`quartz.config.yaml`)
|
||||||
|
- **Plugin system**: Plugins are now standalone Git repositories, installed via `npx quartz plugin add`
|
||||||
|
- **Import pattern**: Community plugins use `ExternalPlugin.X()` (from `.quartz/plugins`) instead of `Plugin.X()` (from `./quartz/plugins`)
|
||||||
|
- **Layout structure**: `quartz.layout.ts` is gone — layout position is now a per-plugin property in `quartz.config.yaml`
|
||||||
|
- **Page types**: A new plugin category for page rendering (content, folder, tag pages)
|
||||||
|
- **URL casing**: All generated URLs are now lowercased and hyphenated (e.g. `My Notes/Hello World.md` → `/my-notes/hello-world`). In v4, the original casing of file and folder names was preserved in URLs.
|
||||||
|
|
||||||
|
### URL Casing and SEO
|
||||||
|
|
||||||
|
If your v4 site had URLs with uppercase letters, those URLs will return 404 errors after upgrading to v5. This also affects search engine indexing, since Google treats URLs as [case-sensitive](https://developers.google.com/search/docs/crawling-indexing/url-structure).
|
||||||
|
|
||||||
|
The [[AliasRedirects]] plugin (enabled by default) automatically handles this. During build, it detects files whose original path contained uppercase characters and generates redirect pages at the old URLs. These redirect pages include proper SEO signals (`<link rel="canonical">`, `<meta http-equiv="refresh">`, `<meta name="robots" content="noindex">`) so that search engines transfer ranking to the new lowercase URLs.
|
||||||
|
|
||||||
|
No manual configuration is needed — the plugin is enabled by default and the case redirect behavior is on by default. If you want to disable it, set `enableCaseRedirects: false` in the plugin options:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/alias-redirects
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
enableCaseRedirects: false
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip] Hosting on Netlify?
|
||||||
|
> Netlify automatically lowercases all URLs and issues server-side 301 redirects. If you're hosting on Netlify, the case redirect pages aren't strictly necessary, but they don't hurt either.
|
||||||
|
|
||||||
|
> [!note] Most users don't need to worry about these details
|
||||||
|
> If you used the default Quartz 4 configuration (or only changed settings that `npx quartz create` prompts for), the setup wizard handles everything. The details below are for users who had custom plugin configurations.
|
||||||
|
|
||||||
|
### Plugin Reference Table
|
||||||
|
|
||||||
|
Mapping v4 plugin names to v5 equivalents:
|
||||||
|
|
||||||
|
| v4 | v5 | Type |
|
||||||
|
| ----------------------------------- | ------------------------------------------- | --------------------- |
|
||||||
|
| `Plugin.FrontMatter()` | `ExternalPlugin.NoteProperties()` | Community |
|
||||||
|
| `Plugin.CreatedModifiedDate()` | `ExternalPlugin.CreatedModifiedDate()` | Community |
|
||||||
|
| `Plugin.SyntaxHighlighting()` | `ExternalPlugin.SyntaxHighlighting()` | Community |
|
||||||
|
| `Plugin.ObsidianFlavoredMarkdown()` | `ExternalPlugin.ObsidianFlavoredMarkdown()` | Community |
|
||||||
|
| `Plugin.GitHubFlavoredMarkdown()` | `ExternalPlugin.GitHubFlavoredMarkdown()` | Community |
|
||||||
|
| `Plugin.CrawlLinks()` | `ExternalPlugin.CrawlLinks()` | Community |
|
||||||
|
| `Plugin.Description()` | `ExternalPlugin.Description()` | Community |
|
||||||
|
| `Plugin.Latex()` | `ExternalPlugin.Latex()` | Community |
|
||||||
|
| `Plugin.RemoveDrafts()` | `ExternalPlugin.RemoveDrafts()` | Community |
|
||||||
|
| `Plugin.ContentPage()` | `ExternalPlugin.ContentPage()` | Community (pageTypes) |
|
||||||
|
| `Plugin.FolderPage()` | `ExternalPlugin.FolderPage()` | Community (pageTypes) |
|
||||||
|
| `Plugin.TagPage()` | `ExternalPlugin.TagPage()` | Community (pageTypes) |
|
||||||
|
| `Plugin.NotFoundPage()` | `Plugin.PageTypes.NotFoundPageType()` | Internal (pageTypes) |
|
||||||
|
| `Plugin.ComponentResources()` | `Plugin.ComponentResources()` (unchanged) | Internal |
|
||||||
|
| `Plugin.Assets()` | `Plugin.Assets()` (unchanged) | Internal |
|
||||||
|
| `Plugin.Static()` | `Plugin.Static()` (unchanged) | Internal |
|
||||||
|
| `Plugin.AliasRedirects()` | `ExternalPlugin.AliasRedirects()` | Community |
|
||||||
|
| `Plugin.ContentIndex()` | `ExternalPlugin.ContentIndex()` | Community |
|
||||||
|
|
||||||
|
Component layout mapping:
|
||||||
|
|
||||||
|
| v4 Layout | v5 Layout |
|
||||||
|
| ----------------------------- | ---------------------------------------- |
|
||||||
|
| `Component.Explorer()` | `Plugin.Explorer()` |
|
||||||
|
| `Component.Graph()` | `Plugin.Graph()` |
|
||||||
|
| `Component.Search()` | `Plugin.Search()` |
|
||||||
|
| `Component.Backlinks()` | `Plugin.Backlinks()` |
|
||||||
|
| `Component.Darkmode()` | `Plugin.Darkmode()` |
|
||||||
|
| `Component.Footer()` | `Plugin.Footer()` |
|
||||||
|
| `Component.TableOfContents()` | `Plugin.TableOfContents()` |
|
||||||
|
| `Component.Head()` | `Component.Head()` (unchanged, internal) |
|
||||||
|
| `Component.Spacer()` | `Plugin.Spacer()` |
|
||||||
|
|
||||||
|
## Updating Your CI/CD
|
||||||
|
|
||||||
|
Quartz 5 requires plugins to be installed before building. Add a plugin install step and (optionally) caching to your CI pipeline.
|
||||||
|
|
||||||
|
Here's the recommended pattern, based on the project's own GitHub Actions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Cache Quartz plugins
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: .quartz/plugins
|
||||||
|
key: ${{ runner.os }}-plugins-${{ hashFiles('quartz.lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-plugins-
|
||||||
|
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: Install Quartz plugins
|
||||||
|
run: npx quartz plugin install
|
||||||
|
|
||||||
|
- name: Build Quartz
|
||||||
|
run: npx quartz build
|
||||||
|
```
|
||||||
|
|
||||||
|
The plugin cache uses `quartz.lock.json` as the cache key, so plugins are only re-downloaded when the lockfile changes.
|
||||||
|
|
||||||
|
For non-GitHub CI providers (Cloudflare, Vercel, Netlify), the build command should be:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx quartz plugin install && npx quartz build
|
||||||
|
```
|
||||||
|
|
||||||
|
See [[hosting]] for provider-specific setup details.
|
||||||
|
|
||||||
|
## Setting Your Default Branch to v5
|
||||||
|
|
||||||
|
After verifying your site builds and deploys correctly, update your repository's default branch to `v5`:
|
||||||
|
|
||||||
|
1. Go to your repository on GitHub
|
||||||
|
2. Navigate to **Settings** → **General**
|
||||||
|
3. Under **Default branch**, click the switch icon next to your current default branch
|
||||||
|
4. Select `v5` from the dropdown and click **Update**
|
||||||
|
5. Confirm the change
|
||||||
|
|
||||||
|
This ensures that new clones, pull requests, and GitHub Pages deployments all target v5 by default. Your old v4 branch remains available for reference.
|
||||||
|
|
||||||
|
> [!warning] Update your CI triggers
|
||||||
|
> If your CI workflow triggers on a specific branch (e.g. `branches: [v4]`), make sure to update it to `v5`. See the [[hosting]] guide for examples.
|
||||||
|
|
||||||
|
## Notes for Quartz 3 Users
|
||||||
|
|
||||||
|
If you're coming from Quartz 3 (the Hugo-based version), follow the same steps above — get the v5 branch, run `npx quartz create`, and import your content. There is no need to go through Quartz 4 first.
|
||||||
|
|
||||||
|
### Key changes from Quartz 3
|
||||||
|
|
||||||
|
1. **Hugo is gone**: Quartz now uses a Node-based static-site generation process. No more Go templates or `hugo-obsidian`.
|
||||||
|
2. **Full hot-reload**: The development server (`npx quartz build --serve`) re-processes all content on every change.
|
||||||
|
3. **JSX instead of Go templates**: Layout components are written in JSX (JavaScript XML), which is significantly easier to customize.
|
||||||
|
4. **New plugin system**: See [[configuration#Plugins|Plugins]] for details on the extensible plugin architecture.
|
||||||
|
|
||||||
|
### Things to update
|
||||||
|
|
||||||
|
- Update your deploy scripts — see the [[hosting]] guide.
|
||||||
|
- Ensure your default branch on GitHub is updated to `v5`.
|
||||||
|
- [[folder and tag listings|Folder and tag listings]] have changed:
|
||||||
|
- Folder descriptions go under `content/<folder-name>/index.md`
|
||||||
|
- Tag descriptions go under `content/tags/<tag-name>.md`
|
||||||
|
- Custom CSS may need updates if you depended on specific HTML hierarchy or class names from Quartz 3.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: "Upgrading Quartz"
|
||||||
|
aliases:
|
||||||
|
- upgrading
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> This is specifically a guide for upgrading your Quartz to a more recent update. If you are coming from Quartz 4 or Quartz 3, check out the [[migrating|migration guide]] for more info.
|
||||||
|
|
||||||
|
To fetch the latest Quartz updates, simply run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
As Quartz uses [git](https://git-scm.com/) under the hood for versioning, upgrading effectively 'pulls' in the updates from the official Quartz GitHub repository. Merge conflicts in `quartz.lock.json` are handled automatically — Quartz backs up your lockfile before pulling and restores it afterward. For other files with local changes that conflict with the updates, you may need to resolve these manually yourself (or, pull manually using `git pull origin upstream`).
|
||||||
|
|
||||||
|
> [!hint]
|
||||||
|
> Quartz will try to cache your content before upgrading to try and prevent merge conflicts. If you get a conflict mid-merge, you can stop the merge and then run `npx quartz restore` to restore your content from the cache.
|
||||||
|
|
||||||
|
If you have the [GitHub desktop app](https://desktop.github.com/), this will automatically open to help you resolve the conflicts. Otherwise, you will need to resolve this in a text editor like VSCode. For more help on resolving conflicts manually, check out the [GitHub guide on resolving merge conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line#competing-line-change-merge-conflicts).
|
||||||
|
|
||||||
|
To update your installed plugins separately, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin install --latest
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [[upgrade|CLI reference for upgrade]] for more details on available flags.
|
||||||
|
|
||||||
|
### Cleaning Up Unused Plugins
|
||||||
|
|
||||||
|
If you've removed plugins from your configuration during an upgrade, you can clean up the leftover files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin prune --dry-run # preview what would be removed
|
||||||
|
npx quartz plugin prune # remove orphaned plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [[cli/plugin#prune|plugin prune reference]] for more details.
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
---
|
||||||
|
title: "What's New in Quartz 5"
|
||||||
|
aliases:
|
||||||
|
- "changelog"
|
||||||
|
- "v5"
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz 5 is a ground-up rearchitecture of Quartz focused on extensibility, performance, and Obsidian compatibility. If you're coming from v4, see [[migrating|Migrating to Quartz 5]] for the upgrade path.
|
||||||
|
|
||||||
|
## Plugin Ecosystem
|
||||||
|
|
||||||
|
The biggest change in v5 is the move to a **community plugin ecosystem**. Plugins are now standalone packages maintained in the [quartz-community](https://github.com/quartz-community) GitHub organization and installed via git:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx quartz plugin add github:quartz-community/explorer
|
||||||
|
```
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
- **Independent versioning**: Plugins can be updated without upgrading Quartz itself
|
||||||
|
- **Community contributions**: Anyone can publish a Quartz plugin
|
||||||
|
- **Smaller core**: Quartz core is leaner; features live in plugins
|
||||||
|
- **Plugin registry**: Discover plugins via `npx quartz tui` or the [plugin registry](https://github.com/quartz-community/registry)
|
||||||
|
|
||||||
|
Over 40 official plugins ship with Quartz, covering everything from search and graph view to encrypted pages and canvas rendering.
|
||||||
|
|
||||||
|
## YAML Configuration
|
||||||
|
|
||||||
|
Configuration moved from TypeScript (`quartz.config.ts`) to **YAML** (`quartz.config.yaml`):
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
configuration:
|
||||||
|
pageTitle: My Digital Garden
|
||||||
|
enableSPA: true
|
||||||
|
enablePopovers: true
|
||||||
|
locale: en-US
|
||||||
|
baseUrl: mysite.github.io
|
||||||
|
theme:
|
||||||
|
typography:
|
||||||
|
header: Schibsted Grotesk
|
||||||
|
body: Source Sans Pro
|
||||||
|
code: IBM Plex Mono
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/obsidian-flavored-markdown
|
||||||
|
enabled: true
|
||||||
|
order: 30
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 50
|
||||||
|
```
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
|
||||||
|
- **No TypeScript knowledge required** for basic customization
|
||||||
|
- **JSON Schema validation** — editors with YAML support show errors inline
|
||||||
|
- **Layout defined per-plugin** — each plugin declares its own position and priority
|
||||||
|
- **Templates** — `npx quartz create` offers preconfigured templates (default, obsidian, ttrpg, blog)
|
||||||
|
|
||||||
|
For advanced options that need JavaScript (callbacks, custom components), the `quartz.ts` override system provides full programmatic control.
|
||||||
|
|
||||||
|
## Improved Obsidian Compatibility
|
||||||
|
|
||||||
|
Quartz 5 aims for full compatibility with Obsidian's core features:
|
||||||
|
|
||||||
|
- **Wikilinks** — all variations including aliases, headings, block references, and pipe escaping in tables
|
||||||
|
- **Callouts** — all built-in types, collapsible variants, and nested callouts
|
||||||
|
- **Highlights** — `==highlighted text==` syntax
|
||||||
|
- **Comments** — `%%hidden comments%%` (inline and block)
|
||||||
|
- **Tags** — `#tag` and `#nested/tag` with tag pages
|
||||||
|
- **Custom task characters** — `[?]`, `[!]`, `[>]`, etc. preserved as `data-task` attributes
|
||||||
|
- **Mermaid diagrams** — rendered with expand button
|
||||||
|
- **YouTube and Tweet embeds** — via image syntax
|
||||||
|
- **Block references** — `^block-id` with broad character support
|
||||||
|
- **Video/audio embeds** — full format support (mp4, webm, ogv, mov, mkv, avi, flac, aac, etc.)
|
||||||
|
- **Canvas files** — rendered as interactive, pannable pages via the canvas-page plugin
|
||||||
|
- **Obsidian URI links** — marked with CSS class for custom styling
|
||||||
|
- **Footnotes** — via the GitHub Flavored Markdown plugin
|
||||||
|
|
||||||
|
See [[Obsidian compatibility]] for the full list.
|
||||||
|
|
||||||
|
## Page Type System
|
||||||
|
|
||||||
|
Quartz 5 introduces **page types** — plugins that define how different kinds of pages are rendered:
|
||||||
|
|
||||||
|
- **Content pages** — regular markdown notes
|
||||||
|
- **Folder pages** — directory listing pages
|
||||||
|
- **Tag pages** — pages listing notes with a given tag
|
||||||
|
- **Canvas pages** — interactive JSON Canvas renderings
|
||||||
|
- **Bases pages** — database-style views of your content
|
||||||
|
|
||||||
|
Each page type can use a different [[layout#Page Frames|page frame]] for fundamentally different HTML structures (three-column, full-width, minimal, etc.).
|
||||||
|
|
||||||
|
## Layout System
|
||||||
|
|
||||||
|
The layout system is now declarative. Plugins declare their position (`left`, `right`, `beforeBody`, `afterBody`) and priority in the config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 50
|
||||||
|
- source: github:quartz-community/graph
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional features:
|
||||||
|
|
||||||
|
- **Groups** — combine components into flex rows/columns (e.g., toolbar with search + darkmode toggle)
|
||||||
|
- **Conditional rendering** — show/hide components based on page properties (`condition: not-index`, `condition: has-tags`)
|
||||||
|
- **Display modifiers** — `display: mobile-only` or `display: desktop-only`
|
||||||
|
- **Per-page-type overrides** — different layouts for content, folder, tag, and 404 pages
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Parallel processing** — markdown parsing uses a worker pool across all CPU cores
|
||||||
|
- **Incremental rebuilds** — watch mode only re-processes changed files
|
||||||
|
- **Pre-built plugins** — community plugins ship compiled `dist/` directories, skipping build-from-source on install
|
||||||
|
- **SPA routing** — client-side navigation with `micromorph` for instant page transitions
|
||||||
|
- **CDN-cached fonts** — Google Fonts with aggressive caching, or fully self-hosted with `fontOrigin: local`
|
||||||
|
|
||||||
|
## CLI Improvements
|
||||||
|
|
||||||
|
The CLI is simpler and more helpful:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| -------------------------------- | --------------------------------------- |
|
||||||
|
| `npx quartz create` | Interactive setup wizard with templates |
|
||||||
|
| `npx quartz build --serve` | Build and serve with hot reload |
|
||||||
|
| `npx quartz sync` | Commit and push to GitHub |
|
||||||
|
| `npx quartz upgrade` | Pull latest Quartz updates |
|
||||||
|
| `npx quartz plugin install` | Install plugins from lockfile |
|
||||||
|
| `npx quartz plugin add <source>` | Add a new plugin |
|
||||||
|
| `npx quartz plugin list` | List installed plugins |
|
||||||
|
| `npx quartz plugin prune` | Remove unused plugins |
|
||||||
|
|
||||||
|
Other improvements:
|
||||||
|
|
||||||
|
- **Node.js version check** — clear error message if running on Node < 22
|
||||||
|
- **Port conflict handling** — helpful message when port is already in use
|
||||||
|
- **Plugin lockfile** — `quartz.lock.json` pins plugin versions for reproducible builds
|
||||||
|
- **Concurrency control** — `--concurrency` flag for memory-constrained environments
|
||||||
|
|
||||||
|
## Internationalization
|
||||||
|
|
||||||
|
Quartz 5 supports multiple locales out of the box. Set `locale: ja-JP` (or any supported locale) in your config to translate all UI strings — search placeholders, "table of contents", date formatting, and more.
|
||||||
|
|
||||||
|
## New Plugins
|
||||||
|
|
||||||
|
Plugins new to v5 (not available in v4):
|
||||||
|
|
||||||
|
```base
|
||||||
|
filters:
|
||||||
|
and:
|
||||||
|
- file.ext == "md"
|
||||||
|
- file.inFolder("plugins")
|
||||||
|
- note["new-in-v5"] == true
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
displayName: Plugin
|
||||||
|
repository:
|
||||||
|
displayName: Repository
|
||||||
|
description:
|
||||||
|
displayName: Description
|
||||||
|
views:
|
||||||
|
- type: table
|
||||||
|
name: New in v5
|
||||||
|
order:
|
||||||
|
- title
|
||||||
|
- repository
|
||||||
|
- description
|
||||||
|
sort:
|
||||||
|
- property: title
|
||||||
|
direction: ASC
|
||||||
|
```
|
||||||
|
|
||||||
|
## For Plugin Developers
|
||||||
|
|
||||||
|
If you built plugins for v4, the development model has changed significantly:
|
||||||
|
|
||||||
|
- Plugins are **standalone npm packages** with their own `package.json`, `tsconfig.json`, and build system
|
||||||
|
- The **factory function pattern** (inspired by Astro integrations) replaces class-based plugins
|
||||||
|
- **`@quartz-community/types`** provides full type safety without depending on the Quartz core
|
||||||
|
- **`@quartz-community/utils`** provides shared path, DOM, and language utilities
|
||||||
|
- **`@quartz-community/runtime`** provides browser runtime utilities
|
||||||
|
- Plugins can ship **components**, **frames**, **stylesheets**, and **client scripts**
|
||||||
|
- A **plugin template** is available at [quartz-community/plugin-template](https://github.com/quartz-community/plugin-template)
|
||||||
|
|
||||||
|
See [[making plugins]] for the full guide.
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
---
|
||||||
|
title: Hosting
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz effectively turns your Markdown files and other resources into a bundle of HTML, JS, and CSS files (a website!).
|
||||||
|
|
||||||
|
However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with common hosting providers but any service that allows you to deploy static HTML should work as well.
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, follow the [[installation#Setting Up Your GitHub Repository|GitHub repository setup]] section of the installation guide.
|
||||||
|
|
||||||
|
> [!hint]
|
||||||
|
> Some Quartz features (like [[RSS Feed]] and sitemap generation) require `baseUrl` to be configured properly in your [[configuration]] to work properly. Make sure you set this before deploying!
|
||||||
|
|
||||||
|
> [!tip] Keeping plugins in sync
|
||||||
|
> All hosting examples below use `npx quartz plugin install` to install plugins from the lockfile. If contributors may add plugins to `quartz.config.yaml` without updating the lockfile, add `npx quartz plugin install --from-config` after `install` in your build command to install any missing plugins. See [[cli/plugin#install|plugin install]] for details.
|
||||||
|
|
||||||
|
## Cloudflare Pages
|
||||||
|
|
||||||
|
1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com/) and select your account.
|
||||||
|
2. In Account Home, select **Compute (Workers)** > **Workers & Pages** > **Create application** > **Pages** > **Connect to Git**.
|
||||||
|
3. Select the new GitHub repository that you created and, in the **Set up builds and deployments** section, provide the following information:
|
||||||
|
|
||||||
|
| Configuration option | Value |
|
||||||
|
| ---------------------- | ----------------------------------------------- |
|
||||||
|
| Production branch | `v5` |
|
||||||
|
| Framework preset | `None` |
|
||||||
|
| Build command | `npx quartz plugin install && npx quartz build` |
|
||||||
|
| Build output directory | `public` |
|
||||||
|
|
||||||
|
Press "Save and deploy" and Cloudflare should have a deployed version of your site in about a minute. Then, every time you sync your Quartz changes to GitHub, your site should be updated.
|
||||||
|
|
||||||
|
To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/).
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz plugin install && npx quartz build`).
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For more detailed CI/CD configuration including caching and plugin management, see [[migrating#Updating Your CI/CD|the migration guide]].
|
||||||
|
|
||||||
|
## GitHub Pages
|
||||||
|
|
||||||
|
In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`.
|
||||||
|
|
||||||
|
```yaml title="quartz/.github/workflows/deploy.yml"
|
||||||
|
name: Deploy Quartz site to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- v5
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for git info
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
- name: Cache dependencies
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
- name: Cache Quartz plugins
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: .quartz/plugins
|
||||||
|
key: ${{ runner.os }}-plugins-${{ hashFiles('quartz.lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-plugins-
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Install Quartz plugins
|
||||||
|
run: npx quartz plugin install
|
||||||
|
- name: Build Quartz
|
||||||
|
run: npx quartz build
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: public
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
1. Head to "Settings" tab of your forked repository and in the sidebar, click "Pages". Under "Source", select "GitHub Actions".
|
||||||
|
2. Commit these changes by doing `npx quartz sync`. This should deploy your site to `<github-username>.github.io/<repository-name>`.
|
||||||
|
|
||||||
|
> [!hint]
|
||||||
|
> If you get an error about not being allowed to deploy to `github-pages` due to environment protection rules, make sure you remove any existing GitHub pages environments.
|
||||||
|
>
|
||||||
|
> You can do this by going to your Settings page on your GitHub fork and going to the Environments tab and pressing the trash icon. The GitHub action will recreate the environment for you correctly the next time you sync your Quartz.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]].
|
||||||
|
|
||||||
|
### Custom Domain
|
||||||
|
|
||||||
|
Here's how to add a custom domain to your GitHub pages deployment.
|
||||||
|
|
||||||
|
1. Head to the "Settings" tab of your forked repository.
|
||||||
|
2. In the "Code and automation" section of the sidebar, click "Pages".
|
||||||
|
3. Under "Custom Domain", type your custom domain and click "Save".
|
||||||
|
4. This next step depends on whether you are using an apex domain (`example.com`) or a subdomain (`subdomain.example.com`).
|
||||||
|
- If you are using an apex domain, navigate to your DNS provider and create an `A` record that points your apex domain to GitHub's name servers which have the following IP addresses:
|
||||||
|
- `185.199.108.153`
|
||||||
|
- `185.199.109.153`
|
||||||
|
- `185.199.110.153`
|
||||||
|
- `185.199.111.153`
|
||||||
|
- If you are using a subdomain, navigate to your DNS provider and create a `CNAME` record that points your subdomain to the default domain for your site. For example, if you want to use the subdomain `quartz.example.com` for your user site, create a `CNAME` record that points `quartz.example.com` to `<github-username>.github.io`.
|
||||||
|
|
||||||
|
![[dns records.png]]_The above shows a screenshot of Google Domains configured for both `jzhao.xyz` (an apex domain) and `quartz.jzhao.xyz` (a subdomain)._
|
||||||
|
|
||||||
|
See the [GitHub documentation](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-a-subdomain) for more detail about how to setup your own custom domain with GitHub Pages.
|
||||||
|
|
||||||
|
> [!question] Why aren't my changes showing up?
|
||||||
|
> There could be many different reasons why your changes aren't showing up but the most likely reason is that you forgot to push your changes to GitHub.
|
||||||
|
>
|
||||||
|
> Make sure you save your changes to Git and sync it to GitHub by doing `npx quartz sync`. This will also make sure to pull any updates you may have made from other devices so you have them locally.
|
||||||
|
|
||||||
|
## Vercel
|
||||||
|
|
||||||
|
### Fix URLs
|
||||||
|
|
||||||
|
Before deploying to Vercel, a `vercel.json` file is required at the root of the project directory. It needs to contain the following configuration so that URLs don't require the `.html` extension:
|
||||||
|
|
||||||
|
```json title="vercel.json"
|
||||||
|
{
|
||||||
|
"cleanUrls": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy to Vercel
|
||||||
|
|
||||||
|
1. Log in to the [Vercel Dashboard](https://vercel.com/dashboard) and click "Add New..." > Project
|
||||||
|
2. Import the Git repository containing your Quartz project.
|
||||||
|
3. Give the project a name (lowercase characters and hyphens only)
|
||||||
|
4. Check that these configuration options are set:
|
||||||
|
|
||||||
|
| Configuration option | Value |
|
||||||
|
| ----------------------------------------- | ----------------------------------------------- |
|
||||||
|
| Framework Preset | `Other` |
|
||||||
|
| Root Directory | `./` |
|
||||||
|
| Build and Output Settings > Build Command | `npx quartz plugin install && npx quartz build` |
|
||||||
|
|
||||||
|
5. Press Deploy. Once it's live, you'll have 2 `*.vercel.app` URLs to view the page.
|
||||||
|
|
||||||
|
### Custom Domain
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> If there is something already hosted on the domain, these steps will not work without replacing the previous content. As a workaround, you could use Next.js rewrites or use the next section to create a subdomain.
|
||||||
|
|
||||||
|
1. Update the `baseUrl` in `quartz.config.yaml` if necessary.
|
||||||
|
2. Go to the [Domains - Dashboard](https://vercel.com/dashboard/domains) page in Vercel.
|
||||||
|
3. Connect the domain to Vercel
|
||||||
|
4. Press "Add" to connect a custom domain to Vercel.
|
||||||
|
5. Select your Quartz repository and press Continue.
|
||||||
|
6. Enter the domain you want to connect it to.
|
||||||
|
7. Follow the instructions to update your DNS records until you see "Valid Configuration"
|
||||||
|
|
||||||
|
### Use a Subdomain
|
||||||
|
|
||||||
|
Using `docs.example.com` is an example of a subdomain. They're a simple way of connecting multiple deployments to one domain.
|
||||||
|
|
||||||
|
1. Update the `baseUrl` in `quartz.config.yaml` if necessary.
|
||||||
|
2. Ensure your domain has been added to the [Domains - Dashboard](https://vercel.com/dashboard/domains) page in Vercel.
|
||||||
|
3. Go to the [Vercel Dashboard](https://vercel.com/dashboard) and select your Quartz project.
|
||||||
|
4. Go to the Settings tab and then click Domains in the sidebar
|
||||||
|
5. Enter your subdomain into the field and press Add
|
||||||
|
|
||||||
|
## Netlify
|
||||||
|
|
||||||
|
1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site".
|
||||||
|
2. Select your Git provider and repository containing your Quartz project.
|
||||||
|
3. Under "Build command", enter `npx quartz plugin install && npx quartz build`.
|
||||||
|
4. Under "Publish directory", enter `public`.
|
||||||
|
5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page.
|
||||||
|
6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel.
|
||||||
|
|
||||||
|
## GitLab Pages
|
||||||
|
|
||||||
|
In your local Quartz, create a new file `.gitlab-ci.yml`.
|
||||||
|
|
||||||
|
```yaml title=".gitlab-ci.yml"
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
image: node:24
|
||||||
|
cache:
|
||||||
|
- key: npm-$CI_COMMIT_REF_SLUG
|
||||||
|
paths:
|
||||||
|
- .npm/
|
||||||
|
- key: plugins-$CI_COMMIT_REF_SLUG
|
||||||
|
paths:
|
||||||
|
- .quartz/plugins/
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_REF_NAME == "v5"'
|
||||||
|
before_script:
|
||||||
|
- hash -r
|
||||||
|
- npm ci --cache .npm --prefer-offline
|
||||||
|
script:
|
||||||
|
- npx quartz plugin install
|
||||||
|
- npx quartz build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
|
||||||
|
pages:
|
||||||
|
stage: deploy
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_REF_NAME == "v5"'
|
||||||
|
script:
|
||||||
|
- echo "Deploying to GitLab Pages..."
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- public
|
||||||
|
```
|
||||||
|
|
||||||
|
When `.gitlab-ci.yaml` is committed, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar.
|
||||||
|
|
||||||
|
By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`.
|
||||||
|
|
||||||
|
## Self-Hosting
|
||||||
|
|
||||||
|
Copy the `public` directory to your web server and configure it to serve the files. You can use any web server to host your site. Since Quartz generates links that do not include the `.html` extension, you need to let your web server know how to deal with it.
|
||||||
|
|
||||||
|
### Using Nginx
|
||||||
|
|
||||||
|
Here's an example of how to do this with Nginx:
|
||||||
|
|
||||||
|
```nginx title="nginx.conf"
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name example.com;
|
||||||
|
root /path/to/quartz/public;
|
||||||
|
index index.html;
|
||||||
|
error_page 404 /404.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri.html $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Apache
|
||||||
|
|
||||||
|
Here's an example of how to do this with Apache:
|
||||||
|
|
||||||
|
```apache title=".htaccess"
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
ErrorDocument 404 /404.html
|
||||||
|
|
||||||
|
# Rewrite rule for .html extension removal (with directory check)
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI}.html -f
|
||||||
|
RewriteRule ^(.*)$ $1.html [L]
|
||||||
|
|
||||||
|
# Handle directory requests explicitly
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^(.*)/$ $1/index.html [L]
|
||||||
|
```
|
||||||
|
|
||||||
|
Don't forget to activate brotli / gzip compression.
|
||||||
|
|
||||||
|
### Using Caddy
|
||||||
|
|
||||||
|
Here's and example of how to do this with Caddy:
|
||||||
|
|
||||||
|
```caddy title="Caddyfile"
|
||||||
|
example.com {
|
||||||
|
root * /path/to/quartz/public
|
||||||
|
try_files {path} {path}.html {path}/ =404
|
||||||
|
file_server
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
handle_errors {
|
||||||
|
rewrite * /{err.status_code}.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching
|
||||||
|
|
||||||
|
Quartz emits CSS and JS files with content hashes in their filenames (e.g. `index-a3f2c1b.css`, `component-7d4e2f.css`). Since the filename changes whenever the content changes, these files can be cached indefinitely. HTML files should not be cached long-term since they reference the hashed filenames and need to stay fresh.
|
||||||
|
|
||||||
|
### Cloudflare Pages / Vercel / Netlify
|
||||||
|
|
||||||
|
These platforms handle caching automatically. No configuration is needed — hashed assets will be served with appropriate cache headers out of the box.
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
|
||||||
|
```nginx title="nginx.conf"
|
||||||
|
# Immutable cache for hashed assets
|
||||||
|
location ~* \.(css|js)$ {
|
||||||
|
if ($uri ~* "-[0-9a-f]{8}\.") {
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caddy
|
||||||
|
|
||||||
|
```caddy title="Caddyfile"
|
||||||
|
@hashed path_regexp hashed -[0-9a-f]{8}\.(css|js)$
|
||||||
|
header @hashed Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apache
|
||||||
|
|
||||||
|
```apache title=".htaccess"
|
||||||
|
# Immutable cache for content-hashed assets
|
||||||
|
<FilesMatch "-[0-9a-f]{8}\.(css|js)$">
|
||||||
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
</FilesMatch>
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 572 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Welcome to Quartz 5
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, websites, and [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web.
|
||||||
|
|
||||||
|
## 🪴 Get Started
|
||||||
|
|
||||||
|
Quartz requires **at least [Node](https://nodejs.org/) v22** and `npm` v10.9.2 to function correctly. Ensure you have these installed on your machine before continuing. See the [[getting-started/index#Prerequisites|prerequisites]] for help installing them.
|
||||||
|
|
||||||
|
> [!tip] GitHub users
|
||||||
|
> You can also use the **[GitHub template](https://github.com/jackyzha0/quartz/generate)** to create your repository in one click, then clone that instead. See [[installation#Option A Use the GitHub Template Recommended|Option A]] in the installation guide.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 1. Clone the Quartz repository
|
||||||
|
git clone https://github.com/jackyzha0/quartz.git
|
||||||
|
cd quartz
|
||||||
|
|
||||||
|
# 2. Install dependencies
|
||||||
|
npm i
|
||||||
|
|
||||||
|
# 3. Initialize your site (choose a template, set your base URL, import content)
|
||||||
|
npx quartz create
|
||||||
|
|
||||||
|
# 4. Install plugins referenced by your chosen template
|
||||||
|
npx quartz plugin install --from-config
|
||||||
|
|
||||||
|
# 5. Preview your site locally
|
||||||
|
npx quartz build --serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Your site is now running at `http://localhost:8080`. From here:
|
||||||
|
|
||||||
|
- **[[authoring-content|Write content]]** in the `content/` folder
|
||||||
|
- **[[installation|Push to GitHub]]** with `npx quartz sync`
|
||||||
|
- **[[hosting|Deploy]]** to GitHub Pages, Cloudflare, Netlify, or Vercel
|
||||||
|
|
||||||
|
For the full walkthrough, see the [[getting-started/index|Getting Started]] guide.
|
||||||
|
|
||||||
|
### Returning User?
|
||||||
|
|
||||||
|
Already have a Quartz repository and cloning it on a new machine?
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/<your-username>/<your-repo>.git
|
||||||
|
cd <your-repo>
|
||||||
|
npm ci
|
||||||
|
npx quartz plugin install
|
||||||
|
npx quartz build --serve
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> If you hit build errors on a fresh clone, try `npx quartz plugin install --latest` to refresh plugins to their latest versions. See [[troubleshooting#Plugins fail to build on a fresh clone]] for details.
|
||||||
|
|
||||||
|
## 🔧 Features
|
||||||
|
|
||||||
|
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], [[wikilinks|wikilinks, transclusions]], [[plugins/Backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]], [[features/comments|comments]] and [many more](./features/) right out of the box
|
||||||
|
- Hot-reload on configuration edits and incremental rebuilds for content edits
|
||||||
|
- Simple JSX layouts and [[creating components|page components]]
|
||||||
|
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
|
||||||
|
- Fully-customizable parsing, filtering, and page generation through [[making plugins|plugins]]
|
||||||
|
|
||||||
|
For a comprehensive list of features, visit the [features page](./features/). You can read more about the _why_ behind these features on the [[philosophy]] page and a technical overview on the [[architecture]] page.
|
||||||
|
|
||||||
|
### 🚧 Troubleshooting + Updating
|
||||||
|
|
||||||
|
Having trouble with Quartz? Try searching for your issue using the search feature or check the [[troubleshooting]] page. If you haven't already, [[upgrading|upgrade]] to the newest version of Quartz to see if this fixes your issue.
|
||||||
|
|
||||||
|
If you're still having trouble, feel free to [submit an issue](https://github.com/jackyzha0/quartz/issues) if you feel you found a bug or ask for help in our [Discord Community](https://discord.gg/cRFFHYye7t). You can also browse the [[community]] page for third-party plugins and resources.
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
title: Higher-Order Layout Components
|
||||||
|
---
|
||||||
|
|
||||||
|
Quartz provides several higher-order components that help with layout composition and responsive design. These components wrap other components to add additional functionality or modify their behavior.
|
||||||
|
|
||||||
|
Most common use cases can be configured directly in `quartz.config.yaml` using layout properties. For advanced scenarios requiring custom logic, you can use the TS override approach in `quartz.ts`.
|
||||||
|
|
||||||
|
## `Flex` Component
|
||||||
|
|
||||||
|
The `Flex` component creates a [flexible box layout](https://developer.mozilla.org/en-US/docs/Web/CSS/flex) that can arrange child components in various ways. It's particularly useful for creating responsive layouts and organizing components in rows or columns.
|
||||||
|
|
||||||
|
### YAML Configuration
|
||||||
|
|
||||||
|
In YAML, flex layouts are created using **groups**. Define a group in the top-level `layout.groups` section, then assign plugins to that group via their `layout.group` property:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/search
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 20
|
||||||
|
group: toolbar
|
||||||
|
groupOptions:
|
||||||
|
grow: true # Search will grow to fill available space
|
||||||
|
- source: github:quartz-community/darkmode
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 30
|
||||||
|
group: toolbar # Darkmode keeps its natural size
|
||||||
|
- source: github:quartz-community/reader-mode
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 35
|
||||||
|
group: toolbar
|
||||||
|
|
||||||
|
layout:
|
||||||
|
groups:
|
||||||
|
toolbar:
|
||||||
|
direction: row
|
||||||
|
gap: 0.5rem
|
||||||
|
```
|
||||||
|
|
||||||
|
The `groupOptions` field on each plugin entry supports the following flex item properties:
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
| --------- | --------------------------------------------------------------- | --------------------------------------------------------- |
|
||||||
|
| `grow` | `boolean` | Whether the component should grow to fill available space |
|
||||||
|
| `shrink` | `boolean` | Whether the component should shrink if needed |
|
||||||
|
| `basis` | `string` | Initial main size of the component (e.g., `"200px"`) |
|
||||||
|
| `order` | `number` | Order in the flex container |
|
||||||
|
| `align` | `"start"` \| `"end"` \| `"center"` \| `"stretch"` | Cross-axis alignment |
|
||||||
|
| `justify` | `"start"` \| `"end"` \| `"center"` \| `"between"` \| `"around"` | Main-axis alignment |
|
||||||
|
|
||||||
|
The top-level `layout.groups` section configures the flex container itself:
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
| ----------- | -------------------------------------------------------------- | ----------------------------------------- |
|
||||||
|
| `direction` | `"row"` \| `"row-reverse"` \| `"column"` \| `"column-reverse"` | Flex direction |
|
||||||
|
| `wrap` | `"nowrap"` \| `"wrap"` \| `"wrap-reverse"` | Flex wrap behavior |
|
||||||
|
| `gap` | `string` | Gap between flex items (e.g., `"0.5rem"`) |
|
||||||
|
|
||||||
|
### TS Override
|
||||||
|
|
||||||
|
For full programmatic control, use the `Component.Flex()` wrapper in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
Component.Flex({
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
Component: Plugin.Search(),
|
||||||
|
grow: true, // Search will grow to fill available space
|
||||||
|
},
|
||||||
|
{ Component: Plugin.Darkmode() }, // Darkmode keeps its natural size
|
||||||
|
],
|
||||||
|
direction: "row",
|
||||||
|
gap: "1rem",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type FlexConfig = {
|
||||||
|
components: {
|
||||||
|
Component: QuartzComponent
|
||||||
|
grow?: boolean
|
||||||
|
shrink?: boolean
|
||||||
|
basis?: string
|
||||||
|
order?: number
|
||||||
|
align?: "start" | "end" | "center" | "stretch"
|
||||||
|
justify?: "start" | "end" | "center" | "between" | "around"
|
||||||
|
}[]
|
||||||
|
direction?: "row" | "row-reverse" | "column" | "column-reverse"
|
||||||
|
wrap?: "nowrap" | "wrap" | "wrap-reverse"
|
||||||
|
gap?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!note] Overriding behavior
|
||||||
|
> Components inside `Flex` get an additional CSS class `flex-component` that adds the `display: flex` property. If you want to override this behavior, you can add a `display` property to the component's CSS class in your custom CSS file.
|
||||||
|
>
|
||||||
|
> ```scss
|
||||||
|
> .flex-component {
|
||||||
|
> display: block; // or any other display type
|
||||||
|
> }
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## `MobileOnly` / `DesktopOnly` Components
|
||||||
|
|
||||||
|
These components control whether a plugin is visible on mobile or desktop devices. This is useful for creating responsive layouts where certain components should only appear on specific screen sizes.
|
||||||
|
|
||||||
|
### YAML Configuration
|
||||||
|
|
||||||
|
In YAML, use the `display` property on a plugin's layout entry:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/table-of-contents
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 20
|
||||||
|
display: desktop-only # Only visible on desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
Available `display` values:
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
| -------------- | ------------------------------------- |
|
||||||
|
| `all` | Visible on all screen sizes (default) |
|
||||||
|
| `mobile-only` | Only visible on mobile devices |
|
||||||
|
| `desktop-only` | Only visible on desktop devices |
|
||||||
|
|
||||||
|
### TS Override
|
||||||
|
|
||||||
|
For the TS override approach, use `Component.MobileOnly()` or `Component.DesktopOnly()` wrappers:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
Component.MobileOnly(Component.Spacer())
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
Component.DesktopOnly(Plugin.TableOfContents())
|
||||||
|
```
|
||||||
|
|
||||||
|
## `ConditionalRender` Component
|
||||||
|
|
||||||
|
The `ConditionalRender` component conditionally renders a plugin based on page properties. This is useful for creating dynamic layouts where components should only appear under certain conditions.
|
||||||
|
|
||||||
|
### YAML Configuration
|
||||||
|
|
||||||
|
In YAML, use the `condition` property on a plugin's layout entry. Quartz provides several built-in condition presets:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/breadcrumbs
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 5
|
||||||
|
condition: not-index # Hide breadcrumbs on the root index page
|
||||||
|
```
|
||||||
|
|
||||||
|
Available built-in conditions:
|
||||||
|
|
||||||
|
| Condition | Description |
|
||||||
|
| --------------- | ----------------------------------------------------- |
|
||||||
|
| `not-index` | Only render when the page is not the root `index.md` |
|
||||||
|
| `has-tags` | Only render when the page has tags in its frontmatter |
|
||||||
|
| `has-backlinks` | Only render when the page has backlinks |
|
||||||
|
| `has-toc` | Only render when the page has a table of contents |
|
||||||
|
|
||||||
|
### TS Override
|
||||||
|
|
||||||
|
For custom conditions that aren't covered by the built-in presets, use `Component.ConditionalRender()` in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts (override)"
|
||||||
|
Component.ConditionalRender({
|
||||||
|
component: Plugin.Search(),
|
||||||
|
condition: (props) => props.displayClass !== "fullpage",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type ConditionalRenderConfig = {
|
||||||
|
component: QuartzComponent
|
||||||
|
condition: (props: QuartzComponentProps) => boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> You can also register custom conditions for use in YAML by calling `registerCondition()` in a plugin's initialization code. See [[making plugins]] for more details.
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
---
|
||||||
|
title: Layout
|
||||||
|
---
|
||||||
|
|
||||||
|
Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page.
|
||||||
|
|
||||||
|
In v5, the layout is defined in `quartz.config.yaml`. Each plugin controls its own layout position via `layout.position` and `layout.priority` fields. The top-level `layout` section provides two additional mechanisms:
|
||||||
|
|
||||||
|
- `layout.groups` defines flex containers (like `toolbar`) that group multiple components into a single row or column. See [[layout-components]] for details.
|
||||||
|
- `layout.byPageType` contains per-page-type overrides (content, folder, tag, 404) for beforeBody, left, right sections, and optionally a `template` to control the page's [[#Page Frames|page frame]].
|
||||||
|
|
||||||
|
Each page is composed of multiple different sections which contain `QuartzComponents`. The following code snippet lists all of the valid sections that you can add components to:
|
||||||
|
|
||||||
|
```typescript title="quartz/cfg.ts"
|
||||||
|
export interface FullPageLayout {
|
||||||
|
head: QuartzComponent // single component
|
||||||
|
header: QuartzComponent[] // laid out horizontally
|
||||||
|
beforeBody: QuartzComponent[] // laid out vertically
|
||||||
|
pageBody: QuartzComponent // single component
|
||||||
|
afterBody: QuartzComponent[] // laid out vertically
|
||||||
|
left: QuartzComponent[] // vertical on desktop and tablet, horizontal on mobile
|
||||||
|
right: QuartzComponent[] // vertical on desktop, horizontal on tablet and mobile
|
||||||
|
footer: QuartzComponent // single component
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These correspond to following parts of the page:
|
||||||
|
|
||||||
|
| Layout | Preview |
|
||||||
|
| ------------------------------- | ----------------------------------- |
|
||||||
|
| Desktop (width > 1200px) | ![[quartz-layout-desktop.png\|800]] |
|
||||||
|
| Tablet (800px < width < 1200px) | ![[quartz-layout-tablet.png\|800]] |
|
||||||
|
| Mobile (width < 800px) | ![[quartz-layout-mobile.png\|800]] |
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> There are two additional layout fields that are _not_ shown in the above diagram.
|
||||||
|
>
|
||||||
|
> 1. `head` is a single component that renders the `<head>` [tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) in the HTML. This doesn't appear visually on the page and is only is responsible for metadata about the document like the tab title, scripts, and styles.
|
||||||
|
> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz doesn't place any components in the `header`.
|
||||||
|
|
||||||
|
Layout components are configured in the `layout` section of `quartz.config.yaml`. Plugins declare their position and priority, and the layout system arranges them automatically:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/explorer
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 50
|
||||||
|
- source: github:quartz-community/graph
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 10
|
||||||
|
- source: github:quartz-community/search
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: left
|
||||||
|
priority: 20
|
||||||
|
- source: github:quartz-community/backlinks
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: right
|
||||||
|
priority: 30
|
||||||
|
- source: github:quartz-community/article-title
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 10
|
||||||
|
- source: github:quartz-community/content-meta
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 20
|
||||||
|
- source: github:quartz-community/tag-list
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 30
|
||||||
|
- source: github:quartz-community/footer
|
||||||
|
enabled: true
|
||||||
|
options:
|
||||||
|
links:
|
||||||
|
GitHub: https://github.com/jackyzha0/quartz
|
||||||
|
Discord Community: https://discord.gg/cRFFHYye7t
|
||||||
|
|
||||||
|
layout:
|
||||||
|
groups:
|
||||||
|
toolbar:
|
||||||
|
direction: row
|
||||||
|
gap: 0.5rem
|
||||||
|
byPageType:
|
||||||
|
content: {}
|
||||||
|
folder:
|
||||||
|
exclude:
|
||||||
|
- reader-mode
|
||||||
|
positions:
|
||||||
|
right: []
|
||||||
|
tag:
|
||||||
|
exclude:
|
||||||
|
- reader-mode
|
||||||
|
positions:
|
||||||
|
right: []
|
||||||
|
"404":
|
||||||
|
positions:
|
||||||
|
beforeBody: []
|
||||||
|
left: []
|
||||||
|
right: []
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Rendering
|
||||||
|
|
||||||
|
Plugins can specify a `condition` in their layout block to control when they appear. This uses built-in presets:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
plugins:
|
||||||
|
- source: github:quartz-community/breadcrumbs
|
||||||
|
enabled: true
|
||||||
|
layout:
|
||||||
|
position: beforeBody
|
||||||
|
priority: 5
|
||||||
|
condition: not-index
|
||||||
|
```
|
||||||
|
|
||||||
|
Available conditions:
|
||||||
|
|
||||||
|
| Condition | Effect |
|
||||||
|
| ----------- | ---------------------------------------------------- |
|
||||||
|
| `not-index` | Hidden on the root index page, shown everywhere else |
|
||||||
|
| `has-tags` | Only shown on pages that have tags in frontmatter |
|
||||||
|
|
||||||
|
See [[layout-components]] for more details on conditional rendering and display options.
|
||||||
|
|
||||||
|
For advanced layout overrides using TypeScript (e.g. custom component wrappers or conditional logic), you can use the TS override in `quartz.ts`:
|
||||||
|
|
||||||
|
```ts title="quartz.ts"
|
||||||
|
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
|
||||||
|
|
||||||
|
const config = await loadQuartzConfig()
|
||||||
|
export default config
|
||||||
|
export const layout = await loadQuartzLayout({
|
||||||
|
defaults: {
|
||||||
|
// override default layout for all page types
|
||||||
|
},
|
||||||
|
byPageType: {
|
||||||
|
content: {
|
||||||
|
// override layout for content pages only
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
// override layout for folder pages only
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields defined in `defaults` can be overridden by specific entries in `byPageType`.
|
||||||
|
|
||||||
|
Community component plugins are installed via `npx quartz plugin add github:quartz-community/<name>`. See [[layout-components]] for built-in layout utilities (Flex, MobileOnly, DesktopOnly, etc.).
|
||||||
|
|
||||||
|
You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
|
||||||
|
|
||||||
|
### Page Frames
|
||||||
|
|
||||||
|
Page frames control the overall HTML structure of a page — specifically, how the layout slots (sidebars, header, content, footer) are arranged inside the page shell. Different page types can use different frames to produce fundamentally different layouts.
|
||||||
|
|
||||||
|
Quartz ships with three built-in frames:
|
||||||
|
|
||||||
|
| Frame | Description | Used by |
|
||||||
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||||
|
| `default` | Three-column layout with left sidebar, center content (header, beforeBody, content, afterBody), right sidebar, and footer. This is the standard Quartz layout. | ContentPage, FolderPage, TagPage, BasesPage |
|
||||||
|
| `full-width` | No sidebars. Single center column spanning the full width with header, content, afterBody, and footer. | — |
|
||||||
|
| `minimal` | No sidebars, no header or beforeBody chrome. Only content and footer. | NotFoundPage (404) |
|
||||||
|
|
||||||
|
Plugins can also provide their own frames. For example, the `canvas-page` plugin ships a `"canvas"` frame that provides a fullscreen canvas with a togglable sidebar.
|
||||||
|
|
||||||
|
#### How frames are resolved
|
||||||
|
|
||||||
|
Each page type can declare a default frame in its plugin source code via the `frame` property. The resolution order is:
|
||||||
|
|
||||||
|
1. **YAML config override**: `layout.byPageType.<name>.template` in `quartz.config.yaml`
|
||||||
|
2. **Plugin-registered frame**: Frames registered by plugins via the Frame Registry (loaded from the plugin's `frames` export)
|
||||||
|
3. **Plugin declaration**: The `frame` property set in the page type plugin's source code
|
||||||
|
4. **Fallback**: `"default"`
|
||||||
|
|
||||||
|
For example, to override canvas pages to use the minimal frame:
|
||||||
|
|
||||||
|
```yaml title="quartz.config.yaml"
|
||||||
|
layout:
|
||||||
|
byPageType:
|
||||||
|
canvas:
|
||||||
|
template: minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom frames
|
||||||
|
|
||||||
|
There are two ways to provide custom frames:
|
||||||
|
|
||||||
|
**1. Plugin-provided frames (recommended for reusable frames):**
|
||||||
|
|
||||||
|
Plugins can ship their own frames by declaring them in `package.json` and exporting them from a `./frames` subpath. See [[making plugins#Providing Custom Frames|the plugin guide]] for details. When a plugin with frames is installed, its frames are automatically registered in the Frame Registry and available by name.
|
||||||
|
|
||||||
|
**2. Core frames (for project-specific frames):**
|
||||||
|
|
||||||
|
You can also create frames directly in `quartz/components/frames/` by implementing the `PageFrame` interface and registering the frame in `quartz/components/frames/index.ts`. See the [[architecture|architecture overview]] for the full `PageFrame` interface.
|
||||||
|
|
||||||
|
Frames are applied as a `data-frame` attribute on the `.page` element, which you can target in CSS:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.page[data-frame="my-frame"] > #quartz-body {
|
||||||
|
/* custom grid layout */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Frame CSS should be scoped with `[data-frame="name"]` selectors to avoid conflicts with other frames.
|
||||||
|
|
||||||
|
### Layout breakpoints
|
||||||
|
|
||||||
|
Quartz has different layouts depending on the width the screen viewing the website.
|
||||||
|
|
||||||
|
The breakpoints for layouts can be configured in `variables.scss`.
|
||||||
|
|
||||||
|
- `mobile`: screen width below this size will use mobile layout.
|
||||||
|
- `desktop`: screen width above this size will use desktop layout.
|
||||||
|
- Screen width between `mobile` and `desktop` width will use the tablet layout.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
$breakpoints: (
|
||||||
|
mobile: 800px,
|
||||||
|
desktop: 1200px,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Style
|
||||||
|
|
||||||
|
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz uses [Sass](https://sass-lang.com/guide/) for styling.
|
||||||
|
|
||||||
|
You can see the base style sheet in `quartz/styles/base.scss` and write your own in `quartz/styles/custom.scss`.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> Some components may provide their own styling as well! Community plugins bundle their own styles. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined.
|
||||||