From 8b5a71dd3a490718540a7e9d48da2a220256a628 Mon Sep 17 00:00:00 2001 From: Naz Date: Mon, 15 Sep 2025 14:50:08 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20dirty=20commit=20bringing=20basic=20fu?= =?UTF-8?q?nctionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 975 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 + examples/bookmarks.json | 214 +++++++++ examples/cosmic.css | 536 ++++++++++++++++++++++ examples/favicon.svg | 349 +++++++++++++++ examples/simple-catppuccin.css | 35 ++ examples/simple-gruvbox.css | 35 ++ examples/simple-solarized.css | 35 ++ src/config.rs | 68 +++ src/errors.rs | 88 ++++ src/handlers.rs | 28 ++ src/lib.rs | 29 ++ src/main.rs | 10 +- src/models.rs | 24 + templates/index.html | 26 ++ 15 files changed, 2459 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock create mode 100644 examples/bookmarks.json create mode 100644 examples/cosmic.css create mode 100644 examples/favicon.svg create mode 100644 examples/simple-catppuccin.css create mode 100644 examples/simple-gruvbox.css create mode 100644 examples/simple-solarized.css create mode 100644 src/config.rs create mode 100644 src/errors.rs create mode 100644 src/handlers.rs create mode 100644 src/lib.rs create mode 100644 src/models.rs create mode 100644 templates/index.html diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4fae000 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,975 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "indexmap" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "sbm-rs" +version = "0.1.0" +dependencies = [ + "askama", + "axum", + "colored", + "derive_more", + "serde", + "serde_json", + "tokio", + "toml", + "tower-http", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.223" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.223" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.223" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a8abed938137c7183c173848e3c9b3517f5e038226849a4ecc9b21a4b4e2a" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 74281a9..6a89f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,12 @@ homepage = "https://git.ch-naseem.com/ndpm13/sbm-rs" edition = "2024" [dependencies] +askama = "0.14.0" +axum = { version = "0.8.4", features = ["macros"] } +colored = "3.0.0" +derive_more = { version = "2.0.1", features = ["from"] } +serde = { version = "1.0.223", features = ["derive"] } +serde_json = "1.0.145" +tokio = { version = "1.47.1", features = ["full"] } +toml = "0.9.5" +tower-http = { version = "0.6.6", features = ["fs"] } diff --git a/examples/bookmarks.json b/examples/bookmarks.json new file mode 100644 index 0000000..8ae3413 --- /dev/null +++ b/examples/bookmarks.json @@ -0,0 +1,214 @@ +{ + "sections": [ + { + "title": "BMs", + "cards": [ + { + "title": "distros", + "links": [ + { + "title": "arch", + "url": "https://archlinux.org/" + }, + { + "title": "void", + "url": "https://voidlinux.org/" + }, + { + "title": "debian", + "url": "https://www.debian.org/" + }, + { + "title": "mx", + "url": "https://mxlinux.org/" + } + ] + }, + { + "title": "search", + "links": [ + { + "title": "brave", + "url": "https://search.brave.com/" + }, + { + "title": "starpage", + "url": "https://www.startpage.com/" + }, + { + "title": "duckduckgo", + "url": "https://duckduckgo.com/" + } + ] + }, + { + "title": "rust", + "links": [ + { + "title": "surrealdb", + "url": "https://surrealdb.com/" + }, + { + "title": "tauri", + "url": "https://tauri.app/" + }, + { + "title": "crates", + "url": "https://crates.io/" + }, + { + "title": "docs.rs", + "url": "https://docs.rs/" + }, + { + "title": "DA BOOK", + "url": "https://rust-book.cs.brown.edu/" + } + ] + }, + { + "title": "minecraft", + "links": [ + { + "title": "curseforge", + "url": "https://www.curseforge.com/minecraft" + }, + { + "title": "vanilla tweaks", + "url": "https://vanillatweaks.net/" + }, + { + "title": "modrinth", + "url": "https://modrinth.com/" + } + ] + }, + { + "title": "Mail", + "links": [ + { + "title": "proton", + "url": "https://mail.proton.me" + }, + { + "title": "tuta", + "url": "https://app.tuta.com/login" + }, + { + "title": "10minutemail", + "url": "https://10minutemail.com/" + }, + { + "title": "disroot", + "url": "https://mail.disroot.org/" + } + ] + } + ] + }, + { + "title": "torrent", + "cards": [ + { + "title": "media", + "links": [ + { + "title": "yts movies", + "url": "https://yts.mx/" + }, + { + "title": "1337x", + "url": "https://1337x.to/home/" + }, + { + "title": "nyaa", + "url": "https://nyaa.si/" + } + ] + }, + { + "title": "misc", + "links": [ + { + "title": "torrentz2", + "url": "https://torrentz2.nz/" + }, + { + "title": "the pirate bay", + "url": "https://thepiratebay.org/" + } + ] + } + ] + }, + { + "title": "homelab", + "cards": [ + { + "title": "media", + "links": [ + { + "title": "RSS", + "url": "https://yarr.local/" + }, + { + "title": "Jellyfin", + "url": "https://jellyfin.local/" + }, + { + "title": "Calibre", + "url": "https://calibre.local/" + } + ] + }, + { + "title": "code", + "links": [ + { + "title": "Gogs", + "url": "https://gogs.local/" + }, + { + "title": "Forgejo", + "url": "https://git.ch-naseem.com/" + } + ] + }, + { + "title": "admin", + "links": [ + { + "title": "Proxy", + "url": "https://npm.local/" + }, + { + "title": "Pi-hole", + "url": "https://pihole.local/" + } + ] + }, + { + "title": "misc", + "links": [ + { + "title": "Glance", + "url": "https://glance.local/" + }, + { + "title": "Syncthing", + "url": "https://syncthing.local/" + }, + { + "title": "Transmission", + "url": "https://transmission.local/" + }, + { + "title": "4get", + "url": "https://4get.local/" + } + ] + } + ] + } + ] +} diff --git a/examples/cosmic.css b/examples/cosmic.css new file mode 100644 index 0000000..177fe10 --- /dev/null +++ b/examples/cosmic.css @@ -0,0 +1,536 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Space+Grotesk:wght@400;600;700&display=swap"); + +:root { + --glass-bg: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + --glass-shadow: rgba(0, 0, 0, 0.3); + --accent-purple: #8b5cf6; + --accent-blue: #06b6d4; + --accent-pink: #ec4899; + --accent-orange: #f59e0b; + --text-primary: #ffffff; + --text-muted: rgba(255, 255, 255, 0.7); + --dark-space: #0f0f23; + --darker-space: #050510; +} + +* { + box-sizing: border-box; +} + +body { + font-family: "Inter", sans-serif; + background: radial-gradient( + ellipse at top, + #1e1b4b 0%, + var(--dark-space) 50%, + var(--darker-space) 100% + ); + color: var(--text-primary); + margin: 0; + padding: 0; + min-height: 100vh; + overflow-x: hidden; + position: relative; +} + +/* Cosmic background with moving stars */ +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 200%; + height: 200%; + background-image: + radial-gradient( + 2px 2px at 20px 30px, + rgba(255, 255, 255, 0.8), + transparent + ), + radial-gradient( + 1px 1px at 40px 70px, + rgba(255, 255, 255, 0.4), + transparent + ), + radial-gradient( + 1px 1px at 90px 40px, + rgba(255, 255, 255, 0.6), + transparent + ), + radial-gradient( + 2px 2px at 130px 80px, + rgba(255, 255, 255, 0.3), + transparent + ), + radial-gradient( + 1px 1px at 160px 30px, + rgba(255, 255, 255, 0.5), + transparent + ); + background-repeat: repeat; + background-size: 200px 200px; + z-index: -2; + animation: starField 60s linear infinite; +} + +/* Nebula clouds */ +body::after { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + radial-gradient( + ellipse 800px 600px at 10% 20%, + rgba(139, 92, 246, 0.05) 0%, + transparent 50% + ), + radial-gradient( + ellipse 600px 800px at 90% 80%, + rgba(6, 182, 212, 0.05) 0%, + transparent 50% + ), + radial-gradient( + ellipse 400px 400px at 50% 10%, + rgba(236, 72, 153, 0.03) 0%, + transparent 50% + ); + z-index: -1; + animation: nebulaDrift 40s ease-in-out infinite; +} + +@keyframes starField { + 0% { + transform: translateY(0) translateX(0); + } + 100% { + transform: translateY(-200px) translateX(-200px); + } +} + +@keyframes nebulaDrift { + 0%, + 100% { + transform: scale(1) rotate(0deg); + } + 33% { + transform: scale(1.1) rotate(1deg); + } + 66% { + transform: scale(0.9) rotate(-1deg); + } +} + +h1 { + font-family: "Space Grotesk", sans-serif; + font-size: clamp(3rem, 10vw, 6rem); + font-weight: 700; + text-align: center; + margin: 80px 0 60px 0; + background: linear-gradient( + 135deg, + var(--accent-purple) 0%, + var(--accent-blue) 50%, + var(--accent-pink) 100% + ); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + position: relative; + filter: drop-shadow(0 0 30px rgba(139, 92, 246, 0.3)); + animation: titleGlow 4s ease-in-out infinite alternate; +} + +/* Floating orbs using h1 pseudo-elements */ +h1::before { + content: ""; + position: fixed; + width: 300px; + height: 300px; + background: radial-gradient( + circle at 30% 30%, + rgba(139, 92, 246, 0.1), + transparent + ); + top: 10%; + right: 10%; + border-radius: 50%; + pointer-events: none; + z-index: -1; + animation: float1 20s ease-in-out infinite; +} + +h1::after { + content: ""; + position: fixed; + width: 200px; + height: 200px; + background: radial-gradient( + circle at 70% 70%, + rgba(6, 182, 212, 0.1), + transparent + ); + bottom: 20%; + left: 10%; + border-radius: 50%; + pointer-events: none; + z-index: -1; + animation: float2 25s ease-in-out infinite; +} + +@keyframes float1 { + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + 33% { + transform: translate(-50px, -30px) scale(1.1); + } + 66% { + transform: translate(30px, -60px) scale(0.9); + } +} + +@keyframes float2 { + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + 50% { + transform: translate(40px, -40px) scale(1.2); + } +} + +@keyframes titleGlow { + 0% { + filter: drop-shadow(0 0 30px rgba(139, 92, 246, 0.3)); + } + 100% { + filter: drop-shadow(0 0 50px rgba(6, 182, 212, 0.4)); + } +} + +.cards-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 40px; + margin: 60px auto; + max-width: 1400px; + padding: 0 40px; + perspective: 1200px; +} + +/* Additional floating orb using cards-container */ +.cards-container::before { + content: ""; + position: fixed; + width: 150px; + height: 150px; + background: radial-gradient( + circle at 50% 50%, + rgba(236, 72, 153, 0.1), + transparent + ); + top: 60%; + right: 20%; + border-radius: 50%; + pointer-events: none; + z-index: -1; + animation: float3 30s ease-in-out infinite; +} + +@keyframes float3 { + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + 25% { + transform: translate(-20px, 30px) scale(0.8); + } + 75% { + transform: translate(20px, -20px) scale(1.1); + } +} + +.card { + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: 24px; + padding: 35px 30px; + position: relative; + transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); + transform-style: preserve-3d; + overflow: hidden; +} + +/* Glass reflection effect */ +.card::before { + content: ""; + position: absolute; + top: 0; + left: -50%; + width: 50%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + transform: skewX(-25deg); + transition: left 0.6s ease; + opacity: 0; +} + +.card:hover::before { + left: 150%; + opacity: 1; +} + +/* Floating shadow */ +.card::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + width: 80%; + height: 20px; + background: radial-gradient(ellipse, rgba(0, 0, 0, 0.3), transparent); + transform: translateX(-50%); + border-radius: 50%; + transition: all 0.5s ease; + z-index: -1; +} + +.card:hover { + transform: translateY(-20px) rotateX(8deg) rotateY(-2deg); + border-color: rgba(255, 255, 255, 0.2); + box-shadow: + 0 40px 80px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.card:hover::after { + top: 120%; + width: 90%; + height: 30px; + background: radial-gradient(ellipse, rgba(0, 0, 0, 0.5), transparent); +} + +h2 { + font-family: "Space Grotesk", sans-serif; + color: var(--text-primary); + margin: 0 0 30px 0; + font-size: 1.8rem; + font-weight: 600; + text-align: center; + position: relative; + text-transform: uppercase; + letter-spacing: 1px; +} + +/* Animated underline */ +h2::after { + content: ""; + position: absolute; + bottom: -15px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 3px; + background: linear-gradient( + 90deg, + var(--accent-purple), + var(--accent-blue), + var(--accent-pink) + ); + border-radius: 2px; + transition: width 0.4s ease; +} + +.card:hover h2::after { + width: 80%; +} + +a { + color: var(--text-muted); + display: flex; + align-items: center; + margin: 16px 0; + text-decoration: none; + padding: 18px 24px; + border-radius: 16px; + transition: all 0.4s ease; + position: relative; + font-weight: 400; + font-size: 1.1rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.05); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + overflow: hidden; +} + +/* Gradient hover effect */ +a::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 135deg, + rgba(139, 92, 246, 0.1) 0%, + rgba(6, 182, 212, 0.1) 50%, + rgba(236, 72, 153, 0.1) 100% + ); + opacity: 0; + transition: opacity 0.4s ease; + border-radius: 16px; +} + +/* Icon placeholder */ +a::after { + content: "→"; + margin-left: auto; + font-size: 1.2rem; + font-weight: bold; + transition: transform 0.3s ease; + opacity: 0.6; +} + +a:hover { + color: var(--text-primary); + transform: translateX(8px) translateZ(10px); + border-color: rgba(255, 255, 255, 0.15); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +a:hover::before { + opacity: 1; +} + +a:hover::after { + transform: translateX(8px); + opacity: 1; +} + +/* Magnetic effect */ +a:active { + transform: translateX(4px) translateZ(5px) scale(0.98); +} + +/* Responsive design */ +@media (max-width: 768px) { + .cards-container { + grid-template-columns: 1fr; + gap: 30px; + padding: 0 20px; + } + + .card { + padding: 30px 25px; + } + + h1 { + margin: 60px 0 40px 0; + } +} + +/* Card entrance animations */ +.card { + opacity: 0; + animation: cardEnter 0.8s ease forwards; +} + +.card:nth-child(1) { + animation-delay: 0.1s; +} +.card:nth-child(2) { + animation-delay: 0.2s; +} +.card:nth-child(3) { + animation-delay: 0.3s; +} +.card:nth-child(4) { + animation-delay: 0.4s; +} +.card:nth-child(5) { + animation-delay: 0.5s; +} +.card:nth-child(6) { + animation-delay: 0.6s; +} +.card:nth-child(7) { + animation-delay: 0.7s; +} + +@keyframes cardEnter { + 0% { + opacity: 0; + transform: translateY(100px) rotateX(-15deg) scale(0.8); + filter: blur(10px); + } + 100% { + opacity: 1; + transform: translateY(0) rotateX(0) scale(1); + filter: blur(0); + } +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient( + 135deg, + var(--accent-purple), + var(--accent-blue) + ); + border-radius: 4px; + backdrop-filter: blur(10px); +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, var(--accent-blue), var(--accent-pink)); +} + +/* Selection styling */ +::selection { + background: rgba(139, 92, 246, 0.3); + color: var(--text-primary); +} + +/* Page load animation */ +@keyframes pageLoad { + 0% { + opacity: 0; + transform: scale(1.1); + filter: blur(20px); + } + 100% { + opacity: 1; + transform: scale(1); + filter: blur(0); + } +} + +body { + animation: pageLoad 1.2s ease-out; +} diff --git a/examples/favicon.svg b/examples/favicon.svg new file mode 100644 index 0000000..2af9e83 --- /dev/null +++ b/examples/favicon.svg @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/simple-catppuccin.css b/examples/simple-catppuccin.css new file mode 100644 index 0000000..1b3945b --- /dev/null +++ b/examples/simple-catppuccin.css @@ -0,0 +1,35 @@ +body { + background-color: #1e1e2e; + color: #cdd6f4; +} + +.cards-container { + display: grid; + grid-template-columns: repeat(7, 2fr); + align-items: center; + gap: 20px; + margin: 13px; +} + +.card { + background-color: #313244; + border-radius: 12px; + text-align: center; + padding-bottom: 20px; +} + +h2 { + color: #74c7ec; + margin-bottom: 5px; +} + +a { + color: #bac2de; + display: block; + margin: 5px 0; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} diff --git a/examples/simple-gruvbox.css b/examples/simple-gruvbox.css new file mode 100644 index 0000000..b5c12cf --- /dev/null +++ b/examples/simple-gruvbox.css @@ -0,0 +1,35 @@ +body { + background-color: #282828; + color: #ebdbb2; +} + +.cards-container { + display: grid; + grid-template-columns: repeat(7, 2fr); + align-items: center; + gap: 20px; + margin: 13px; +} + +.card { + background-color: #363332; + border-radius: 12px; + text-align: center; + padding-bottom: 20px; +} + +h2 { + color: #83a598; + margin-bottom: 5px; +} + +a { + color: #bdae93; + display: block; + margin: 5px 0; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} diff --git a/examples/simple-solarized.css b/examples/simple-solarized.css new file mode 100644 index 0000000..af5c8be --- /dev/null +++ b/examples/simple-solarized.css @@ -0,0 +1,35 @@ +body { + background-color: #002b36; + color: #eee8d5; +} + +.cards-container { + display: grid; + grid-template-columns: repeat(7, 2fr); + align-items: center; + gap: 20px; + margin: 13px; +} + +.card { + background-color: #073642; + border-radius: 12px; + text-align: center; + padding-bottom: 20px; +} + +h2 { + color: #2aa198; + margin-bottom: 5px; +} + +a { + color: #93a1a1; + display: block; + margin: 5px 0; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..97dac88 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,68 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use crate::{Error, Result}; + +const STYLE: &str = include_str!("../examples/simple-gruvbox.css"); +const BOOKMARKS: &str = include_str!("../examples/bookmarks.json"); +const FAVICON: &str = include_str!("../examples/favicon.svg"); + +#[derive(Deserialize, Serialize, Clone)] +pub struct Config { + pub port: u16, + pub style_file: PathBuf, + pub bookmarks_file: PathBuf, + pub favicon_file: PathBuf, +} + +impl Config { + pub fn new() -> Result { + let config_home = if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") { + PathBuf::from(xdg_config_home).join("sbm-rs") + } else if let Ok(home) = std::env::var("HOME") { + PathBuf::from(home).join(".config/sbm-rs") + } else { + return Err(Error::ConfigNotFound); + }; + + if config_home.exists() { + Config::load_config(&config_home) + } else { + Config::generate_defaults(&config_home) + } + } + pub fn load_config(config_home: &PathBuf) -> Result { + let config = config_home.join("config.toml"); + + let config = std::fs::read_to_string(config)?; + + let config: Config = toml::from_str(&config)?; + + Ok(config) + } + pub fn generate_defaults(config_home: &PathBuf) -> Result { + std::fs::create_dir_all(&config_home)?; + + let style_file = config_home.join("style.css"); + let bookmarks_file = config_home.join("bookmarks.json"); + let favicon_file = config_home.join("favicon.svg"); + let config_file = config_home.join("config.toml"); + + std::fs::write(&style_file, &STYLE)?; + std::fs::write(&bookmarks_file, &BOOKMARKS)?; + std::fs::write(&favicon_file, &FAVICON)?; + + let config = Config { + port: 8080, + style_file, + bookmarks_file, + favicon_file, + }; + + let config_file_content = toml::to_string(&config)?; + std::fs::write(config_file, config_file_content)?; + + Ok(config) + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..251ee5f --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,88 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use derive_more::From; + +pub type Result = core::result::Result; + +#[derive(Debug, From)] +pub enum Error { + ConfigNotFound, + ConfigHomeNotFound, + + #[from] + Io(std::io::Error), + + #[from] + EnvVar(std::env::VarError), + + #[from] + Askama(askama::Error), + + #[from] + SerdeError(serde_json::Error), + + #[from] + TomlDeError(toml::de::Error), + + #[from] + TomlSerError(toml::ser::Error), +} + +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), std::fmt::Error> { + match self { + Error::ConfigNotFound => write!( + fmt, + "Config file can't be found at ~/.config/sbm-rs/config.toml" + ), + Error::ConfigHomeNotFound => write!(fmt, "Config home can't be found"), + Error::Io(e) => write!(fmt, "{e}"), + Error::EnvVar(e) => write!(fmt, "Environment variable error: {e}"), + Error::Askama(e) => write!(fmt, "Askama error: {e}"), + Error::SerdeError(e) => write!(fmt, "Serde error: {e}"), + Error::TomlDeError(e) => write!(fmt, "TOML deserialization error: {e}"), + Error::TomlSerError(e) => write!(fmt, "TOML serialization error: {e}"), + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + let (status, error_message) = match self { + Error::ConfigNotFound => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Config file can't be found at ~/.config/sbm-rs/config.toml"), + ), + Error::ConfigHomeNotFound => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Config home can't be found"), + ), + Error::Io(ref e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + Error::EnvVar(ref e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Environment variable error: {e}"), + ), + Error::Askama(ref e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Askama error: {e}"), + ), + Error::SerdeError(ref e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serde error: {e}"), + ), + Error::TomlDeError(ref e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("TOML deserialization error: {e}"), + ), + Error::TomlSerError(ref e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("TOML serialization error: {e}"), + ), + }; + + println!("{} {}", &status, &error_message); + (status, error_message).into_response() + } +} diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..b235c84 --- /dev/null +++ b/src/handlers.rs @@ -0,0 +1,28 @@ +use askama::Template; +use axum::{extract::State, response::Html}; + +use crate::{ + Result, + config::Config, + models::{Bookmarks, Section}, +}; + +#[derive(Template)] +#[template(path = "index.html")] +pub struct MyTemplate { + bookmarks: Vec
, +} + +pub async fn handler(State(config): State) -> Result> { + let bookmarks = std::fs::read_to_string(config.bookmarks_file)?; + + let bookmarks: Bookmarks = serde_json::from_str(&bookmarks)?; + + let template = MyTemplate { + bookmarks: bookmarks.sections, + }; + + let html = template.render()?; + + Ok(Html(html)) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c328a44 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,29 @@ +mod config; +mod errors; +mod handlers; +mod models; + +pub use errors::{Error, Result}; + +use std::net::{IpAddr, Ipv6Addr, SocketAddr}; + +use axum::{Router, routing::get}; +use tokio::net::TcpListener; +use tower_http::services::ServeFile; + +pub async fn run() -> Result<()> { + let config = config::Config::new()?; + + let app = Router::new() + .nest_service("/static/style.css", ServeFile::new(&config.style_file)) + .nest_service("/static/favicon.svg", ServeFile::new(&config.favicon_file)) + .route("/", get(handlers::handler)) + .with_state(config.clone()); + + let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), config.port); + let listener = TcpListener::bind(socket).await?; + + axum::serve(listener, app).await?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..4d78916 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,9 @@ -fn main() { - println!("Hello, world!"); +use colored::Colorize; + +#[tokio::main] +async fn main() { + if let Err(e) = sbm_rs::run().await { + eprintln!("{} {}", "Error:".red().bold(), e); + std::process::exit(1); + } } diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..7b99ba5 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,24 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Bookmarks { + pub sections: Vec
, +} + +#[derive(Deserialize)] +pub struct Section { + pub title: String, + pub cards: Vec, +} + +#[derive(Deserialize)] +pub struct Card { + pub title: String, + pub links: Vec, +} + +#[derive(Deserialize)] +pub struct Link { + pub title: String, + pub url: String, +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..58ab290 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,26 @@ + + + + + + + SBM-RS + + + + + {% for section in bookmarks %} +

{{ section.title }}

+
+ {% for card in section.cards %} +
+

{{ card.title }}

+ {% for link in card.links %} + {{ link.title }} + {% endfor %} +
+ {% endfor %} +
+ {% endfor %} + + -- cgit v1.2.3