serve: use maud for HTML templating
Replace tera runtime templates with maud's compile-time HTML macro DSL.
Removes the templates/ directory — all markup is now type-checked Rust
code with automatic escaping. Maud's axum integration provides direct
Markup responses without manual Html wrapping.
diff --git a/Cargo.lock b/Cargo.lock
index 9b4c46f..af0b1ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -32,15 +32,6 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
-[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "anstream"
version = "0.6.21"
@@ -241,12 +232,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "bumpalo"
-version = "3.20.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
-
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -275,39 +260,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-[[package]]
-name = "chrono"
-version = "0.4.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
-dependencies = [
- "iana-time-zone",
- "num-traits",
- "windows-link",
-]
-
-[[package]]
-name = "chrono-tz"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
-dependencies = [
- "chrono",
- "chrono-tz-build",
- "phf",
-]
-
-[[package]]
-name = "chrono-tz-build"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
-dependencies = [
- "parse-zoneinfo",
- "phf",
- "phf_codegen",
-]
-
[[package]]
name = "clap"
version = "4.5.60"
@@ -408,12 +360,6 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
-[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -438,25 +384,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
-[[package]]
-name = "crossbeam-deque"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
-dependencies = [
- "crossbeam-epoch",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
-dependencies = [
- "crossbeam-utils",
-]
-
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
@@ -493,12 +420,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "deunicode"
-version = "1.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
-
[[package]]
name = "difflib"
version = "0.4.0"
@@ -749,30 +670,6 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-[[package]]
-name = "globset"
-version = "0.4.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
-dependencies = [
- "aho-corasick",
- "bstr",
- "log",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "globwalk"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
-dependencies = [
- "bitflags",
- "ignore",
- "walkdir",
-]
-
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -883,15 +780,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
-[[package]]
-name = "humansize"
-version = "2.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
-dependencies = [
- "libm",
-]
-
[[package]]
name = "hyper"
version = "1.8.1"
@@ -928,30 +816,6 @@ dependencies = [
"tower-service",
]
-[[package]]
-name = "iana-time-zone"
-version = "0.1.65"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "icu_collections"
version = "2.1.1"
@@ -1060,22 +924,6 @@ dependencies = [
"icu_properties",
]
-[[package]]
-name = "ignore"
-version = "0.4.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
-dependencies = [
- "crossbeam-deque",
- "globset",
- "log",
- "memchr",
- "regex-automata",
- "same-file",
- "walkdir",
- "winapi-util",
-]
-
[[package]]
name = "indenter"
version = "0.3.4"
@@ -1156,16 +1004,6 @@ dependencies = [
"jiff-tzdb",
]
-[[package]]
-name = "js-sys"
-version = "0.3.91"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1258,6 +1096,30 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+[[package]]
+name = "maud"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e"
+dependencies = [
+ "axum-core",
+ "http",
+ "itoa",
+ "maud_macros",
+]
+
+[[package]]
+name = "maud_macros"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca"
+dependencies = [
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "md-5"
version = "0.10.6"
@@ -1417,15 +1279,6 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "parse-zoneinfo"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
-dependencies = [
- "regex",
-]
-
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -1441,87 +1294,6 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-[[package]]
-name = "pest"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
-dependencies = [
- "memchr",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
-dependencies = [
- "pest",
- "sha2",
-]
-
-[[package]]
-name = "phf"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
-dependencies = [
- "phf_shared",
-]
-
-[[package]]
-name = "phf_codegen"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
-dependencies = [
- "phf_generator",
- "phf_shared",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
-dependencies = [
- "phf_shared",
- "rand",
-]
-
-[[package]]
-name = "phf_shared"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
-dependencies = [
- "siphasher",
-]
-
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -1649,6 +1421,18 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
[[package]]
name = "quote"
version = "1.0.44"
@@ -1704,13 +1488,13 @@ dependencies = [
"clap_complete",
"color-eyre",
"jiff",
+ "maud",
"predicates",
"rand",
"serde",
"serde_json",
"sqlx",
"tempfile",
- "tera",
"thiserror",
"tokio",
"tracing-subscriber",
@@ -1803,27 +1587,12 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1959,28 +1728,12 @@ dependencies = [
"rand_core",
]
-[[package]]
-name = "siphasher"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
-
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
-[[package]]
-name = "slug"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
-dependencies = [
- "deunicode",
- "wasm-bindgen",
-]
-
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -2277,28 +2030,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "tera"
-version = "1.20.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722"
-dependencies = [
- "chrono",
- "chrono-tz",
- "globwalk",
- "humansize",
- "lazy_static",
- "percent-encoding",
- "pest",
- "pest_derive",
- "rand",
- "regex",
- "serde",
- "serde_json",
- "slug",
- "unicode-segmentation",
-]
-
[[package]]
name = "termtree"
version = "0.5.1"
@@ -2504,12 +2235,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
-[[package]]
-name = "ucd-trie"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
-
[[package]]
name = "unicode-bidi"
version = "0.3.18"
@@ -2537,12 +2262,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
-[[package]]
-name = "unicode-segmentation"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
-
[[package]]
name = "unicode-xid"
version = "0.2.6"
@@ -2600,16 +2319,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -2640,51 +2349,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.114"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.114"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.114"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
-dependencies = [
- "bumpalo",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.114"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
-dependencies = [
- "unicode-ident",
-]
-
[[package]]
name = "wasm-encoder"
version = "0.244.0"
@@ -2729,74 +2393,12 @@ dependencies = [
"wasite",
]
-[[package]]
-name = "winapi-util"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.62.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-[[package]]
-name = "windows-result"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
-dependencies = [
- "windows-link",
-]
-
[[package]]
name = "windows-sys"
version = "0.48.0"
diff --git a/Cargo.toml b/Cargo.toml
index 4308770..89c9067 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,7 @@ rand = "*"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
sqlx = { version = "*", features = ["runtime-tokio", "sqlite", "migrate"] }
-tera = "*"
+maud = { version = "*", features = ["axum"] }
thiserror = "*"
tokio = { version = "*", features = ["full"] }
xdg = "*"
diff --git a/src/bin/ranger/commands/serve.rs b/src/bin/ranger/commands/serve.rs
index fb76d05..280a7a6 100644
--- a/src/bin/ranger/commands/serve.rs
+++ b/src/bin/ranger/commands/serve.rs
@@ -1,56 +1,29 @@
use axum::extract::State;
use axum::http::header;
-use axum::response::{Html, IntoResponse};
+use axum::response::IntoResponse;
use axum::{Router, routing::get};
+use maud::{DOCTYPE, Markup, html};
use ranger::key;
use ranger::models::Task;
use ranger::ops;
use ranger::ops::task::ListFilter;
-use serde::Serialize;
use sqlx::SqlitePool;
use std::net::SocketAddr;
-use std::sync::Arc;
-use tera::{Context, Tera};
use tokio::net::TcpListener;
/// Static CSS embedded at compile time from `static/style.css`.
const STYLE_CSS: &str = include_str!("../../../../static/style.css");
-/// Raw template strings embedded at compile time.
-const TEMPLATES: &[(&str, &str)] = &[
- ("base.html", include_str!("../../../../templates/base.html")),
- (
- "board.html",
- include_str!("../../../../templates/board.html"),
- ),
- (
- "panels/backlog.html",
- include_str!("../../../../templates/panels/backlog.html"),
- ),
- (
- "panels/column.html",
- include_str!("../../../../templates/panels/column.html"),
- ),
- ("task.html", include_str!("../../../../templates/task.html")),
-];
-
#[derive(Clone)]
struct AppState {
pool: SqlitePool,
backlog_name: String,
- tera: Arc<Tera>,
}
pub async fn run(pool: &SqlitePool, port: u16, backlog_name: String) -> color_eyre::Result<()> {
- let mut tera = Tera::default();
- for &(name, content) in TEMPLATES {
- tera.add_raw_template(name, content)?;
- }
-
let state = AppState {
pool: pool.clone(),
backlog_name,
- tera: Arc::new(tera),
};
let app = Router::new()
@@ -71,16 +44,20 @@ async fn serve_css() -> impl IntoResponse {
([(header::CONTENT_TYPE, "text/css")], STYLE_CSS)
}
-async fn index(State(state): State<AppState>) -> Html<String> {
+async fn index(State(state): State<AppState>) -> Markup {
match render_board(&state).await {
- Ok(html) => Html(html),
- Err(e) => Html(format!(
- "<html><body><h1>Error</h1><pre>{e}</pre></body></html>"
- )),
+ Ok(markup) => markup,
+ Err(e) => html! {
+ html {
+ body {
+ h1 { "Error" }
+ pre { (e) }
+ }
+ }
+ },
}
}
-#[derive(Serialize)]
struct TaskView {
key_prefix: String,
key_rest: String,
@@ -91,14 +68,7 @@ struct TaskView {
done_subtask_count: usize,
}
-#[derive(Serialize)]
-struct ColumnView {
- label: String,
- state_class: String,
- tasks: Vec<TaskView>,
-}
-
-async fn render_board(state: &AppState) -> color_eyre::Result<String> {
+async fn render_board(state: &AppState) -> color_eyre::Result<Markup> {
let mut conn = state.pool.acquire().await?;
let backlog = ops::backlog::get_by_name(&mut conn, &state.backlog_name).await?;
let all_keys = ops::task::keys_for_backlog(&mut conn, backlog.id).await?;
@@ -131,29 +101,109 @@ async fn render_board(state: &AppState) -> color_eyre::Result<String> {
let total = in_progress.len() + queued.len() + icebox.len() + done.len();
let active = in_progress.len() + queued.len();
+ let backlog_name = &state.backlog_name;
+
+ Ok(html! {
+ (DOCTYPE)
+ html lang="en" {
+ head {
+ meta charset="utf-8";
+ meta name="viewport" content="width=device-width, initial-scale=1";
+ title { "ranger › " (backlog_name) }
+ link rel="stylesheet" href="/static/style.css";
+ }
+ body {
+ header {
+ h1 { "ranger" span.sep { "›" } (backlog_name) }
+ div.counts { (active) " active · " (total) " total" }
+ }
+ div.board {
+ (render_backlog_panel(&in_progress, &queued))
+ (render_column_panel("Icebox", "state-icebox", &icebox))
+ (render_column_panel("Done", "state-done", &done))
+ }
+ }
+ }
+ })
+}
- let columns = vec![
- ColumnView {
- label: "Icebox".to_string(),
- state_class: "state-icebox".to_string(),
- tasks: icebox,
- },
- ColumnView {
- label: "Done".to_string(),
- state_class: "state-done".to_string(),
- tasks: done,
- },
- ];
+fn render_backlog_panel(in_progress: &[TaskView], queued: &[TaskView]) -> Markup {
+ let count = in_progress.len() + queued.len();
+ html! {
+ div.panel {
+ div.panel-header {
+ h2 { "Backlog" }
+ span.count { (count) }
+ }
+ @if in_progress.is_empty() && queued.is_empty() {
+ div.empty { "No active tasks" }
+ } @else {
+ @if !in_progress.is_empty() {
+ div.section-label.section-label-in-progress {
+ span.dot { "●" } " In Progress"
+ }
+ div.state-in-progress {
+ @for task in in_progress {
+ (render_task(task))
+ }
+ }
+ }
+ @if !queued.is_empty() {
+ div.section-label.section-label-queued {
+ span.dot { "●" } " Queued"
+ }
+ div.state-queued {
+ @for task in queued {
+ (render_task(task))
+ }
+ }
+ }
+ }
+ }
+ }
+}
- let mut context = Context::new();
- context.insert("backlog_name", &state.backlog_name);
- context.insert("active", &active);
- context.insert("total", &total);
- context.insert("in_progress", &in_progress);
- context.insert("queued", &queued);
- context.insert("columns", &columns);
+fn render_column_panel(label: &str, state_class: &str, tasks: &[TaskView]) -> Markup {
+ let count = tasks.len();
+ html! {
+ div.panel {
+ div.panel-header {
+ h2 { (label) }
+ span.count { (count) }
+ }
+ @if tasks.is_empty() {
+ div.empty { "No " (label.to_lowercase()) " tasks" }
+ } @else {
+ div class=(state_class) {
+ @for task in tasks {
+ (render_task(task))
+ }
+ }
+ }
+ }
+ }
+}
- Ok(state.tera.render("board.html", &context)?)
+fn render_task(task: &TaskView) -> Markup {
+ html! {
+ div.task {
+ div.task-header {
+ span.key {
+ span.key-prefix { (task.key_prefix) }
+ span.key-rest { (task.key_rest) }
+ }
+ span.title { (task.title) }
+ }
+ @if let Some(desc) = &task.description {
+ div.desc { (desc) }
+ }
+ @if task.has_subtasks {
+ div.subtask-indicator {
+ "◆ " (task.done_subtask_count) "/" (task.subtask_count) " subtasks"
+ }
+ }
+ }
+ }
}
async fn to_task_views(
diff --git a/templates/base.html b/templates/base.html
deleted file mode 100644
index b789588..0000000
--- a/templates/base.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-<meta charset="utf-8">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-<title>ranger › {{ backlog_name }}</title>
-<link rel="stylesheet" href="/static/style.css">
-</head>
-<body>
-{% block body %}{% endblock body %}
-</body>
-</html>
diff --git a/templates/board.html b/templates/board.html
deleted file mode 100644
index 2a9585a..0000000
--- a/templates/board.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% extends "base.html" %}
-
-{% block body %}
-<header>
- <h1>ranger<span class="sep">›</span>{{ backlog_name }}</h1>
- <div class="counts">{{ active }} active · {{ total }} total</div>
-</header>
-<div class="board">
- {% include "panels/backlog.html" %}
- {% include "panels/column.html" with context %}
-</div>
-{% endblock body %}
diff --git a/templates/panels/backlog.html b/templates/panels/backlog.html
deleted file mode 100644
index d80e9c3..0000000
--- a/templates/panels/backlog.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<div class="panel">
- <div class="panel-header">
- <h2>Backlog</h2>
- <span class="count">{{ in_progress | length + queued | length }}</span>
- </div>
- {% if in_progress | length == 0 and queued | length == 0 %}
- <div class="empty">No active tasks</div>
- {% else %}
- {% if in_progress | length > 0 %}
- <div class="section-label section-label-in-progress"><span class="dot">●</span> In Progress</div>
- <div class="state-in-progress">
- {% for task in in_progress %}
- {% include "task.html" %}
- {% endfor %}
- </div>
- {% endif %}
- {% if queued | length > 0 %}
- <div class="section-label section-label-queued"><span class="dot">●</span> Queued</div>
- <div class="state-queued">
- {% for task in queued %}
- {% include "task.html" %}
- {% endfor %}
- </div>
- {% endif %}
- {% endif %}
-</div>
diff --git a/templates/panels/column.html b/templates/panels/column.html
deleted file mode 100644
index 660dbcd..0000000
--- a/templates/panels/column.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% for column in columns %}
-<div class="panel">
- <div class="panel-header">
- <h2>{{ column.label }}</h2>
- <span class="count">{{ column.tasks | length }}</span>
- </div>
- {% if column.tasks | length == 0 %}
- <div class="empty">No {{ column.label | lower }} tasks</div>
- {% else %}
- <div class="{{ column.state_class }}">
- {% for task in column.tasks %}
- {% include "task.html" %}
- {% endfor %}
- </div>
- {% endif %}
-</div>
-{% endfor %}
diff --git a/templates/task.html b/templates/task.html
deleted file mode 100644
index 44fedfe..0000000
--- a/templates/task.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<div class="task">
- <div class="task-header">
- <span class="key"><span class="key-prefix">{{ task.key_prefix }}</span><span class="key-rest">{{ task.key_rest }}</span></span>
- <span class="title">{{ task.title }}</span>
- </div>
- {% if task.description %}
- <div class="desc">{{ task.description }}</div>
- {% endif %}
- {% if task.has_subtasks %}
- <div class="subtask-indicator">◆ {{ task.done_subtask_count }}/{{ task.subtask_count }} subtasks</div>
- {% endif %}
-</div>