diff --git a/common/rss.hbs b/common/rss.hbs new file mode 100644 index 0000000..cfb9abf --- /dev/null +++ b/common/rss.hbs @@ -0,0 +1,26 @@ + + + + {{ blog.title }} + {{ blog.description }} + {{ page.base_url }} + + + {{ #if next }} + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f534e79..fc610f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,7 @@ async fn default_route(_req: Request, url: Url) -> MyResult { } else { // TODO: Actually render the page... return Response::new_with_opt_str_and_init( - Some(&render::render_post(post).await?), + Some(&render::render_post(url, post).await?), ResponseInit::new() .status(200) .headers(headers!{ diff --git a/src/render.rs b/src/render.rs index 2e40696..0b703b0 100644 --- a/src/render.rs +++ b/src/render.rs @@ -17,6 +17,7 @@ include!(concat!(env!("OUT_DIR"), "/load_theme.rs")); pub fn build_routes(router: &mut Router) { router.add_route("/static/", &serve_static); + router.add_route("/feed.xml", &serve_rss); } async fn serve_static(_req: Request, url: Url) -> MyResult { @@ -37,6 +38,17 @@ async fn serve_static(_req: Request, url: Url) -> MyResult { } } +async fn serve_rss(_req: Request, url: Url) -> MyResult { + Response::new_with_opt_str_and_init( + Some(&_render_homepage(url, "rss.hbs").await?), + ResponseInit::new() + .status(200) + .headers(headers!{ + "Content-Type" => "application/rss+xml" + }.as_ref()) + ).internal_err() +} + // Context objects used when rendering pages #[derive(Serialize)] struct BlogRootContext { @@ -45,6 +57,17 @@ struct BlogRootContext { description: &'static str, } +#[derive(Serialize)] +struct PageContext { + // The current base URL that the client is visiting + // This may be different from the blog's preferred URL + // (e.g. the workers.dev domain, which is differnent + // from where you actually deploy the worker) + base_url: String, + // The current query string + query: String +} + #[derive(Serialize)] struct HomePagePost { title: String, @@ -56,6 +79,7 @@ struct HomePagePost { #[derive(Serialize)] struct HomePageContext { blog: &'static BlogRootContext, + page: PageContext, posts: Vec, prev: Option, next: Option @@ -64,6 +88,7 @@ struct HomePageContext { #[derive(Serialize)] struct PostContext { blog: &'static BlogRootContext, + page: PageContext, title: String, url: String, timestamp: u64, @@ -88,6 +113,9 @@ handlebars_helper!(build_num: | | BuildTag{}.get_build_timestamp()); handlebars_helper!(format_date: |date: u64, format: str| { NaiveDateTime::from_timestamp(date as i64, 0).format(format).to_string() }); +// Convert "/?offset=X" to "/feed.xml?offset=X" +// for use in RSS feed (feed.xml) +handlebars_helper!(feed_pagination: |s: str| s.replace("/", "/feed.xml")); fn build_handlebars() -> Handlebars<'static> { let mut hbs = Handlebars::new(); @@ -96,6 +124,7 @@ fn build_handlebars() -> Handlebars<'static> { hbs.register_helper("cur_year", Box::new(cur_year)); hbs.register_helper("build_num", Box::new(build_num)); hbs.register_helper("format_date", Box::new(format_date)); + hbs.register_helper("feed_pagination", Box::new(feed_pagination)); // Templates for file in THEME_DIR.files() { @@ -106,15 +135,35 @@ fn build_handlebars() -> Handlebars<'static> { path, file.contents_utf8().unwrap()).unwrap(); } } + + // The common RSS template + hbs.register_template_string("rss.hbs", + include_str!("../common/rss.hbs")).unwrap(); + return hbs; } -pub async fn render_homepage(url: Url) -> MyResult { +fn build_page_context(url: &Url) -> PageContext { + PageContext { + base_url: url.origin(), + query: url.search() + } +} + +pub fn render_homepage(url: Url) -> impl std::future::Future> { + _render_homepage(url, "home.hbs") +} + +// Shared logic for both homepage rendering and RSS +// RSS is just the "homepage" (i.e. post list) rendered +// to XML RSS format, which is done by a common template +async fn _render_homepage(url: Url, tpl_name: &str) -> MyResult { let params = UrlSearchParams::new_with_str(&url.search()) .map_err(|_| Error::BadRequest("Failed to parse query string".into()))?; let hbs = build_handlebars(); let mut context = HomePageContext { blog: &ROOT_CONTEXT, + page: build_page_context(&url), posts: vec![], prev: None, next: None @@ -163,15 +212,16 @@ pub async fn render_homepage(url: Url) -> MyResult { summary: post_cache.summary }); } - hbs.render("home.hbs", &context) + hbs.render(tpl_name, &context) .map_err(|e| Error::BadRequest(format!("{:#?}", e))) } -pub async fn render_post(post: blog::Post) -> MyResult { +pub async fn render_post(url: Url, post: blog::Post) -> MyResult { let hbs = build_handlebars(); let post_cache = blog::PostContentCache::find_or_render(&post).await; let context = PostContext { blog: &ROOT_CONTEXT, + page: build_page_context(&url), title: post.title, url: post.url, timestamp: post.timestamp, diff --git a/theme/default/head.hbs b/theme/default/head.hbs index 3a5676a..18346b9 100644 --- a/theme/default/head.hbs +++ b/theme/default/head.hbs @@ -3,4 +3,5 @@ {{ #if title }}{{ title }} - {{ blog.title }}{{ else }}{{ blog.title }}{{ /if }} + \ No newline at end of file