diff --git a/site_test/css/external-link.svg b/site_test/css/external-link.svg new file mode 100644 index 0000000..55003a6 --- /dev/null +++ b/site_test/css/external-link.svg @@ -0,0 +1,27 @@ + + + + + + diff --git a/site_test/css/fonts.scss b/site_test/css/fonts.scss new file mode 100644 index 0000000..1cdc16a --- /dev/null +++ b/site_test/css/fonts.scss @@ -0,0 +1,49 @@ +@font-face { + font-family: "Valkyrie A"; + font-style: normal; + font-weight: normal; + font-stretch: normal; + font-display: auto; + src: url("data:font/woff2;base64," + $valkyrie-a-regular) format("woff2"); +} + +@font-face { + font-family: "Valkyrie A"; + font-style: italic; + font-weight: normal; + font-stretch: normal; + font-display: auto; + src: url("data:font/woff2;base64," + $valkyrie-a-italic) format("woff2"); +} + +@font-face { + font-family: "Valkyrie A"; + font-style: normal; + font-weight: bold; + font-stretch: normal; + font-display: auto; + src: url("data:font/woff2;base64," + $valkyrie-a-bold) format("woff2"); +} + +@font-face { + font-family: "Valkyrie A"; + font-style: italic; + font-weight: bold; + font-stretch: normal; + font-display: auto; + src: url("data:font/woff2;base64," + $valkyrie-a-bold-italic) + format("woff2"); +} + +@font-face { + font-family: "Berkeley Mono"; + src: url("data:font/woff2;base64," + $berkeley-mono-regular) format("woff2"); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: "Berkeley Mono"; + src: url("data:font/woff2;base64," + $berkeley-mono-italic) format("woff2"); + font-weight: normal; + font-style: italic; +} diff --git a/site_test/css/fonts/BerkeleyMono-Italic.woff2 b/site_test/css/fonts/BerkeleyMono-Italic.woff2 new file mode 100644 index 0000000..62b16c6 Binary files /dev/null and b/site_test/css/fonts/BerkeleyMono-Italic.woff2 differ diff --git a/site_test/css/fonts/BerkeleyMono-Regular.woff2 b/site_test/css/fonts/BerkeleyMono-Regular.woff2 new file mode 100644 index 0000000..0240f0e Binary files /dev/null and b/site_test/css/fonts/BerkeleyMono-Regular.woff2 differ diff --git a/site_test/css/fonts/valkyrie_a_bold.woff2 b/site_test/css/fonts/valkyrie_a_bold.woff2 new file mode 100644 index 0000000..35d8ea2 Binary files /dev/null and b/site_test/css/fonts/valkyrie_a_bold.woff2 differ diff --git a/site_test/css/fonts/valkyrie_a_bold_italic.woff2 b/site_test/css/fonts/valkyrie_a_bold_italic.woff2 new file mode 100644 index 0000000..c2ccba3 Binary files /dev/null and b/site_test/css/fonts/valkyrie_a_bold_italic.woff2 differ diff --git a/site_test/css/fonts/valkyrie_a_italic.woff2 b/site_test/css/fonts/valkyrie_a_italic.woff2 new file mode 100644 index 0000000..0af16d4 Binary files /dev/null and b/site_test/css/fonts/valkyrie_a_italic.woff2 differ diff --git a/site_test/css/fonts/valkyrie_a_regular.woff2 b/site_test/css/fonts/valkyrie_a_regular.woff2 new file mode 100644 index 0000000..4387da7 Binary files /dev/null and b/site_test/css/fonts/valkyrie_a_regular.woff2 differ diff --git a/site_test/css/main.scss b/site_test/css/main.scss index 2ec45ad..b4f4772 100644 --- a/site_test/css/main.scss +++ b/site_test/css/main.scss @@ -1,35 +1,105 @@ -@font-face { - font-family: "Equity A"; - font-style: normal; - font-weight: normal; - font-stretch: normal; - font-display: auto; - src: url("data:font/woff2;base64," + $equity-a-regular) format("woff2"); +@import "normalize.scss"; +@import "fonts.scss"; + +:root { + --background-color: #f8e7cf; + --text-color: black; + --link-color: blue; + --page-vertical-margin: 3rem; + --page-horizontal-margin: 2rem; } -@font-face { - font-family: "Equity A"; +.container { + max-width: 768px; +} + +a, +a:visited { + color: var(--link-color); + text-decoration-thickness: 0.05em; +} +a:hover { + text-decoration-thickness: 0.1em; +} +a[href^="http://"]::after, +a[href^="https://"]::after +{ + background-color: currentColor; + content: ""; + width: calc(max(0.667em, 12px)); + height: calc(max(0.667em, 12px)); + margin-left: 0.333em; + display: inline-block; + mask: url("data:image/svg+xml;base64," + $external-link) no-repeat 50% 50%; + mask-size: cover; +} + +pre, +code { + font-family: "Berkeley Mono"; +} + +body { + font-family: "Valkyrie A", Charter, serif; + font-size: 16px; + /* background-color: #dfd3c3; */ + background-color: var(--background-color); + /* background-color: #e8dbc5; */ + color: var(--text-color); +} + +header { + margin-top: var(--page-vertical-margin); font-style: italic; - font-weight: normal; - font-stretch: normal; - font-display: auto; - src: url("data:font/woff2;base64," + $equity-a-italic) format("woff2"); + h1 { + font-size: 5rem; + margin: 0; + text-underline-offset: 0.5rem; + a { + color: var(--text-color) !important; + transition: text-decoration-thickness 0.1s ease-in-out; + } + } + p { + margin-top: 0.25rem; + font-size: 1.5rem; + font-weight: lighter; + } } -@font-face { - font-family: "Equity A"; - font-style: normal; - font-weight: bold; - font-stretch: normal; - font-display: auto; - src: url("data:font/woff2;base64," + $equity-a-bold) format("woff2"); +.article-content { + font-size: 1.25rem; } -@font-face { - font-family: "Equity A"; +.header-anchor { + text-decoration: none; + font-size: 1rem; + vertical-align: middle; + // drag it up so it's more in the middle + padding-bottom: 0.25rem; + color: gray !important; + &:hover { + color: var(--link-color) !important; + } +} + +.footnote-reference > a { + text-decoration: none; +} + +footer { + margin-bottom: var(--page-vertical-margin); font-style: italic; - font-weight: bold; - font-stretch: normal; - font-display: auto; - src: url("data:font/woff2;base64," + $equity-a-bold-italic) format("woff2"); + font-size: 1.5rem; + ul { + padding: 0; + list-style: none; + } +} + +@media (min-width: calc(768px + 2rem)) { + .container { + margin-left: var(--page-horizontal-margin); + margin-right: var(--page-horizontal-margin); + } } diff --git a/site_test/css/normalize.scss b/site_test/css/normalize.scss new file mode 100644 index 0000000..5d8c1e3 --- /dev/null +++ b/site_test/css/normalize.scss @@ -0,0 +1,351 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { + /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/site_test/fonts/equity-a-bold-italic.woff2 b/site_test/fonts/equity-a-bold-italic.woff2 deleted file mode 100644 index 339cc03..0000000 Binary files a/site_test/fonts/equity-a-bold-italic.woff2 and /dev/null differ diff --git a/site_test/fonts/equity-a-bold.woff2 b/site_test/fonts/equity-a-bold.woff2 deleted file mode 100644 index 2977a86..0000000 Binary files a/site_test/fonts/equity-a-bold.woff2 and /dev/null differ diff --git a/site_test/fonts/equity-a-italic.woff2 b/site_test/fonts/equity-a-italic.woff2 deleted file mode 100644 index 21ed50d..0000000 Binary files a/site_test/fonts/equity-a-italic.woff2 and /dev/null differ diff --git a/site_test/fonts/equity-a-regular.woff2 b/site_test/fonts/equity-a-regular.woff2 deleted file mode 100644 index a242789..0000000 Binary files a/site_test/fonts/equity-a-regular.woff2 and /dev/null differ diff --git a/site_test/index.html b/site_test/index.html index 8f47c8a..bc980a7 100644 --- a/site_test/index.html +++ b/site_test/index.html @@ -2,8 +2,8 @@ {% block content -%} -hello - -{{ latest_post.metadata.title }} + + {{ latest_post.metadata.title }} + {%- endblock %} diff --git a/site_test/layout/article.html b/site_test/layout/article.html index d0e599c..8c4bd48 100644 --- a/site_test/layout/article.html +++ b/site_test/layout/article.html @@ -31,6 +31,16 @@
+

+ {% if metadata.html_title %} + {{ metadata.html_title }} + {% else %} + {{ metadata.title }} + {% endif %} +

+
+ {{ content }} +
{%- endblock %} diff --git a/site_test/layout/default.html b/site_test/layout/default.html index f450589..db3f1c0 100644 --- a/site_test/layout/default.html +++ b/site_test/layout/default.html @@ -33,7 +33,29 @@ - {% block content %}{% endblock %} +
+
+

Shadowfacts

+

The outer part of a shadow is called the penumbra.

+
+
+ +
+
+ {% block content %}{% endblock %} +
+
+ + diff --git a/src/generator/css.rs b/src/generator/css.rs index 79534d9..b8c47b8 100644 --- a/src/generator/css.rs +++ b/src/generator/css.rs @@ -27,15 +27,36 @@ pub fn make_graph( watcher: Rc>, ) -> Input<()> { let mut watcher_ = watcher.borrow_mut(); - let mut fonts = HashMap::<&'static str, Input>::new(); - let filenames: &[&str] = &[ - "equity-a-regular", - "equity-a-bold", - "equity-a-italic", - "equity-a-bold-italic", + let mut files = HashMap::<&'static str, Input>::new(); + let filenames = [ + ( + "valkyrie-a-regular", + content_path("css/fonts/valkyrie_a_regular.woff2"), + ), + ( + "valkyrie-a-bold", + content_path("css/fonts/valkyrie_a_bold.woff2"), + ), + ( + "valkyrie-a-italic", + content_path("css/fonts/valkyrie_a_italic.woff2"), + ), + ( + "valkyrie-a-bold-italic", + content_path("css/fonts/valkyrie_a_bold_italic.woff2"), + ), + ("external-link", content_path("css/external-link.svg")), + ( + "berkeley-mono-regular", + content_path("css/fonts/BerkeleyMono-Regular.woff2"), + ), + ( + "berkeley-mono-italic", + content_path("css/fonts/BerkeleyMono-Italic.woff2"), + ), ]; - for name in filenames { - fonts.insert(name, read_font(name, builder, &mut *watcher_)); + for (name, path) in filenames.into_iter() { + files.insert(name, read_file(path, builder, &mut *watcher_)); } drop(watcher_); @@ -44,21 +65,18 @@ pub fn make_graph( watcher, watched: HashSet::new(), invalidate: Rc::clone(&invalidate_css_box), - fonts, + fonts: files, }); invalidate_css_box.replace(Some(invalidate_css)); css } -fn read_font( - name: &'static str, +fn read_file( + path: PathBuf, builder: &mut GraphBuilder<(), Asynchronous>, watcher: &mut FileWatcher, ) -> Input { - let mut path = content_path("fonts"); - path.push(name); - path.set_extension("woff2"); - let (font, invalidate) = builder.add_invalidatable_rule(ReadFont(path.clone())); + let (font, invalidate) = builder.add_invalidatable_rule(ReadFile(path.clone())); watcher.watch(path, move || invalidate.invalidate()); font } @@ -139,8 +157,8 @@ impl<'a> Fs for TrackingFs<'a> { } #[derive(InputVisitable)] -struct ReadFont(PathBuf); -impl Rule for ReadFont { +struct ReadFile(PathBuf); +impl Rule for ReadFile { type Output = String; fn evaluate(&mut self) -> Self::Output { match std::fs::read(&self.0) { diff --git a/src/generator/home.rs b/src/generator/home.rs index 0a7f0b6..f368a38 100644 --- a/src/generator/home.rs +++ b/src/generator/home.rs @@ -36,6 +36,7 @@ pub fn make_graph( latest_post, |latest_post, ctx| { ctx.insert("latest_post", latest_post); + ctx.insert("latest_post_permalink", &latest_post.permalink()); ctx.insert("latest_post_content", latest_post.content.html()); }, )); diff --git a/src/generator/markdown/heading_anchors.rs b/src/generator/markdown/heading_anchors.rs index c6532da..3fb5bb1 100644 --- a/src/generator/markdown/heading_anchors.rs +++ b/src/generator/markdown/heading_anchors.rs @@ -3,7 +3,7 @@ use std::iter::empty; use crate::generator::util::one_more::OneMore; use crate::generator::util::slugify::slugify_iter; use State::*; -use pulldown_cmark::{CowStr, Event, HeadingLevel, Tag, TagEnd, html}; +use pulldown_cmark::{CowStr, Event, Tag, TagEnd, html}; pub struct HeadingAnchors<'a, I: Iterator>> { iter: I, @@ -42,14 +42,11 @@ impl<'a, I: Iterator>> HeadingAnchors<'a, I> { let slug = slugify_events(&inside_heading); let mut inner = String::new(); html::push_html(&mut inner, inside_heading.into_iter()); - let hash_marks = level_hash_marks(level); let heading_html = format!( "\ - <{} id=\"{}\">\ - {} \ - {}\ - ", - level, slug, slug, hash_marks, inner, level + <{level} id=\"{slug}\">\ + {inner} § \ + ", ); Event::Html(CowStr::Boxed(heading_html.into_boxed_str())) } @@ -82,17 +79,6 @@ fn slugify_events<'a>(events: &[Event<'a>]) -> String { slugify_iter(chars) } -fn level_hash_marks(level: HeadingLevel) -> &'static str { - match level { - HeadingLevel::H1 => "#", - HeadingLevel::H2 => "##", - HeadingLevel::H3 => "###", - HeadingLevel::H4 => "####", - HeadingLevel::H5 => "#####", - HeadingLevel::H6 => "######", - } -} - enum State { TakeEvent, Done, diff --git a/src/generator/templates.rs b/src/generator/templates.rs index 835d8cd..6f676f4 100644 --- a/src/generator/templates.rs +++ b/src/generator/templates.rs @@ -8,7 +8,7 @@ use compute_graph::{ }; use log::error; use once_cell::sync::Lazy; -use tera::{Context, Tera}; +use tera::{Context, Filter, Tera}; use crate::generator::util::output_writer; @@ -18,7 +18,10 @@ pub fn make_graph( builder: &mut GraphBuilder<(), Asynchronous>, watcher: &mut FileWatcher, ) -> Input { - let empty_templates = builder.add_value(Templates::default()); + let mut empty_templates = Templates::default(); + empty_templates.register_filter("pretty_date", filters::pretty_date); + empty_templates.register_filter("pretty_datetime", filters::pretty_datetime); + let empty_templates = builder.add_value(empty_templates); let default_path = content_path("layout/default.html"); let (default, invalidate_default) = builder.add_invalidatable_rule(AddTemplate::new( @@ -64,6 +67,12 @@ impl NodeValue for Templates { } } +impl Templates { + fn register_filter(&mut self, name: &str, filter: impl Filter + 'static) { + self.tera.register_filter(name, filter); + } +} + static DOMAIN: Lazy = Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned())); static CB: Lazy = Lazy::new(|| { @@ -128,5 +137,60 @@ impl Rule for RenderTemplate { fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.output_path.display()) +pub mod filters { + use chrono::{DateTime, Datelike, Local}; + use serde::Deserialize; + use std::collections::HashMap; + use tera::{Result, Value}; + + // pub fn iso_date(date: &NaiveDate) -> String { + // Utc::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()) + // .format("%+:0") + // .to_string() + // } + + // pub fn iso_datetime(datetime: &DateTime) -> String + // where + // Tz: TimeZone, + // Tz::Offset: Display, + // { + // datetime.format("%+:0").to_string() + // } + + const MONTHS: &[&str; 12] = &[ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ]; + + pub fn pretty_date(value: &Value, _args: &HashMap) -> Result { + let date = DateTime::::deserialize(value)?; + Ok(Value::String(format_pretty_date(date))) } + + fn format_pretty_date(date: DateTime) -> String { + let month = MONTHS[date.month0() as usize]; + let suffix = match date.day() { + 1 | 21 | 31 => "st", + 2 | 22 => "nd", + 3 | 23 => "rd", + _ => "th", + }; + format!("{} {}{}, {}", month, date.day(), suffix, date.year()) + } + + pub fn pretty_datetime(value: &Value, _args: &HashMap) -> Result { + dbg!(value); + let datetime = DateTime::::deserialize(value)?; + dbg!(&datetime); + let s = format!( + "{} {}", + datetime.format("%-I:%M:%S %p"), + format_pretty_date(datetime) + ); + Ok(Value::String(s)) + } + + // pub fn reading_time(words: &u32) -> u32 { + // let wpm = 225.0; + // (*words as f32 / wpm).max(1.0) as u32 + // } } diff --git a/src/generator/util/templates.rs b/src/generator/util/templates.rs index 653eeb4..7f39222 100644 --- a/src/generator/util/templates.rs +++ b/src/generator/util/templates.rs @@ -23,55 +23,3 @@ // &*GENERATED_AT // } // } - -pub mod filters { - use std::fmt::Display; - - use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Utc}; - - pub fn iso_date(date: &NaiveDate) -> String { - Utc::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()) - .format("%+:0") - .to_string() - } - - pub fn iso_datetime(datetime: &DateTime) -> String - where - Tz: TimeZone, - Tz::Offset: Display, - { - datetime.format("%+:0").to_string() - } - - const MONTHS: &[&str; 12] = &[ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - ]; - - pub fn pretty_date(date: &impl Datelike) -> String { - let month = MONTHS[date.month0() as usize]; - let suffix = match date.day() { - 1 | 21 | 31 => "st", - 2 | 22 => "nd", - 3 | 23 => "rd", - _ => "th", - }; - format!("{} {}{}, {}", month, date.day(), suffix, date.year()) - } - - pub fn pretty_datetime(datetime: &DateTime) -> String - where - Tz: TimeZone, - Tz::Offset: Display, - { - format!( - "{} {}", - datetime.format("%-I:%M:%S %p"), - pretty_date(datetime) - ) - } - - pub fn reading_time(words: &u32) -> u32 { - let wpm = 225.0; - (*words as f32 / wpm).max(1.0) as u32 - } -}