implement RSS feed
as an alternative common template for home page
This commit is contained in:
parent
84ff757700
commit
1f2fee8341
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>{{ blog.title }}</title>
|
||||||
|
<description>{{ blog.description }}</description>
|
||||||
|
<link>{{ page.base_url }}</link>
|
||||||
|
|
||||||
|
<atom:link href="{{ page.base_url }}/feed.xml{{ #if page.query }}{{ page.query }}{{ /if }}" rel="self" type="application/rss+xml" />
|
||||||
|
{{ #if next }}
|
||||||
|
<atom:link href="{{ page.base_url }}{{ feed_pagination next }}" rel="next" type="application/rss+xml" />
|
||||||
|
{{ /if }}
|
||||||
|
{{ #if prev }}
|
||||||
|
<atom:link href="{{ page.base_url }}{{ feed_pagination prev }}" rel="previous" type="application/rss+xml" />
|
||||||
|
{{ /if }}
|
||||||
|
|
||||||
|
{{ #each posts }}
|
||||||
|
<item>
|
||||||
|
<title>{{ this.title }}</title>
|
||||||
|
<description><![CDATA[{{{ this.summary }}}]]></description>
|
||||||
|
<pubDate>{{ format_date this.timestamp "%a, %d %b %Y %T GMT" }}</pubDate>
|
||||||
|
<link>{{ ../page.base_url }}/{{ this.url }}</link>
|
||||||
|
<guid isPermaLink="true">{{ ../page.base_url }}/{{ this.url }}</guid>
|
||||||
|
</item>
|
||||||
|
{{ /each }}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
|
@ -148,7 +148,7 @@ async fn default_route(_req: Request, url: Url) -> MyResult<Response> {
|
||||||
} else {
|
} else {
|
||||||
// TODO: Actually render the page...
|
// TODO: Actually render the page...
|
||||||
return Response::new_with_opt_str_and_init(
|
return Response::new_with_opt_str_and_init(
|
||||||
Some(&render::render_post(post).await?),
|
Some(&render::render_post(url, post).await?),
|
||||||
ResponseInit::new()
|
ResponseInit::new()
|
||||||
.status(200)
|
.status(200)
|
||||||
.headers(headers!{
|
.headers(headers!{
|
||||||
|
|
|
@ -17,6 +17,7 @@ include!(concat!(env!("OUT_DIR"), "/load_theme.rs"));
|
||||||
|
|
||||||
pub fn build_routes(router: &mut Router) {
|
pub fn build_routes(router: &mut Router) {
|
||||||
router.add_route("/static/", &serve_static);
|
router.add_route("/static/", &serve_static);
|
||||||
|
router.add_route("/feed.xml", &serve_rss);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_static(_req: Request, url: Url) -> MyResult<Response> {
|
async fn serve_static(_req: Request, url: Url) -> MyResult<Response> {
|
||||||
|
@ -37,6 +38,17 @@ async fn serve_static(_req: Request, url: Url) -> MyResult<Response> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn serve_rss(_req: Request, url: Url) -> MyResult<Response> {
|
||||||
|
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
|
// Context objects used when rendering pages
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct BlogRootContext {
|
struct BlogRootContext {
|
||||||
|
@ -45,6 +57,17 @@ struct BlogRootContext {
|
||||||
description: &'static str,
|
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)]
|
#[derive(Serialize)]
|
||||||
struct HomePagePost {
|
struct HomePagePost {
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -56,6 +79,7 @@ struct HomePagePost {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct HomePageContext {
|
struct HomePageContext {
|
||||||
blog: &'static BlogRootContext,
|
blog: &'static BlogRootContext,
|
||||||
|
page: PageContext,
|
||||||
posts: Vec<HomePagePost>,
|
posts: Vec<HomePagePost>,
|
||||||
prev: Option<String>,
|
prev: Option<String>,
|
||||||
next: Option<String>
|
next: Option<String>
|
||||||
|
@ -64,6 +88,7 @@ struct HomePageContext {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct PostContext {
|
struct PostContext {
|
||||||
blog: &'static BlogRootContext,
|
blog: &'static BlogRootContext,
|
||||||
|
page: PageContext,
|
||||||
title: String,
|
title: String,
|
||||||
url: String,
|
url: String,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
|
@ -88,6 +113,9 @@ handlebars_helper!(build_num: | | BuildTag{}.get_build_timestamp());
|
||||||
handlebars_helper!(format_date: |date: u64, format: str| {
|
handlebars_helper!(format_date: |date: u64, format: str| {
|
||||||
NaiveDateTime::from_timestamp(date as i64, 0).format(format).to_string()
|
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> {
|
fn build_handlebars() -> Handlebars<'static> {
|
||||||
let mut hbs = Handlebars::new();
|
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("cur_year", Box::new(cur_year));
|
||||||
hbs.register_helper("build_num", Box::new(build_num));
|
hbs.register_helper("build_num", Box::new(build_num));
|
||||||
hbs.register_helper("format_date", Box::new(format_date));
|
hbs.register_helper("format_date", Box::new(format_date));
|
||||||
|
hbs.register_helper("feed_pagination", Box::new(feed_pagination));
|
||||||
|
|
||||||
// Templates
|
// Templates
|
||||||
for file in THEME_DIR.files() {
|
for file in THEME_DIR.files() {
|
||||||
|
@ -106,15 +135,35 @@ fn build_handlebars() -> Handlebars<'static> {
|
||||||
path, file.contents_utf8().unwrap()).unwrap();
|
path, file.contents_utf8().unwrap()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The common RSS template
|
||||||
|
hbs.register_template_string("rss.hbs",
|
||||||
|
include_str!("../common/rss.hbs")).unwrap();
|
||||||
|
|
||||||
return hbs;
|
return hbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_homepage(url: Url) -> MyResult<String> {
|
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<Output = MyResult<String>> {
|
||||||
|
_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<String> {
|
||||||
let params = UrlSearchParams::new_with_str(&url.search())
|
let params = UrlSearchParams::new_with_str(&url.search())
|
||||||
.map_err(|_| Error::BadRequest("Failed to parse query string".into()))?;
|
.map_err(|_| Error::BadRequest("Failed to parse query string".into()))?;
|
||||||
let hbs = build_handlebars();
|
let hbs = build_handlebars();
|
||||||
let mut context = HomePageContext {
|
let mut context = HomePageContext {
|
||||||
blog: &ROOT_CONTEXT,
|
blog: &ROOT_CONTEXT,
|
||||||
|
page: build_page_context(&url),
|
||||||
posts: vec![],
|
posts: vec![],
|
||||||
prev: None,
|
prev: None,
|
||||||
next: None
|
next: None
|
||||||
|
@ -163,15 +212,16 @@ pub async fn render_homepage(url: Url) -> MyResult<String> {
|
||||||
summary: post_cache.summary
|
summary: post_cache.summary
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
hbs.render("home.hbs", &context)
|
hbs.render(tpl_name, &context)
|
||||||
.map_err(|e| Error::BadRequest(format!("{:#?}", e)))
|
.map_err(|e| Error::BadRequest(format!("{:#?}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_post(post: blog::Post) -> MyResult<String> {
|
pub async fn render_post(url: Url, post: blog::Post) -> MyResult<String> {
|
||||||
let hbs = build_handlebars();
|
let hbs = build_handlebars();
|
||||||
let post_cache = blog::PostContentCache::find_or_render(&post).await;
|
let post_cache = blog::PostContentCache::find_or_render(&post).await;
|
||||||
let context = PostContext {
|
let context = PostContext {
|
||||||
blog: &ROOT_CONTEXT,
|
blog: &ROOT_CONTEXT,
|
||||||
|
page: build_page_context(&url),
|
||||||
title: post.title,
|
title: post.title,
|
||||||
url: post.url,
|
url: post.url,
|
||||||
timestamp: post.timestamp,
|
timestamp: post.timestamp,
|
||||||
|
|
|
@ -3,4 +3,5 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ #if title }}{{ title }} - {{ blog.title }}{{ else }}{{ blog.title }}{{ /if }}</title>
|
<title>{{ #if title }}{{ title }} - {{ blog.title }}{{ else }}{{ blog.title }}{{ /if }}</title>
|
||||||
<link rel="stylesheet" href="/static/style.css?ver={{ build_num }}" />
|
<link rel="stylesheet" href="/static/style.css?ver={{ build_num }}" />
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="RSS Feed for {{ blog.title }}" href="/feed.xml" />
|
||||||
</head>
|
</head>
|
Loading…
Reference in New Issue