diff --git a/.gitignore b/.gitignore index 2942189..2c67e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ worker/ config.json wrangler.toml node_modules/ -dist/ \ No newline at end of file +dist/ +theme_config.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7cf994f..d937016 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,23 +1,101 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bumpalo" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + [[package]] name = "console_error_panic_hook" version = "0.1.6" @@ -28,18 +106,108 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "failure" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "futures" version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "handlebars" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba758d094d31274eb49d15da6f326b96bf3185239a6359bf684f3d5321148900" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error", + "serde", + "serde_json", +] + [[package]] name = "hex" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" +[[package]] +name = "include_dir" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ec8c61b6100ad0fc58ca644c59c0137d526d73cefa7497426369ea0d84d769" +dependencies = [ + "glob", + "include_dir_impl", + "proc-macro-hack", +] + +[[package]] +name = "include_dir_impl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03b4f7b64011df46794cc221854be5c3084345e260576518b91693866d0afd1" +dependencies = [ + "failure", + "proc-macro-hack", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn", +] + [[package]] name = "itoa" version = "0.4.5" @@ -76,6 +244,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.3.3" @@ -88,15 +262,60 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "paprika" version = "0.1.0" dependencies = [ "cfg-if", + "chrono", "console_error_panic_hook", + "handlebars", "hex", + "include_dir", "js-sys", "lazy_static", + "mime_guess", "pulldown-cmark", "serde", "serde_json", @@ -107,6 +326,55 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -136,6 +404,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "0.6.13" @@ -154,6 +428,18 @@ dependencies = [ "proc-macro2 1.0.10", ] +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + [[package]] name = "ryu" version = "1.0.3" @@ -197,6 +483,18 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "syn" version = "1.0.17" @@ -208,6 +506,41 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn", + "unicode-xid 0.2.0", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +dependencies = [ + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicase" version = "2.6.0" diff --git a/Cargo.toml b/Cargo.toml index 7f3a06e..b93851b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,13 @@ default = ["console_error_panic_hook", "wee_alloc"] [dependencies] cfg-if = "0.1.2" +chrono = "0.4" lazy_static = "1.4" +handlebars = "3.0" hex = "0.4" +include_dir = "0.5" js-sys = "0.3" +mime_guess = "2.0" pulldown-cmark = { version = "0.7", default-features = false } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" diff --git a/src/lib.rs b/src/lib.rs index fc9eda0..f38ad82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,13 @@ #![feature(vec_remove_item)] +#[macro_use] +extern crate handlebars; +#[macro_use] +extern crate include_dir; #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate serde_json; #[macro_use] mod utils; @@ -9,6 +15,7 @@ mod router; mod store; mod blog; mod sn; +mod render; use cfg_if::cfg_if; use js_sys::{Promise}; @@ -42,6 +49,7 @@ fn build_routes() -> router::Router { router.add_route("/hello", &hello_world); router.add_route(blog::IMG_CACHE_PREFIX, &proxy_remote_image); sn::build_routes(&mut router); + render::build_routes(&mut router); return router; } @@ -109,7 +117,19 @@ async fn default_route(_req: Request, url: Url) -> MyResult { ).internal_err(); } - // TODO: handle home page and pagination on home page + // Home page (this cannot be registered as a standalone route due to our Router) + if path == "/" { + return Response::new_with_opt_str_and_init( + Some(&render::render_homepage().await?), + ResponseInit::new() + .status(200) + .headers(headers!{ + "Content-Type" => "text/html", + "Cache-Control" => "no-cache" + }.as_ref()) + ).internal_err(); + } + // Now we can be sure the path ends with `/` // (and of course it starts with `/` as per standard) if path.len() > 1 { diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..2adb777 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,117 @@ +// Front-end page rendering +use crate::blog; +use crate::router::Router; +use crate::utils::*; +use chrono::NaiveDateTime; +use handlebars::Handlebars; +use include_dir::{include_dir, Dir}; +use js_sys::{Date, Uint8Array}; +use serde::Serialize; +use std::vec::Vec; +use web_sys::*; + +// TODO: allow static configuration of which theme to use +const THEME_DIR: Dir = include_dir!("theme/default"); + +pub fn build_routes(router: &mut Router) { + router.add_route("/static/", &serve_static); +} + +async fn serve_static(_req: Request, url: Url) -> MyResult { + let path = url.pathname(); + + if let Some(file) = THEME_DIR.get_file(&path[1..path.len()]) { + let u8arr: Uint8Array = file.contents().into(); + Response::new_with_opt_buffer_source_and_init( + Some(&u8arr), + ResponseInit::new() + .status(200) + .headers(headers!{ + "Content-Type" => mime_guess::from_path(path).first().unwrap().essence_str() + }.as_ref()) + ).internal_err() + } else { + Err(Error::NotFound("This file does not exist".into())) + } +} + +// Context objects used when rendering pages +#[derive(Serialize)] +struct BlogRootContext { + theme_config: &'static serde_json::Value, + title: &'static str, + description: &'static str, +} + +#[derive(Serialize)] +struct HomePagePost { + title: String, + url: String, + timestamp: u64, + summary: String +} + +#[derive(Serialize)] +struct HomePageContext { + blog: &'static BlogRootContext, + posts: Vec +} + +lazy_static! { + static ref THEME_CONFIG: serde_json::Value = serde_json::from_str( + include_str!("../theme_config.json")).unwrap(); + + static ref ROOT_CONTEXT: BlogRootContext = { + BlogRootContext { + theme_config: &THEME_CONFIG, + title: &crate::CONFIG.title, + description: &crate::CONFIG.description + } + }; +} + +handlebars_helper!(cur_year: |dummy: u64| Date::new_0().get_full_year()); +// TODO: actually implement this helper +handlebars_helper!(format_date: |date: u64, format: str| { + NaiveDateTime::from_timestamp(date as i64, 0).format(format).to_string() +}); + +fn build_handlebars() -> Handlebars<'static> { + let mut hbs = Handlebars::new(); + + // Helpers + hbs.register_helper("cur_year", Box::new(cur_year)); + hbs.register_helper("format_date", Box::new(format_date)); + + // Templates + for file in THEME_DIR.files() { + let path = file.path().to_str().unwrap(); + if path.ends_with(".hbs") { + // Register all .hbs templates + hbs.register_template_string( + path, file.contents_utf8().unwrap()).unwrap(); + } + } + return hbs; +} + +pub async fn render_homepage() -> MyResult { + let hbs = build_handlebars(); + let mut context = HomePageContext { + blog: &ROOT_CONTEXT, + posts: vec![] + }; + let posts_list = blog::PostsList::load().await; + for uuid in posts_list.0.iter() { + let post = blog::Post::find_by_uuid(uuid).await?; + let post_cache = blog::PostContentCache::find_or_render(&post).await; + context.posts.push(HomePagePost { + title: post.title, + url: post.url, + timestamp: post.timestamp, + summary: post_cache.content // TODO: make actual summaries + }); + } + hbs.render("home.hbs", &context) + .map_err(|e| Error::BadRequest(format!("{:#?}", e))) +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 12dac5d..9fccd31 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -147,6 +147,10 @@ pub struct Config { pub secret: String, // Title of the blog pub title: String, + // Language of blog + pub lang: String, + // Description of the blog + pub description: String, // Plugin identifier used for Standard Notes pub plugin_identifier: String } \ No newline at end of file diff --git a/theme/default/home.hbs b/theme/default/home.hbs new file mode 100644 index 0000000..3eede4c --- /dev/null +++ b/theme/default/home.hbs @@ -0,0 +1,29 @@ + + + + {{ blog.title }} + + + +
+ {{> sidebar.hbs }} +
+ {{ #each posts }} +
+

{{ this.title }}

+ {{ format_date this.timestamp "%e %b, %Y" }} +
+ {{{ this.summary }}} +
+ +
+ {{ /each }} + +
+
+ + + \ No newline at end of file diff --git a/theme/default/sidebar.hbs b/theme/default/sidebar.hbs new file mode 100644 index 0000000..a5cb8f8 --- /dev/null +++ b/theme/default/sidebar.hbs @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/theme/default/static/script.js b/theme/default/static/script.js new file mode 100644 index 0000000..41e1cd0 --- /dev/null +++ b/theme/default/static/script.js @@ -0,0 +1,9 @@ +// Avatar animation on hover +// Unfortunately for best result, this can only be done with JS +let avatar = document.querySelector(".sidebar .avatar img"); +avatar.onmouseover = (ev) => { + ev.target.className = "animate"; +}; +avatar.onanimationend = (ev) => { + ev.target.className = ""; +}; \ No newline at end of file diff --git a/theme/default/static/style.css b/theme/default/static/style.css new file mode 100644 index 0000000..84c33b3 --- /dev/null +++ b/theme/default/static/style.css @@ -0,0 +1,304 @@ +@import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300&display=swap'); + +/* Get rid of default padding */ +html, body { + margin: 0; + padding: 0; + font-family: 'Fira Sans', sans-serif; + font-size: 18px; + text-shadow: 0 0 3px #dddddd; + color: rgb(100, 100, 100); +} + +html { + background-image: url("/static/whitenoise-100x100.png"); + background-repeat: repeat; + background-attachment: fixed; +} + +.hidden { + display: none; +} + +a, a:link, a:visited, a:hover, a:active { + text-decoration: none; + color: inherit; + outline: none; +} + +body { + text-align: center; +} + +.page-wrapper { + display: inline-block; + position: absolute; + left: 25vw; + padding-top: 10px; + width: 60vw; + max-width: 1000px; + min-width: 600px; + padding-bottom: 15px; + text-align: left; +} + +.with-divider::after { + content: ""; + display: block; + margin-top: 20px; + width: 40px; + height: 3px; + background-color: #E91E63; +} + +.with-divider.with-divider-wide-center::after { + margin-left: calc(50% - 90px); + width: 180px; +} + +.with-divider.with-divider-thin::after { + margin-top: 18px; + height: 2px; +} + +.with-divider.with-divider-right::after { + margin-left: calc(100% - 40px); +} + +/* Sidebar */ +.sidebar { + float: left; + position: sticky; + top: 20px; + text-align: right; + width: 22%; +} + +.sidebar section { + margin-top: 20px; +} + +.sidebar .avatar { + display: inline-block; + overflow: hidden; + width: 100px; + height: 100px; + line-height: 100px; + text-align: center; + background-color: #ffffff; + border-radius: 50%; + box-shadow: 0px 0px 6px 0px #cccccc; +} + +.sidebar .avatar img { + display: inline-block; + margin-top: 5%; + width: 90%; + height: 90%; + border-radius: 50%; + object-fit: cover; +} + +.sidebar .avatar img.animate { + animation: spin 1s ease-in-out; +} +/* +.sidebar .avatar img:hover { + animation: spin 0.5s ease-in-out; +}*/ + +@keyframes spin { + 20% { + transform: rotate(30deg); + } + + 40% { + transform: rotate(-30deg); + } + + 60% { + transform: rotate(30deg); + } + + 80% { + transform: rotate(-30deg); + } + + 100% { + transform: rotate(0); + } +} + +.sidebar .introduction { + font-style: italic; +} + +.sidebar .links ul { + list-style-type: none; + padding-bottom: 10px; +} + +.sidebar .links li { + padding-top: 10px; +} + +.sidebar .links a { + transition: color 0.2s ease-in-out; +} + +.sidebar .links a:hover { + color: #F06292; +} + +.sidebar .copyright { + font-size: 0.6em; + color: rgb(200, 200, 200); +} + +.copyright a, +.copyright a:link, +.copyright a:visited, +.copyright a:hover, +.copyright a:active { + text-decoration: underline; +} + +/* Home page post list */ +.post-list, .content { + margin-top: 15px; + margin-left: 25%; + width: 75%; +} + +.post-list .post { + padding: 10px 5px 5px 5px; + margin-bottom: 12px; +} + +.post-list .post h1 { + font-size: 1.2em; + margin: 0 0 0.5em 0; + padding: 0; +} + +.post-list .post h1 a { + color: inherit; + transition: color 0.2s ease-in-out; +} + +.post-list .post section { + margin-top: 0.5em; +} + +.post-list .post h1 a:hover { + color: #E91E63; + text-decoration: none; +} + +.post-list .post .read-more::after { + content: "......"; + color: #F06292; +} + +.post-list .pagination { + font-size: 0.8em; +} + +.post-list .pagination .page-older:after { + content: "Older >"; + color: #E91E63; + float: right; +} + +.post-list .pagination .page-newer:after { + content: "< Newer"; + color: #E91E63; + float: left; +} + +.post a, +.post a:link, +.post a:visited, +.post a:hover, +.post a:active { + color: #E91E63; +} + +.post a:hover { + text-decoration: underline; +} + +/* Posts */ +.date { + font-style: italic; +} + +.post img, +.content img { + max-width: 100%; + object-fit: contain; +} + +.toc-wrapper { + display: flex; + align-items: center; + float: right; + position: fixed; + right: 0; + top: 0; + width: 10%; + height: 100%; +} + +.toc { + max-width: 200px; + font-size: 0.6em; + text-align: left; + background-color: #ffffff; + border-radius: 5px; + box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); + padding-left: 15px; +} + +.toc li { + margin-bottom: 2px; +} + +.toc li::before { + content: "-"; + position: absolute; + left: 5px; /* Relative to parent */ +} + +.toc ul { + margin-top: 5px; + margin-bottom: 5px; + padding-left: 0px; + margin-right: 10px; + margin-left: 0; + list-style-type: none; +} + +.toc ul li.current { + color: #000000; +} + +.toc ul li.current::before { + content: "+"; +} + +.toc ul ul { + margin-left: 5px; +} + +.toc ul ul ul { + margin-left: 10px; +} + +.toc ul ul ul ul { + margin-left: 15px; +} + +.toc ul ul ul ul ul { + margin-left: 20px; +} \ No newline at end of file diff --git a/theme/default/static/whitenoise-100x100.png b/theme/default/static/whitenoise-100x100.png new file mode 100644 index 0000000..d51f349 Binary files /dev/null and b/theme/default/static/whitenoise-100x100.png differ