feat: Implement search result view & enable search
Implements a very simple and currently kinda broken-looking search result view.
This commit is contained in:
parent
dae97fdaf5
commit
4132869277
6 changed files with 90 additions and 4 deletions
|
@ -176,6 +176,22 @@ pub fn reply_thread(state: State<AppState>,
|
||||||
.responder()
|
.responder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This handler executes a full-text search on the forum database and
|
||||||
|
/// displays the results to the user.
|
||||||
|
pub fn search_forum(state: State<AppState>,
|
||||||
|
query: Form<SearchPosts>) -> ConverseResponse {
|
||||||
|
let query_string = query.0.query.clone();
|
||||||
|
state.db.send(query.0)
|
||||||
|
.flatten()
|
||||||
|
.and_then(move |results| state.renderer.send(SearchResultPage {
|
||||||
|
results,
|
||||||
|
query: query_string,
|
||||||
|
}).from_err())
|
||||||
|
.flatten()
|
||||||
|
.map(|res| HttpResponse::Ok().content_type(HTML).body(res))
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
/// This handler initiates an OIDC login.
|
/// This handler initiates an OIDC login.
|
||||||
pub fn login(state: State<AppState>) -> ConverseResponse {
|
pub fn login(state: State<AppState>) -> ConverseResponse {
|
||||||
state.oidc.send(GetLoginUrl)
|
state.oidc.send(GetLoginUrl)
|
||||||
|
|
|
@ -153,6 +153,7 @@ fn main() {
|
||||||
.resource("/thread/submit", |r| r.method(Method::POST).with3(submit_thread))
|
.resource("/thread/submit", |r| r.method(Method::POST).with3(submit_thread))
|
||||||
.resource("/thread/reply", |r| r.method(Method::POST).with3(reply_thread))
|
.resource("/thread/reply", |r| r.method(Method::POST).with3(reply_thread))
|
||||||
.resource("/thread/{id}", |r| r.method(Method::GET).with2(forum_thread))
|
.resource("/thread/{id}", |r| r.method(Method::GET).with2(forum_thread))
|
||||||
|
.resource("/search", |r| r.method(Method::POST).with2(search_forum))
|
||||||
.resource("/oidc/login", |r| r.method(Method::GET).with(login))
|
.resource("/oidc/login", |r| r.method(Method::GET).with(login))
|
||||||
.resource("/oidc/callback", |r| r.method(Method::POST).with3(callback));
|
.resource("/oidc/callback", |r| r.method(Method::POST).with3(callback));
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ pub struct NewPost {
|
||||||
/// This struct models the response of a full-text search query. It
|
/// This struct models the response of a full-text search query. It
|
||||||
/// does not use a table/schema definition struct like the other
|
/// does not use a table/schema definition struct like the other
|
||||||
/// tables, as no table of this type actually exists.
|
/// tables, as no table of this type actually exists.
|
||||||
#[derive(QueryableByName, Debug)]
|
#[derive(QueryableByName, Debug, Serialize)]
|
||||||
pub struct SearchResult {
|
pub struct SearchResult {
|
||||||
#[sql_type = "Integer"]
|
#[sql_type = "Integer"]
|
||||||
pub post_id: i32,
|
pub post_id: i32,
|
||||||
|
|
|
@ -175,3 +175,24 @@ impl Handler<NewThreadPage> for Renderer {
|
||||||
Ok(self.tera.render("new-thread.html", &ctx)?)
|
Ok(self.tera.render("new-thread.html", &ctx)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message used to render search results
|
||||||
|
pub struct SearchResultPage {
|
||||||
|
pub query: String,
|
||||||
|
pub results: Vec<SearchResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for SearchResultPage {
|
||||||
|
type Result = Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<SearchResultPage> for Renderer {
|
||||||
|
type Result = Result<String>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: SearchResultPage, _: &mut Self::Context) -> Self::Result {
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
ctx.add("query", &msg.query);
|
||||||
|
ctx.add("results", &msg.results);
|
||||||
|
Ok(self.tera.render("search.html", &ctx)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<h2>Converse</h2>
|
<h2>Converse</h2>
|
||||||
</a>
|
</a>
|
||||||
<form class="form-inline">
|
<form class="form-inline" method="post" action="/search">
|
||||||
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" disabled>
|
<input class="form-control mr-sm-2" type="search" placeholder="Search" name="query" aria-label="Search">
|
||||||
<button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit" disabled>Search</button>
|
<button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit">Search</button>
|
||||||
<a class="btn btn-outline-secondary my-2" href="/thread/new">New thread</a>
|
<a class="btn btn-outline-secondary my-2" href="/thread/new">New thread</a>
|
||||||
</form>
|
</form>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
48
templates/search.html
Normal file
48
templates/search.html
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<!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</h2>
|
||||||
|
</a>
|
||||||
|
<form class="form-inline" method="post" action="/search">
|
||||||
|
<input class="form-control mr-sm-2" type="search" placeholder="Search" name="query" aria-label="Search">
|
||||||
|
<button class="btn btn-outline-success my-2 my-sm-0 mr-1" type="submit">Search</button>
|
||||||
|
<a class="btn btn-outline-secondary my-2" href="/thread/new">New thread</a>
|
||||||
|
<a class="btn btn-outline-secondary my-2" href="/">Back to index</a>
|
||||||
|
</form>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<h2>Search results for '{{ query }}':</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="list-group">
|
||||||
|
{% for result in results -%}
|
||||||
|
<a href="/thread/{{ result.thread_id }}#post-{{ result.post_id }}" class="list-group-item flex-column list-group-item-action align-items-start">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">In thread '{{ result.title }}':</h5>
|
||||||
|
<div>{{ result.headline }}</div>
|
||||||
|
<div>(Posted by <i>{{ result.author }})</i></div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue