use crate::signalboost::Person; use maud::{html, Markup, Render}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, fmt::{self, Display}, }; #[derive(Clone, Deserialize, Default)] pub struct Config { pub signalboost: Vec, pub authors: HashMap, #[serde(rename = "defaultAuthor")] pub default_author: Author, pub port: u16, #[serde(rename = "clackSet")] pub clack_set: Vec, #[serde(rename = "miToken")] pub mi_token: String, #[serde(rename = "jobHistory")] pub job_history: Vec, #[serde(rename = "seriesDescriptions")] pub series_descriptions: Vec, #[serde(rename = "seriesDescMap")] pub series_desc_map: HashMap, #[serde(rename = "notableProjects")] pub notable_projects: Vec, #[serde(rename = "contactLinks")] pub contact_links: Vec, pub pronouns: Vec, } fn schema_pronoun_set_type() -> String { "https://xeiaso.net/api/json-ld/PronounSet".to_string() } #[derive(Clone, Deserialize, Serialize, Default)] pub struct PronounSet { #[serde(rename = "@type", default = "schema_pronoun_set_type")] pub schema_type: String, nominative: String, accusative: String, #[serde(rename = "possessiveDeterminer")] possessive_determiner: String, possessive: String, reflexive: String, singular: bool, } impl Render for PronounSet { fn render(&self) -> Markup { html! { big { (self.nominative) "/" (self.accusative) } table { tr { th { "Subject" } td {(self.nominative)} } tr { th { "Object" } td {(self.accusative)} } tr { th { "Dependent Possessive" } td {(self.possessive_determiner)} } tr { th { "Independent Possessive" } td {(self.possessive)} } tr { th { "Reflexive" } td {(self.reflexive)} } } p {"Here are some example sentences with these pronouns:"} ul { li { i{(self.nominative)} " went to the park." } li { "I went with " i{(self.accusative)} "." } li { i{(self.nominative)} " brought " i{(self.possessive_determiner)} " frisbee." } li { "At least I think it was " i{(self.possessive)} "." } li { i{(self.nominative)} " threw the frisbee to " i{(self.reflexive)} "." } } @if !self.singular { p { "Please note that this pronoun is normally a plural pronoun. It is used here to refer to a single person. For more information on this, see " a href="https://www.merriam-webster.com/words-at-play/singular-nonbinary-they" {"this page from Merriam-Webster"} " that will explain in more detail." } } } } } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Link { pub url: String, pub title: String, pub description: String, } impl Render for Link { fn render(&self) -> Markup { html! { span { a href=(self.url) {(self.title)} @if !self.description.is_empty() { ": " (self.description) } } } } } #[derive(Clone, Deserialize, Serialize)] pub enum StockKind { Grant, Options, } impl Default for StockKind { fn default() -> Self { StockKind::Options } } fn schema_person_context() -> serde_json::Value { serde_json::json!(["https://schema.org/", { "pronouns": { "@type": "https://xeiaso.net/api/json-ld/PronounSet", }, }]) } fn schema_person_type() -> String { "Person".to_string() } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Author { #[serde(rename = "@context", default = "schema_person_context")] pub context: serde_json::Value, #[serde(rename = "@type", default = "schema_person_type")] pub schema_type: String, pub name: String, #[serde(skip_serializing)] pub handle: String, #[serde(rename = "image", skip_serializing_if = "Option::is_none")] pub pic_url: Option, #[serde(rename = "inSystem", skip_serializing)] pub in_system: bool, #[serde(rename = "jobTitle", skip_serializing_if = "String::is_empty")] pub job_title: String, #[serde(rename = "sameAs", skip_serializing_if = "Vec::is_empty")] pub same_as: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub url: Option, pub pronouns: PronounSet, } #[derive(Clone, Deserialize, Serialize, Default)] pub struct SeriesDescription { pub name: String, pub details: String, } impl Render for SeriesDescription { fn render(&self) -> Markup { html! { span { a href={"/blog/series/" (self.name)} { (self.name) } ": " (self.details) } } } } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Stock { pub amount: i32, #[serde(rename = "cliffYears")] pub cliff_years: i32, pub kind: StockKind, pub liquid: bool, #[serde(rename = "vestingYears")] pub vesting_years: i32, } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Location { pub city: String, #[serde(rename = "stateOrProvince")] pub state_or_province: String, pub country: String, pub remote: bool, } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Salary { pub amount: i32, pub per: String, pub currency: String, pub stock: Option, } impl Display for Salary { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}${}/{}", self.currency, self.amount, self.per) } } impl Render for Salary { fn render(&self) -> Markup { if self.stock.is_none() { return html! { (maud::display(self)) }; } let stock = self.stock.as_ref().unwrap(); html! { details { summary { (maud::display(self)) } p{ (stock.amount) " " @if stock.liquid { "liquid" } " " @match stock.kind { StockKind::Options => { "options" }, StockKind::Grant => { "granted shares" } } ". Vesting for " (stock.vesting_years) " " @if stock.vesting_years == 1 { "year" } @else { "years" } " " " with a cliff of " (stock.cliff_years) " " @if stock.cliff_years == 1 { "year" } @else { "years" } "." } } } } } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Job { pub company: Company, pub title: String, #[serde(rename = "startDate")] pub start_date: String, #[serde(rename = "endDate")] pub end_date: Option, #[serde(rename = "daysWorked")] pub days_worked: Option, #[serde(rename = "daysBetween")] pub days_between: Option, pub salary: Salary, #[serde(rename = "leaveReason")] pub leave_reason: Option, pub locations: Vec, pub highlights: Vec, #[serde(rename = "hideFromResume")] pub hide_from_resume: bool, } #[derive(Clone, Deserialize, Serialize, Default)] pub struct Company { pub name: String, pub url: Option, pub tagline: String, pub location: Location, pub defunct: bool, } impl Render for Job { fn render(&self) -> Markup { html! { tr { td { (self.title) } td { (self.start_date) } td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) } td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) } td { (self.salary) } td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) } } } } }