aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorXe Iaso <me@christine.website>2022-11-25 19:01:10 -0500
committerGitHub <noreply@github.com>2022-11-25 19:01:10 -0500
commitcc933b31fd23bb06e95bf41f848a1c99353d44ae (patch)
treeaf8288dbd4db2ad886d6b326bffb1c9d5b568de3 /src
parent551e0384c923ff3ee98cfddf7e3eb42c6dbb2941 (diff)
downloadxesite-cc933b31fd23bb06e95bf41f848a1c99353d44ae.tar.xz
xesite-cc933b31fd23bb06e95bf41f848a1c99353d44ae.zip
Start version 3 (#573)
* Start version 3 * Change version to 3.0.0 in Cargo.toml * Add metadata for series * Change types for signal boosts * Add start of LaTeX resume generation at Nix time * Add start of proper author tagging for posts in JSONFeed and ldjson * Convert templates to use Maud * Add start of dynamic resume generation from dhall * Make patrons page embed thumbnails TODO: * [ ] Remove the rest of the old templates * [ ] Bring in Xeact for the share on mastodon button * [ ] Site update post Signed-off-by: Xe <me@christine.website> * fix nix builds Signed-off-by: Xe Iaso <me@christine.website> * fix dhall build Signed-off-by: Xe Iaso <me@christine.website> * fix non-flakes build Signed-off-by: Xe Iaso <me@christine.website> * make new mastodon share button Signed-off-by: Xe Iaso <me@christine.website> * remove the rest of the ructe templates that I can remove Signed-off-by: Xe Iaso <me@christine.website> * refactor blogposts to its own file Signed-off-by: Xe Iaso <me@christine.website> * move resume to be generated by nix Signed-off-by: Xe Iaso <me@christine.website> * write article Signed-off-by: Xe Iaso <me@christine.website> * blog/site-update-v3: hero image Signed-off-by: Xe Iaso <me@christine.website> * add site update series tag to site updates Signed-off-by: Xe Iaso <me@christine.website> Signed-off-by: Xe <me@christine.website> Signed-off-by: Xe Iaso <me@christine.website>
Diffstat (limited to 'src')
-rw-r--r--src/app/config.rs90
-rw-r--r--src/app/mod.rs8
-rwxr-xr-xsrc/frontend/build.sh6
-rw-r--r--src/frontend/deno.json8
-rw-r--r--src/frontend/import_map.json8
-rw-r--r--src/frontend/mastodon_share_button.tsx55
-rw-r--r--src/frontend/xeact/jsx-runtime.js16
-rw-r--r--src/frontend/xeact/xeact.js88
-rw-r--r--src/frontend/xeact/xeact.ts9
-rw-r--r--src/handlers/blog.rs66
-rw-r--r--src/handlers/gallery.rs28
-rw-r--r--src/handlers/mod.rs77
-rw-r--r--src/handlers/talks.rs28
-rw-r--r--src/main.rs12
-rw-r--r--src/post/mod.rs24
-rw-r--r--src/post/schemaorg.rs14
-rw-r--r--src/signalboost.rs12
-rw-r--r--src/tmpl/asciiart.txt45
-rw-r--r--src/tmpl/blog.rs215
-rw-r--r--src/tmpl/mod.rs573
-rw-r--r--src/tmpl/nag.rs2
21 files changed, 1229 insertions, 155 deletions
diff --git a/src/app/config.rs b/src/app/config.rs
index e0a4d90..6499e72 100644
--- a/src/app/config.rs
+++ b/src/app/config.rs
@@ -1,7 +1,8 @@
use crate::signalboost::Person;
-use maud::{html, Markup};
+use maud::{html, Markup, Render};
use serde::{Deserialize, Serialize};
use std::{
+ collections::HashMap,
fmt::{self, Display},
path::PathBuf,
};
@@ -9,7 +10,9 @@ use std::{
#[derive(Clone, Deserialize, Default)]
pub struct Config {
pub signalboost: Vec<Person>,
- pub authors: Vec<Author>,
+ pub authors: HashMap<String, Author>,
+ #[serde(rename = "defaultAuthor")]
+ pub default_author: Author,
pub port: u16,
#[serde(rename = "clackSet")]
pub clack_set: Vec<String>,
@@ -19,6 +22,35 @@ pub struct Config {
pub mi_token: String,
#[serde(rename = "jobHistory")]
pub job_history: Vec<Job>,
+ #[serde(rename = "seriesDescriptions")]
+ pub series_descriptions: Vec<SeriesDescription>,
+ #[serde(rename = "seriesDescMap")]
+ pub series_desc_map: HashMap<String, String>,
+ #[serde(rename = "notableProjects")]
+ pub notable_projects: Vec<Link>,
+ #[serde(rename = "contactLinks")]
+ pub contact_links: Vec<Link>,
+}
+
+#[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)]
@@ -33,17 +65,51 @@ impl Default for StockKind {
}
}
+fn schema_context() -> String {
+ "http://schema.org/".to_string()
+}
+
+fn schema_person_type() -> String {
+ "Person".to_string()
+}
+
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Author {
+ #[serde(rename = "@context", default = "schema_context")]
+ pub context: String,
+ #[serde(rename = "@type", default = "schema_person_type")]
+ pub schema_type: String,
pub name: String,
+ #[serde(skip_serializing)]
pub handle: String,
- #[serde(rename = "picUrl")]
+ #[serde(rename = "image", skip_serializing_if = "Option::is_none")]
pub pic_url: Option<String>,
- pub link: Option<String>,
- pub twitter: Option<String>,
- pub default: bool,
- #[serde(rename = "inSystem")]
+ #[serde(rename = "inSystem", skip_serializing)]
pub in_system: bool,
+ #[serde(rename = "jobTitle")]
+ pub job_title: String,
+ #[serde(rename = "sameAs")]
+ pub same_as: Vec<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub url: Option<String>,
+}
+
+#[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)]
@@ -80,8 +146,8 @@ impl Display for Salary {
}
}
-impl Salary {
- pub fn html(&self) -> Markup {
+impl Render for Salary {
+ fn render(&self) -> Markup {
if self.stock.is_none() {
return html! { (maud::display(self)) };
}
@@ -162,15 +228,15 @@ pub struct Company {
pub defunct: bool,
}
-impl Job {
- pub fn pay_history_row(&self) -> Markup {
+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.html()) }
+ td { (self.salary) }
td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) }
}
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 73320e1..6753ff0 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -1,7 +1,7 @@
use crate::{post::Post, signalboost::Person};
use chrono::prelude::*;
use color_eyre::eyre::Result;
-use std::{fs, path::PathBuf, sync::Arc};
+use std::{path::PathBuf, sync::Arc};
use tracing::{error, instrument};
pub mod config;
@@ -48,7 +48,6 @@ pub const ICON: &'static str = "https://xeiaso.net/static/img/avatar.png";
pub struct State {
pub cfg: Arc<Config>,
pub signalboost: Vec<Person>,
- pub resume: String,
pub blog: Vec<Post>,
pub gallery: Vec<Post>,
pub talks: Vec<Post>,
@@ -62,8 +61,6 @@ pub struct State {
pub async fn init(cfg: PathBuf) -> Result<State> {
let cfg: Arc<Config> = Arc::new(serde_dhall::from_file(cfg).parse()?);
let sb = cfg.signalboost.clone();
- let resume = fs::read_to_string(cfg.clone().resume_fname.clone())?;
- let resume: String = xesite_markdown::render(&resume)?;
let mi = mi::Client::new(
cfg.clone().mi_token.clone(),
crate::APPLICATION_NAME.to_string(),
@@ -85,7 +82,7 @@ pub async fn init(cfg: PathBuf) -> Result<State> {
everything.sort();
everything.reverse();
- let today = Utc::today();
+ let today = Utc::now().date_naive();
let everything: Vec<Post> = everything
.into_iter()
.filter(|p| today.num_days_from_ce() >= p.date.num_days_from_ce())
@@ -141,7 +138,6 @@ pub async fn init(cfg: PathBuf) -> Result<State> {
mi,
cfg,
signalboost: sb,
- resume,
blog,
gallery,
talks,
diff --git a/src/frontend/build.sh b/src/frontend/build.sh
new file mode 100755
index 0000000..ad31763
--- /dev/null
+++ b/src/frontend/build.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -e
+
+export RUST_LOG=info
+deno bundle ./mastodon_share_button.tsx ../../static/js/mastodon_share_button.js
diff --git a/src/frontend/deno.json b/src/frontend/deno.json
new file mode 100644
index 0000000..b763d25
--- /dev/null
+++ b/src/frontend/deno.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "xeact",
+ "lib": ["esnext", "dom", "dom.iterable"]
+ },
+ "importMap": "./import_map.json",
+}
diff --git a/src/frontend/import_map.json b/src/frontend/import_map.json
new file mode 100644
index 0000000..20cfc64
--- /dev/null
+++ b/src/frontend/import_map.json
@@ -0,0 +1,8 @@
+{
+ "imports": {
+ "xeact": "./xeact/xeact.ts",
+ "xeact/jsx-runtime": "./xeact/jsx-runtime.js",
+ "/": "./",
+ "./": "./"
+ }
+}
diff --git a/src/frontend/mastodon_share_button.tsx b/src/frontend/mastodon_share_button.tsx
new file mode 100644
index 0000000..d689abd
--- /dev/null
+++ b/src/frontend/mastodon_share_button.tsx
@@ -0,0 +1,55 @@
+import { g, r, u, x } from "xeact";
+
+r(() => {
+ const root = g("mastodon_share_button");
+
+ let defaultURL = localStorage["mastodon_instance"];
+
+ const title = document.querySelectorAll('meta[property="og:title"]')[0]
+ .getAttribute("content");
+ let series = g("mastodon_share_series").innerText;
+ if (series != "") {
+ series = `#${series} `;
+ }
+ const tags = g("mastodon_share_tags");
+ const articleURL = u();
+
+ const tootTemplate = `${title}
+
+${articleURL}
+
+${series}${tags.innerText} @cadey@pony.social`;
+
+ const instanceBox = (
+ <input type="text" placeholder="https://pony.social" value={defaultURL} />
+ );
+ const tootBox = <textarea rows="6" cols="40">{tootTemplate}</textarea>;
+
+ const doShare = () => {
+ const instanceURL = instanceBox.value;
+ localStorage["mastodon_instance"] = instanceURL;
+ const text = tootBox.value;
+ const mastodon_url = u(instanceURL + "/share", { text });
+ console.log({ text, mastodon_url });
+ window.open(mastodon_url, "_blank");
+ };
+
+ const shareButton = <button onclick={doShare}>Share</button>;
+
+ x(root);
+
+ root.appendChild(
+ <div>
+ <details>
+ <summary>Share on Mastodon</summary>
+ <span>Instance URL (https://mastodon.example)</span>
+ <br />
+ {instanceBox}
+ <br />
+ {tootBox}
+ <br />
+ {shareButton}
+ </details>
+ </div>,
+ );
+});
diff --git a/src/frontend/xeact/jsx-runtime.js b/src/frontend/xeact/jsx-runtime.js
new file mode 100644
index 0000000..58ccfaa
--- /dev/null
+++ b/src/frontend/xeact/jsx-runtime.js
@@ -0,0 +1,16 @@
+import { h } from './xeact.ts';
+
+/**
+ * Create a DOM element, assign the properties of `data` to it, and append all `data.children`.
+ *
+ * @type{function(string, Object=): HTMLElement}
+ */
+export const jsx = (tag, data) => {
+ let children = data.children;
+ delete data.children;
+ const result = h(tag, data, children);
+ result.classList.value = result.class;
+ return result;
+};
+export const jsxs = jsx;
+export const jsxDEV = jsx;
diff --git a/src/frontend/xeact/xeact.js b/src/frontend/xeact/xeact.js
new file mode 100644
index 0000000..7be9a1c
--- /dev/null
+++ b/src/frontend/xeact/xeact.js
@@ -0,0 +1,88 @@
+/**
+ * Creates a DOM element, assigns the properties of `data` to it, and appends all `children`.
+ *
+ * @type{function(string|Function, Object=, Node|Array.<Node|string>=)}
+ */
+const h = (name, data = {}, children = []) => {
+ const result = typeof name == "function" ? name(data) : Object.assign(document.createElement(name), data);
+ if (!Array.isArray(children)) {
+ children = [children];
+ }
+ result.append(...children);
+ return result;
+};
+
+/**
+ * Create a text node.
+ *
+ * Equivalent to `document.createTextNode(text)`
+ *
+ * @type{function(string): Text}
+ */
+const t = (text) => document.createTextNode(text);
+
+/**
+ * Remove all child nodes from a DOM element.
+ *
+ * @type{function(Node)}
+ */
+const x = (elem) => {
+ while (elem.lastChild) {
+ elem.removeChild(elem.lastChild);
+ }
+};
+
+/**
+ * Get all elements with the given ID.
+ *
+ * Equivalent to `document.getElementById(name)`
+ *
+ * @type{function(string): HTMLElement}
+ */
+const g = (name) => document.getElementById(name);
+
+/**
+ * Get all elements with the given class name.
+ *
+ * Equivalent to `document.getElementsByClassName(name)`
+ *
+ * @type{function(string): HTMLCollectionOf.<Element>}
+ */
+const c = (name) => document.getElementsByClassName(name);
+
+/** @type{function(string): HTMLCollectionOf.<Element>} */
+const n = (name) => document.getElementsByName(name);
+
+/**
+ * Get all elements matching the given HTML selector.
+ *
+ * Matches selectors with `document.querySelectorAll(selector)`
+ *
+ * @type{function(string): Array.<HTMLElement>}
+ */
+const s = (selector) => Array.from(document.querySelectorAll(selector));
+
+/**
+ * Generate a relative URL from `url`, appending all key-value pairs from `params` as URL-encoded parameters.
+ *
+ * @type{function(string=, Object=): string}
+ */
+const u = (url = "", params = {}) => {
+ let result = new URL(url, window.location.href);
+ Object.entries(params).forEach((kv) => {
+ let [k, v] = kv;
+ result.searchParams.set(k, v);
+ });
+ return result.toString();
+};
+
+/**
+ * Takes a callback to run when all DOM content is loaded.
+ *
+ * Equivalent to `window.addEventListener('DOMContentLoaded', callback)`
+ *
+ * @type{function(function())}
+ */
+const r = (callback) => window.addEventListener('DOMContentLoaded', callback);
+
+export { h, t, x, g, c, n, u, s, r };
diff --git a/src/frontend/xeact/xeact.ts b/src/frontend/xeact/xeact.ts
new file mode 100644
index 0000000..8974ec1
--- /dev/null
+++ b/src/frontend/xeact/xeact.ts
@@ -0,0 +1,9 @@
+export * from "./xeact.js";
+
+declare global {
+ export namespace JSX {
+ interface IntrinsicElements {
+ [elemName: string]: any;
+ }
+ }
+}
diff --git a/src/handlers/blog.rs b/src/handlers/blog.rs
index 012a7ad..feeab44 100644
--- a/src/handlers/blog.rs
+++ b/src/handlers/blog.rs
@@ -1,14 +1,15 @@
use super::Result;
-use crate::{app::State, post::Post, templates};
+use crate::{app::State, post::Post, tmpl};
use axum::{
extract::{Extension, Path},
- response::Html,
+ http::StatusCode,
};
use http::HeaderMap;
use lazy_static::lazy_static;
+use maud::Markup;
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
use std::sync::Arc;
-use tracing::{error, instrument};
+use tracing::instrument;
lazy_static! {
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
@@ -19,40 +20,27 @@ lazy_static! {
}
#[instrument(skip(state))]
-pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn index(Extension(state): Extension<Arc<State>>) -> Result<Markup> {
let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::blogindex_html(&mut result, state.blog.clone())?;
- Ok(Html(result))
+ let result = tmpl::post_index(&state.blog, "Blogposts", true);
+ Ok(result)
}
#[instrument(skip(state))]
-pub async fn series(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn series(Extension(state): Extension<Arc<State>>) -> Result<Markup> {
let state = state.clone();
- let mut series: Vec<String> = vec![];
- let mut result: Vec<u8> = vec![];
- for post in &state.blog {
- if post.front_matter.series.is_some() {
- series.push(post.front_matter.series.as_ref().unwrap().clone());
- }
- }
-
- series.sort();
- series.dedup();
-
- templates::series_html(&mut result, series)?;
- Ok(Html(result))
+ Ok(tmpl::blog_series(&state.cfg.clone().series_descriptions))
}
#[instrument(skip(state))]
pub async fn series_view(
Path(series): Path<String>,
Extension(state): Extension<Arc<State>>,
-) -> Result {
+) -> (StatusCode, Markup) {
let state = state.clone();
+ let cfg = state.cfg.clone();
let mut posts: Vec<Post> = vec![];
- let mut result: Vec<u8> = vec![];
for post in &state.blog {
if post.front_matter.series.is_none() {
@@ -64,13 +52,25 @@ pub async fn series_view(
posts.push(post.clone());
}
+ posts.reverse();
+
+ let desc = cfg.series_desc_map.get(&series);
+
if posts.len() == 0 {
- error!("series not found");
- return Err(super::Error::SeriesNotFound(series));
+ (
+ StatusCode::NOT_FOUND,
+ tmpl::error(format!("series not found: {series}")),
+ )
+ } else {
+ if let Some(desc) = desc {
+ (StatusCode::OK, tmpl::series_view(&series, desc, &posts))
+ } else {
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ tmpl::error(format!("series metadata in dhall not found: {series}")),
+ )
+ }
}
-
- templates::series_posts_html(&mut result, series, &posts).unwrap();
- Ok(Html(result))
}
#[instrument(skip(state, headers))]
@@ -78,7 +78,7 @@ pub async fn post_view(
Path(name): Path<String>,
Extension(state): Extension<Arc<State>>,
headers: HeaderMap,
-) -> Result {
+) -> Result<(StatusCode, Markup)> {
let mut want: Option<Post> = None;
let want_link = format!("blog/{}", name);
@@ -96,15 +96,13 @@ pub async fn post_view(
};
match want {
- None => Err(super::Error::PostNotFound(name)),
+ None => Ok((StatusCode::NOT_FOUND, tmpl::not_found(want_link))),
Some(post) => {
HIT_COUNTER
.with_label_values(&[name.clone().as_str()])
.inc();
- let body = templates::Html(post.body_html.clone());
- let mut result: Vec<u8> = vec![];
- templates::blogpost_html(&mut result, post, body, referer)?;
- Ok(Html(result))
+ let body = maud::PreEscaped(&post.body_html);
+ Ok((StatusCode::OK, tmpl::blog::blog(&post, body, referer)))
}
}
}
diff --git a/src/handlers/gallery.rs b/src/handlers/gallery.rs
index ae6c411..25c8dfa 100644
--- a/src/handlers/gallery.rs
+++ b/src/handlers/gallery.rs
@@ -1,10 +1,8 @@
-use super::{Error::*, Result};
-use crate::{app::State, post::Post, templates};
-use axum::{
- extract::{Extension, Path},
- response::Html,
-};
+use crate::{app::State, post::Post, tmpl};
+use axum::extract::{Extension, Path};
+use http::StatusCode;
use lazy_static::lazy_static;
+use maud::Markup;
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
use std::sync::Arc;
use tracing::instrument;
@@ -18,36 +16,32 @@ lazy_static! {
}
#[instrument(skip(state))]
-pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn index(Extension(state): Extension<Arc<State>>) -> Markup {
let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::galleryindex_html(&mut result, state.gallery.clone())?;
- Ok(Html(result))
+ tmpl::gallery_index(&state.gallery)
}
#[instrument(skip(state))]
pub async fn post_view(
Path(name): Path<String>,
Extension(state): Extension<Arc<State>>,
-) -> Result {
+) -> (StatusCode, Markup) {
let mut want: Option<Post> = None;
+ let link = format!("gallery/{}", name);
for post in &state.gallery {
- if post.link == format!("gallery/{}", name) {
+ if post.link == link {
want = Some(post.clone());
}
}
match want {
- None => Err(PostNotFound(name)),
+ None => (StatusCode::NOT_FOUND, tmpl::not_found(link)),
Some(post) => {
HIT_COUNTER
.with_label_values(&[name.clone().as_str()])
.inc();
- let body = templates::Html(post.body_html.clone());
- let mut result: Vec<u8> = vec![];
- templates::gallerypost_html(&mut result, post, body)?;
- Ok(Html(result))
+ (StatusCode::OK, tmpl::blog::gallery(&post))
}
}
}
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 37fcac1..a20d654 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -1,4 +1,4 @@
-use crate::{app::State, templates};
+use crate::{app::State, tmpl};
use axum::{
body,
extract::Extension,
@@ -7,6 +7,7 @@ use axum::{
};
use chrono::{Datelike, Timelike, Utc, Weekday};
use lazy_static::lazy_static;
+use maud::Markup;
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
use std::sync::Arc;
use tracing::instrument;
@@ -67,82 +68,74 @@ lazy_static! {
};
}
-#[instrument]
-pub async fn index() -> Result {
+#[instrument(skip(state))]
+pub async fn index(Extension(state): Extension<Arc<State>>) -> Result<Markup> {
HIT_COUNTER.with_label_values(&["index"]).inc();
- let mut result: Vec<u8> = vec![];
- templates::index_html(&mut result)?;
- Ok(Html(result))
+ let state = state.clone();
+ let cfg = state.cfg.clone();
+
+ Ok(tmpl::index(&cfg.default_author, &cfg.notable_projects))
}
-#[instrument]
-pub async fn contact() -> Result {
+#[instrument(skip(state))]
+pub async fn contact(Extension(state): Extension<Arc<State>>) -> Markup {
HIT_COUNTER.with_label_values(&["contact"]).inc();
- let mut result: Vec<u8> = vec![];
- templates::contact_html(&mut result)?;
- Ok(Html(result))
+ let state = state.clone();
+ let cfg = state.cfg.clone();
+
+ crate::tmpl::contact(&cfg.contact_links)
}
#[instrument]
-pub async fn feeds() -> Result {
+pub async fn feeds() -> Markup {
HIT_COUNTER.with_label_values(&["feeds"]).inc();
- let mut result: Vec<u8> = vec![];
- templates::feeds_html(&mut result)?;
- Ok(Html(result))
+ crate::tmpl::feeds()
}
#[axum_macros::debug_handler]
#[instrument(skip(state))]
-pub async fn salary_transparency(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn salary_transparency(Extension(state): Extension<Arc<State>>) -> Result<Markup> {
HIT_COUNTER
.with_label_values(&["salary_transparency"])
.inc();
let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::salary_transparency(&mut result, state.cfg.clone())?;
- Ok(Html(result))
+ let cfg = state.cfg.clone();
+
+ Ok(tmpl::salary_transparency(&cfg.job_history))
}
#[axum_macros::debug_handler]
-#[instrument(skip(state))]
-pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn resume() -> Markup {
HIT_COUNTER.with_label_values(&["resume"]).inc();
- let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::resume_html(&mut result, templates::Html(state.resume.clone()))?;
- Ok(Html(result))
+
+ tmpl::resume()
}
#[instrument(skip(state))]
-pub async fn patrons(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn patrons(Extension(state): Extension<Arc<State>>) -> (StatusCode, Markup) {
HIT_COUNTER.with_label_values(&["patrons"]).inc();
let state = state.clone();
- let mut result: Vec<u8> = vec![];
match &state.patrons {
- None => Err(Error::NoPatrons),
- Some(patrons) => {
- templates::patrons_html(&mut result, patrons.clone())?;
- Ok(Html(result))
- }
+ None => (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ tmpl::error("Patreon API config is broken, no patrons in ram"),
+ ),
+ Some(patrons) => (StatusCode::IM_A_TEAPOT, tmpl::patrons(&patrons)),
}
}
#[axum_macros::debug_handler]
#[instrument(skip(state))]
-pub async fn signalboost(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn signalboost(Extension(state): Extension<Arc<State>>) -> Markup {
HIT_COUNTER.with_label_values(&["signalboost"]).inc();
let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::signalboost_html(&mut result, state.signalboost.clone())?;
- Ok(Html(result))
+ tmpl::signalboost(&state.signalboost)
}
#[instrument]
-pub async fn not_found() -> Result {
+pub async fn not_found(uri: axum::http::Uri) -> (StatusCode, Markup) {
HIT_COUNTER.with_label_values(&["not_found"]).inc();
- let mut result: Vec<u8> = vec![];
- templates::notfound_html(&mut result, "some path".into())?;
- Ok(Html(result))
+ (StatusCode::NOT_FOUND, tmpl::not_found(uri.path()))
}
#[derive(Debug, thiserror::Error)]
@@ -170,8 +163,8 @@ pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
impl IntoResponse for Error {
fn into_response(self) -> Response {
- let mut result: Vec<u8> = vec![];
- templates::error_html(&mut result, format!("{}", self)).unwrap();
+ let result = tmpl::error(format!("{}", self));
+ let result = result.0;
let body = body::boxed(body::Full::from(result));
diff --git a/src/handlers/talks.rs b/src/handlers/talks.rs
index 59d8676..262e481 100644
--- a/src/handlers/talks.rs
+++ b/src/handlers/talks.rs
@@ -1,11 +1,9 @@
-use super::{Error::*, Result};
-use crate::{app::State, post::Post, templates};
-use axum::{
- extract::{Extension, Path},
- response::Html,
-};
-use http::header::HeaderMap;
+use super::Result;
+use crate::{app::State, post::Post, tmpl};
+use axum::extract::{Extension, Path};
+use http::{header::HeaderMap, StatusCode};
use lazy_static::lazy_static;
+use maud::Markup;
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
use std::sync::Arc;
use tracing::instrument;
@@ -19,11 +17,9 @@ lazy_static! {
}
#[instrument(skip(state))]
-pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
+pub async fn index(Extension(state): Extension<Arc<State>>) -> Result<Markup> {
let state = state.clone();
- let mut result: Vec<u8> = vec![];
- templates::talkindex_html(&mut result, state.talks.clone())?;
- Ok(Html(result))
+ Ok(tmpl::post_index(&state.talks, "Talks", false))
}
#[instrument(skip(state, headers))]
@@ -31,7 +27,7 @@ pub async fn post_view(
Path(name): Path<String>,
Extension(state): Extension<Arc<State>>,
headers: HeaderMap,
-) -> Result {
+) -> Result<(StatusCode, Markup)> {
let mut want: Option<Post> = None;
let want_link = format!("talks/{}", name);
@@ -49,15 +45,13 @@ pub async fn post_view(
};
match want {
- None => Err(PostNotFound(name).into()),
+ None => Ok((StatusCode::NOT_FOUND, tmpl::not_found(want_link))),
Some(post) => {
HIT_COUNTER
.with_label_values(&[name.clone().as_str()])
.inc();
- let body = templates::Html(post.body_html.clone());
- let mut result: Vec<u8> = vec![];
- templates::talkpost_html(&mut result, post, body, referer)?;
- Ok(Html(result))
+ let body = maud::PreEscaped(&post.body_html);
+ Ok((StatusCode::OK, tmpl::blog::talk(&post, body, referer)))
}
}
}
diff --git a/src/main.rs b/src/main.rs
index a35db42..737ab45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,8 +4,9 @@ extern crate tracing;
use axum::{
body,
extract::Extension,
+ handler::Handler,
http::header::{self, HeaderValue, CONTENT_TYPE},
- response::{Html, Response},
+ response::Response,
routing::{get, get_service},
Router,
};
@@ -211,6 +212,7 @@ async fn main() -> Result<()> {
)
}),
)
+ .fallback(handlers::not_found.into_service())
.layer(middleware);
#[cfg(target_os = "linux")]
@@ -276,16 +278,12 @@ async fn metrics() -> Response {
.unwrap()
}
-async fn go_vanity() -> Html<Vec<u8>> {
- let mut buffer: Vec<u8> = vec![];
- templates::gitea_html(
- &mut buffer,
+async fn go_vanity() -> maud::Markup {
+ tmpl::gitea(
"christine.website/jsonfeed",
"https://tulpa.dev/Xe/jsonfeed",
"master",
)
- .unwrap();