about summary refs log tree commit diff
path: root/src/render.rs
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@gmail.com>2018-04-11T11·25+0200
committerVincent Ambo <tazjin@gmail.com>2018-04-11T11·25+0200
commit87237f5c28f177830808aeb4710f72d31f14c045 (patch)
tree0e1c0468daeaabd472f347304c19f0617a4591f2 /src/render.rs
parent405e6340f86b5806e865c570e3afc28a1416cf34 (diff)
feat(render): Implement Markdown thread rendering & Gravatar
Implements a new thread rendering pipeline which all posts and the
main thread body are first converted to a `RenderablePost` structure.

During the conversion to this structure, the post body is rendered as
Markdown and the author's email address is converted into the format
required by Gravatar.
Diffstat (limited to 'src/render.rs')
-rw-r--r--src/render.rs75
1 files changed, 67 insertions, 8 deletions
diff --git a/src/render.rs b/src/render.rs
index 8dfce219b215..fee897f281fb 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -4,11 +4,17 @@
 
 use actix::prelude::*;
 use actix_web::HttpResponse;
-use tera::{Context, Tera};
-use models::*;
 use errors::*;
+use md5;
+use models::*;
+use tera::{escape_html, Context, Tera};
+use chrono::prelude::{DateTime, Utc};
+use comrak::{ComrakOptions, markdown_to_html};
 
-pub struct Renderer(pub Tera);
+pub struct Renderer {
+    pub tera: Tera,
+    pub comrak: ComrakOptions,
+}
 
 impl Actor for Renderer {
     type Context = actix::Context<Self>;
@@ -29,7 +35,7 @@ impl Handler<IndexPage> for Renderer {
     fn handle(&mut self, msg: IndexPage, _: &mut Self::Context) -> Self::Result {
         let mut ctx = Context::new();
         ctx.add("threads", &msg.threads);
-        Ok(self.0.render("index.html", &ctx)?)
+        Ok(self.tera.render("index.html", &ctx)?)
     }
 }
 
@@ -43,14 +49,67 @@ impl Message for ThreadPage {
     type Result = Result<String>;
 }
 
+// "Renderable" structures with data transformations applied.
+#[derive(Debug, Serialize)]
+struct RenderablePost {
+    id: i32,
+    body: String,
+    posted: DateTime<Utc>,
+    author_name: String,
+    author_gravatar: String,
+}
+
+/// This structure represents the transformed thread data with
+/// Markdown rendering and other changes applied.
+#[derive(Debug, Serialize)]
+struct RenderableThreadPage {
+    id: i32,
+    title: String,
+    posts: Vec<RenderablePost>,
+}
+
+/// Helper function for computing Gravatar links.
+fn md5_hex(input: &[u8]) -> String {
+    format!("{:x}", md5::compute(input))
+}
+
+fn prepare_thread(comrak: &ComrakOptions, page: ThreadPage) -> RenderableThreadPage {
+    let mut posts = vec![RenderablePost {
+        // Always pin the ID of the first post.
+        id: 0,
+        body: markdown_to_html(&page.thread.body, comrak),
+        posted: page.thread.posted,
+        author_name: page.thread.author_name,
+        author_gravatar: md5_hex(page.thread.author_email.as_bytes()),
+    }];
+
+    for post in page.posts {
+        posts.push(RenderablePost {
+            id: post.id,
+            body: markdown_to_html(&post.body, comrak),
+            posted: post.posted,
+            author_name: post.author_name,
+            author_gravatar: md5_hex(post.author_email.as_bytes()),
+        });
+    }
+
+    RenderableThreadPage {
+        posts,
+        id: page.thread.id,
+        title: escape_html(&page.thread.title),
+    }
+}
+
 impl Handler<ThreadPage> for Renderer {
     type Result = Result<String>;
 
     fn handle(&mut self, msg: ThreadPage, _: &mut Self::Context) -> Self::Result {
+        let renderable = prepare_thread(&self.comrak, msg);
         let mut ctx = Context::new();
-        ctx.add("thread", &msg.thread);
-        ctx.add("posts", &msg.posts);
-        Ok(self.0.render("thread.html", &ctx)?)
+        ctx.add("title", &renderable.title);
+        ctx.add("posts", &renderable.posts);
+        ctx.add("id", &renderable.id);
+        Ok(self.tera.render("thread.html", &ctx)?)
     }
 }
 
@@ -65,6 +124,6 @@ impl Handler<NewThreadPage> for Renderer {
     type Result = Result<String>;
 
     fn handle(&mut self, _: NewThreadPage, _: &mut Self::Context) -> Self::Result {
-        Ok(self.0.render("new-thread.html", &Context::new())?)
+        Ok(self.tera.render("new-thread.html", &Context::new())?)
     }
 }