diff options
-rw-r--r-- | src/main.rs | 14 | ||||
-rw-r--r-- | src/render.rs | 75 | ||||
-rw-r--r-- | templates/thread.html | 31 |
3 files changed, 85 insertions, 35 deletions
diff --git a/src/main.rs b/src/main.rs index a3ad3643044d..eeab96e83ced 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,8 +87,18 @@ fn main() { info!("Compiling templates ..."); let template_path = concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"); - let tera = compile_templates!(template_path); - let renderer = render::Renderer(tera); + let mut tera = compile_templates!(template_path); + tera.autoescape_on(vec![]); + let comrak = comrak::ComrakOptions{ + github_pre_lang: true, + ext_strikethrough: true, + ext_table: true, + ext_autolink: true, + ext_tasklist: true, + ext_footnotes: true, + ..Default::default() + }; + let renderer = render::Renderer{ tera, comrak }; let renderer_addr: Addr<Syn, render::Renderer> = renderer.start(); info!("Initialising HTTP server ..."); 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())?) } } diff --git a/templates/thread.html b/templates/thread.html index ee5a3a4271fb..bbe288e92f13 100644 --- a/templates/thread.html +++ b/templates/thread.html @@ -5,7 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> - <title>Converse: {{ thread.title }}</title> + <title>Converse: {{ title }}</title> </head> <body> <header> @@ -26,30 +26,10 @@ <div class="list-group-item flex-column"> <div class="row"> <div class="col-12"> - <h3>{{ thread.title }}</h3> + <h3>{{ title }}</h3> </div> </div> </div> - <div class="list-group-item flex-column align-items-start"> - <div class="row"> - <div class="col-2 border-right"> - <div class="row"> - <div class="col-12"> - <img src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50" /> - </div> - </div> - <div class="row"> - <div class="col-12"> - <strong>{{ thread.author_name }}</strong> - </div> - </div> - </div> - <div class="col-10"> - {{ thread.body }} - </div> - <small class="text-muted"> {{ thread.posted }} </small> - </div> - </div> {% for post in posts -%} <div class="list-group-item flex-column align-items-start"> @@ -57,7 +37,7 @@ <div class="col-2 border-right"> <div class="row"> <div class="col-12"> - <img src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50" /> + <img src="https://www.gravatar.com/avatar/{{ post.author_gravatar }}?d=monsterid" /> </div> </div> <div class="row"> @@ -69,15 +49,16 @@ <div class="col-10"> {{ post.body }} </div> - <small class="text-muted"> {{ post.posted }} </small> + <small class="text-muted">{{ post.posted }}</small> </div> </div> {%- endfor %} + <div class="list-group-item flex-column align-items-start"> <div class="row"> <div class="col-12"> <form action="/thread/reply" method="post"> - <input type="hidden" id="thread_id" name="thread_id" value="{{ thread.id }}"> + <input type="hidden" id="thread_id" name="thread_id" value="{{ id }}"> <label for="body">You can use <strong>Markdown</strong>!</label> <div class="input-group"> <textarea class="form-control" id="body" name="body" aria-label="thread response"></textarea> |