refactor(templates/render): Add generic post editing template
Adds a generic template that can be used for submitting, responding to and editing posts.
This commit is contained in:
parent
4c0e6552e8
commit
ec712cc4c0
6 changed files with 156 additions and 72 deletions
|
@ -80,7 +80,7 @@ impl Handler<GetThread> for DbExecutor {
|
||||||
/// Message used to create a new thread
|
/// Message used to create a new thread
|
||||||
pub struct CreateThread {
|
pub struct CreateThread {
|
||||||
pub new_thread: NewThread,
|
pub new_thread: NewThread,
|
||||||
pub body: String,
|
pub post: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for CreateThread {
|
impl Message for CreateThread {
|
||||||
|
@ -105,7 +105,7 @@ impl Handler<CreateThread> for DbExecutor {
|
||||||
// ... then create the first post in the thread.
|
// ... then create the first post in the thread.
|
||||||
let new_post = NewPost {
|
let new_post = NewPost {
|
||||||
thread_id: thread.id,
|
thread_id: thread.id,
|
||||||
body: msg.body,
|
body: msg.post,
|
||||||
author_name: msg.new_thread.author_name.clone(),
|
author_name: msg.new_thread.author_name.clone(),
|
||||||
author_email: msg.new_thread.author_email.clone(),
|
author_email: msg.new_thread.author_email.clone(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,7 +99,7 @@ fn anonymous() -> Author {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct NewThreadForm {
|
pub struct NewThreadForm {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub body: String,
|
pub post: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const NEW_THREAD_LENGTH_ERR: &'static str = "Title and body can not be empty!";
|
const NEW_THREAD_LENGTH_ERR: &'static str = "Title and body can not be empty!";
|
||||||
|
@ -112,16 +112,16 @@ pub fn submit_thread(state: State<AppState>,
|
||||||
// Trim whitespace out of inputs:
|
// Trim whitespace out of inputs:
|
||||||
let input = NewThreadForm {
|
let input = NewThreadForm {
|
||||||
title: input.title.trim().into(),
|
title: input.title.trim().into(),
|
||||||
body: input.body.trim().into(),
|
post: input.post.trim().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Perform simple validation and abort here if it fails:
|
// Perform simple validation and abort here if it fails:
|
||||||
if input.title.is_empty() || input.body.is_empty() {
|
if input.title.is_empty() || input.post.is_empty() {
|
||||||
return state.renderer
|
return state.renderer
|
||||||
.send(NewThreadPage {
|
.send(NewThreadPage {
|
||||||
alerts: vec![NEW_THREAD_LENGTH_ERR],
|
alerts: vec![NEW_THREAD_LENGTH_ERR],
|
||||||
title: Some(input.title),
|
title: Some(input.title),
|
||||||
body: Some(input.body),
|
post: Some(input.post),
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|res| HttpResponse::Ok().content_type(HTML).body(res))
|
.map(|res| HttpResponse::Ok().content_type(HTML).body(res))
|
||||||
|
@ -140,7 +140,7 @@ pub fn submit_thread(state: State<AppState>,
|
||||||
|
|
||||||
let msg = CreateThread {
|
let msg = CreateThread {
|
||||||
new_thread,
|
new_thread,
|
||||||
body: input.body,
|
post: input.post,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.db.send(msg)
|
state.db.send(msg)
|
||||||
|
@ -158,7 +158,7 @@ pub fn submit_thread(state: State<AppState>,
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct NewPostForm {
|
pub struct NewPostForm {
|
||||||
pub thread_id: i32,
|
pub thread_id: i32,
|
||||||
pub body: String,
|
pub post: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This handler receives a "Reply"-form and redirects the user to the
|
/// This handler receives a "Reply"-form and redirects the user to the
|
||||||
|
@ -172,7 +172,7 @@ pub fn reply_thread(state: State<AppState>,
|
||||||
|
|
||||||
let new_post = NewPost {
|
let new_post = NewPost {
|
||||||
thread_id: input.thread_id,
|
thread_id: input.thread_id,
|
||||||
body: input.body.trim().into(),
|
body: input.post.trim().into(),
|
||||||
author_name: author.name,
|
author_name: author.name,
|
||||||
author_email: author.email,
|
author_email: author.email,
|
||||||
};
|
};
|
||||||
|
|
|
@ -159,6 +159,41 @@ impl Handler<ThreadPage> for Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The different types of editing modes supported by the editing
|
||||||
|
/// template:
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub enum EditingMode {
|
||||||
|
NewThread,
|
||||||
|
PostReply,
|
||||||
|
EditPost,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EditingMode {
|
||||||
|
fn default() -> EditingMode { EditingMode::NewThread }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct represents the context submitted to the template used
|
||||||
|
/// for rendering the new thread, edit post and reply to thread forms.
|
||||||
|
#[derive(Default, Serialize)]
|
||||||
|
pub struct FormContext {
|
||||||
|
/// Which editing mode is to be used by the template?
|
||||||
|
pub mode: EditingMode,
|
||||||
|
|
||||||
|
/// Potential alerts to display to the user (e.g. input validation
|
||||||
|
/// results)
|
||||||
|
pub alerts: Vec<&'static str>,
|
||||||
|
|
||||||
|
/// Either the title to be used in the subject field or the title
|
||||||
|
/// of the thread the user is responding to.
|
||||||
|
pub title: Option<String>,
|
||||||
|
|
||||||
|
/// Body of the post being edited, if present.
|
||||||
|
pub post: Option<String>,
|
||||||
|
|
||||||
|
/// ID of the thread being replied to or the post being edited.
|
||||||
|
pub id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Message used to render new thread page.
|
/// Message used to render new thread page.
|
||||||
///
|
///
|
||||||
/// It can optionally contain a vector of warnings to display to the
|
/// It can optionally contain a vector of warnings to display to the
|
||||||
|
@ -167,7 +202,7 @@ impl Handler<ThreadPage> for Renderer {
|
||||||
pub struct NewThreadPage {
|
pub struct NewThreadPage {
|
||||||
pub alerts: Vec<&'static str>,
|
pub alerts: Vec<&'static str>,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub body: Option<String>,
|
pub post: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for NewThreadPage {
|
impl Message for NewThreadPage {
|
||||||
|
@ -178,11 +213,13 @@ impl Handler<NewThreadPage> for Renderer {
|
||||||
type Result = Result<String>;
|
type Result = Result<String>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: NewThreadPage, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: NewThreadPage, _: &mut Self::Context) -> Self::Result {
|
||||||
let mut ctx = Context::new();
|
let ctx: FormContext = FormContext {
|
||||||
ctx.add("alerts", &msg.alerts);
|
alerts: msg.alerts,
|
||||||
ctx.add("title", &msg.title.map(|s| escape_html(&s)));
|
title: msg.title,
|
||||||
ctx.add("body", &msg.body.map(|s| escape_html(&s)));
|
post: msg.post,
|
||||||
Ok(self.tera.render("new-thread.html", &ctx)?)
|
..Default::default()
|
||||||
|
};
|
||||||
|
Ok(self.tera.render("post.html", &ctx)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
|
||||||
<!-- 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 Index</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<nav class="navbar navbar-light bg-light justify-content-between mb-3">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<h2>Converse: New Thread</h2>
|
|
||||||
</a>
|
|
||||||
<form class="form-inline">
|
|
||||||
<a class="btn btn-outline-secondary my-2" href="/">Back to index</a>
|
|
||||||
</form>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="container border rounded">
|
|
||||||
<div class="col-8">
|
|
||||||
{% for alert in alerts %}
|
|
||||||
<div class="alert alert-warning m-3"><strong>{{ alert }}</strong></div>
|
|
||||||
{% endfor %}
|
|
||||||
<p class="mt-3">Make <i>your own thread</i> on these here forums!</p>
|
|
||||||
<p>Remember that you can use <strong>Markdown</strong> when
|
|
||||||
writing your posts.</p>
|
|
||||||
<form action="/thread/submit" method="post">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-8 input-group m-3">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text" id="title-text">Title:</span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" id="title" name="title" aria-describedby="title-text" {% if title %}value="{{ title }}"{% endif %}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-8 input-group m-3">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text" id="body-text">Body:</span>
|
|
||||||
</div>
|
|
||||||
<textarea class="form-control" id="body" name="body" aria-label="thread body">{%if body %}{{ body }}{% endif %}</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-2 m-3">
|
|
||||||
<button class="btn btn-outline-primary" type="submit">Post!</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
103
templates/post.html
Normal file
103
templates/post.html
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<!-- {#
|
||||||
|
This template is shared by the new thread, reply and post-editing pages.
|
||||||
|
|
||||||
|
The main display differences between the different editing styles are the
|
||||||
|
headline of the page ("Submit new thread", "Reply to thread", "Edit post")
|
||||||
|
and whether or not the subject line field is displayed in the input form.
|
||||||
|
|
||||||
|
Every one of these pages can have a variable length list of alerts submitted
|
||||||
|
into the template, which will be rendered as Boostrap alert boxes above the
|
||||||
|
user input form.
|
||||||
|
#} -->
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
|
||||||
|
<!-- 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 Index</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-light bg-light justify-content-between mb-3">
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
{% if mode == "NewThread" %}
|
||||||
|
<h2>Converse: Submit new thread</h2>
|
||||||
|
{% elif mode == "PostReply" %}
|
||||||
|
<h2>Converse: Reply to thread</h2>
|
||||||
|
{% elif mode == "EditPost" %}
|
||||||
|
<h2>Converse: Edit post</h2>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
<form class="form-inline">
|
||||||
|
<a class="btn btn-outline-secondary my-2" href="/">Back to index</a>
|
||||||
|
</form>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="container border rounded">
|
||||||
|
<div class="d-flex flex-column mt-3 border-bottom">
|
||||||
|
{%- for alert in alerts %}
|
||||||
|
<div class="alert alert-warning m-3"><strong>{{ alert }}</strong></div>
|
||||||
|
{% endfor -%}
|
||||||
|
|
||||||
|
{%- if mode == "NewThread" %}
|
||||||
|
<h5>Create a new thread</h5>
|
||||||
|
{% elif mode == "PostReply" %}
|
||||||
|
<h5>Respond to thread '{{ title }}'</h5>
|
||||||
|
{% elif mode == "EditPost" %}
|
||||||
|
<h5>Edit your post</h5>
|
||||||
|
{% endif -%}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column mt-3">
|
||||||
|
{% if mode == "NewThread" %}
|
||||||
|
<form action="/thread/submit" method="post">
|
||||||
|
{% elif mode == "PostReply" %}
|
||||||
|
<form action="/thread/reply" method="post">
|
||||||
|
{% elif mode == "EditPost" %}
|
||||||
|
<form action="/post/edit" method="post">
|
||||||
|
{% endif %}
|
||||||
|
{% if mode == "PostReply" %}
|
||||||
|
<input type="hidden" id="thread_id" name="thread_id" value="{{ id }}">
|
||||||
|
{% elif mode == "EditPost" %}
|
||||||
|
<input type="hidden" id="thread_id" name="post_id" value="{{ id }}">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if mode == "NewThread" %}
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text" id="title-text">Title:</span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" id="title" name="title" aria-label="thread title" {% if title %}value="{{ title }}"{% endif %}>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="d-flex flex-row">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text" id="post-text">Post:</span>
|
||||||
|
</div>
|
||||||
|
<textarea class="form-control" id="post" name="post" rows="15" aria-label="post content">{% if body %}{{ body }}{% endif %}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column flex-wrap-reverse border rounded">
|
||||||
|
<p class="m-2 pb-2 border-bottom border-dark">
|
||||||
|
Remember that you can use <a href="https://daringfireball.net/projects/markdown/basics"><strong>Markdown</strong></a> when
|
||||||
|
writing your posts:
|
||||||
|
</p>
|
||||||
|
<p class="ml-4 m-2"><i>*italic text*</i></p>
|
||||||
|
<p class="ml-4 m-2"><strong>**bold text**</strong></p>
|
||||||
|
<p class="ml-4 m-2"><s>~strikethrough text~</s></p>
|
||||||
|
<p class="ml-4 m-2"><code>[link text](https://some.link.com/)</code></p>
|
||||||
|
<p class="ml-4 m-2"><code>![image text](https://foo.com/thing.jpg)</code></p>
|
||||||
|
<p class="ml-4 m-2">Use <code>*</code> or <code>-</code> to enumerate lists.</p>
|
||||||
|
<p class="ml-4 m-2">See Markdown documentation for more information!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3 mb-3" type="submit">Post!</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -55,7 +55,7 @@
|
||||||
<input type="hidden" id="thread_id" name="thread_id" value="{{ id }}">
|
<input type="hidden" id="thread_id" name="thread_id" value="{{ id }}">
|
||||||
<label for="body">You can use <strong>Markdown</strong>!</label>
|
<label for="body">You can use <strong>Markdown</strong>!</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<textarea class="form-control" id="body" name="body" aria-label="thread response" rows="10"></textarea>
|
<textarea class="form-control" id="post" name="post" aria-label="thread response" rows="10"></textarea>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-primary" type="submit">Post!</button>
|
<button class="btn btn-primary" type="submit">Post!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue