split ui into smaller templates and separate css
This commit is contained in:
@ -1,7 +1,9 @@
|
|||||||
use async_std::channel::Sender;
|
use async_std::channel::Sender;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
|
use markdown;
|
||||||
use rocket::{form::Form, get, post, response::Redirect, uri, FromForm, State};
|
use rocket::{form::Form, get, post, response::Redirect, uri, FromForm, State};
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
use uri_encode;
|
||||||
|
|
||||||
use crate::{db::Database, sbot, task_loop::Task, utils, WhoAmI};
|
use crate::{db::Database, sbot, task_loop::Task, utils, WhoAmI};
|
||||||
|
|
||||||
@ -17,7 +19,7 @@ pub async fn home(db: &State<Database>) -> Template {
|
|||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("peers", &peers);
|
context.insert("peers", &peers);
|
||||||
|
|
||||||
Template::render("home", &context.into_json())
|
Template::render("base", &context.into_json())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/posts/<public_key>")]
|
#[get("/posts/<public_key>")]
|
||||||
@ -30,7 +32,7 @@ pub async fn posts(db: &State<Database>, public_key: &str) -> Template {
|
|||||||
context.insert("peers", &peers);
|
context.insert("peers", &peers);
|
||||||
context.insert("posts", &posts);
|
context.insert("posts", &posts);
|
||||||
|
|
||||||
Template::render("home", &context.into_json())
|
Template::render("base", &context.into_json())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/posts/<public_key>/<msg_id>")]
|
#[get("/posts/<public_key>/<msg_id>")]
|
||||||
@ -41,12 +43,54 @@ pub async fn post(db: &State<Database>, public_key: &str, msg_id: &str) -> Templ
|
|||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("selected_peer", &public_key);
|
context.insert("selected_peer", &public_key);
|
||||||
|
context.insert(
|
||||||
|
"selected_peer_encoded",
|
||||||
|
&uri_encode::encode_uri_component(public_key),
|
||||||
|
);
|
||||||
context.insert("selected_post", &msg_id);
|
context.insert("selected_post", &msg_id);
|
||||||
|
context.insert(
|
||||||
|
"selected_post_encoded",
|
||||||
|
&uri_encode::encode_uri_component(msg_id),
|
||||||
|
);
|
||||||
context.insert("peers", &peers);
|
context.insert("peers", &peers);
|
||||||
context.insert("posts", &posts);
|
context.insert("posts", &posts);
|
||||||
|
// TODO: consider converting markdown to html here
|
||||||
context.insert("post", &post);
|
context.insert("post", &post);
|
||||||
|
context.insert("post_is_selected", &true);
|
||||||
|
|
||||||
Template::render("home", &context.into_json())
|
Template::render("base", &context.into_json())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/posts/<public_key>/<msg_id>/read")]
|
||||||
|
pub async fn mark_post_read(db: &State<Database>, public_key: &str, msg_id: &str) -> Redirect {
|
||||||
|
// Retrieve the post from the database, mark it as read and reinsert it.
|
||||||
|
if let Ok(Some(mut post)) = db.get_post(public_key, msg_id) {
|
||||||
|
post.read = true;
|
||||||
|
db.add_post(public_key, post).unwrap();
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"failed to find post {} authored by {} in database",
|
||||||
|
msg_id, public_key
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to(uri!(post(public_key, msg_id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/posts/<public_key>/<msg_id>/unread")]
|
||||||
|
pub async fn mark_post_unread(db: &State<Database>, public_key: &str, msg_id: &str) -> Redirect {
|
||||||
|
// Retrieve the post from the database, mark it as unread and reinsert it.
|
||||||
|
if let Ok(Some(mut post)) = db.get_post(public_key, msg_id) {
|
||||||
|
post.read = false;
|
||||||
|
db.add_post(public_key, post).unwrap();
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"failed to find post {} authored by {} in database",
|
||||||
|
msg_id, public_key
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to(uri!(post(public_key, msg_id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/subscribe", data = "<peer>")]
|
#[post("/subscribe", data = "<peer>")]
|
||||||
@ -77,6 +121,8 @@ pub async fn subscribe_form(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
let peer = peer.public_key.to_string();
|
let peer = peer.public_key.to_string();
|
||||||
|
// Fetch all root posts authored by the peer we're subscribing
|
||||||
|
// to. Posts will be added to the key-value database.
|
||||||
if let Err(e) = tx.send(Task::FetchAll(peer)).await {
|
if let Err(e) = tx.send(Task::FetchAll(peer)).await {
|
||||||
warn!("task loop error: {}", e)
|
warn!("task loop error: {}", e)
|
||||||
}
|
}
|
||||||
|
85
static/css/lykin.css
Normal file
85
static/css/lykin.css
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
.nav {
|
||||||
|
background-color: lightgreen;
|
||||||
|
border: 5px solid #19A974;
|
||||||
|
border-radius: 15px;
|
||||||
|
grid-area: nav;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peers {
|
||||||
|
background-color: lightblue;
|
||||||
|
border: 5px solid #357EDD;
|
||||||
|
border-radius: 15px;
|
||||||
|
grid-area: peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts {
|
||||||
|
background-color: bisque;
|
||||||
|
border: 5px solid #FF6300;
|
||||||
|
border-radius: 15px;
|
||||||
|
grid-area: posts;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post > ul {
|
||||||
|
padding-left: 25px;
|
||||||
|
padding-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background-color: lightyellow;
|
||||||
|
border: 5px solid #FFD700;
|
||||||
|
border-radius: 15px;
|
||||||
|
grid-area: content;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: scroll;
|
||||||
|
word-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: 1fr 3fr 4fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'nav nav nav nav nav'
|
||||||
|
'peers posts posts posts posts'
|
||||||
|
'peers content content content content';
|
||||||
|
grid-gap: 10px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-top: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 85vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-container > div {
|
||||||
|
/* background-color: rgba(255, 255, 255, 0.8); */
|
||||||
|
/* text-align: center; */
|
||||||
|
/* padding: 20px 0; */
|
||||||
|
/* font-size: 30px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container > input {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { text-decoration: none; color: black; }
|
20
templates/base.html.tera
Normal file
20
templates/base.html.tera
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>lykin</title>
|
||||||
|
<meta name="description" content="lykin: an SSB tutorial application">
|
||||||
|
<meta name="author" content="glyph">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/css/lykin.css">
|
||||||
|
</head>
|
||||||
|
<body class="container">
|
||||||
|
<a href="/"><h1 style="margin-left: 15px;">lykin</h1></a>
|
||||||
|
<div class="grid-container">
|
||||||
|
{% include "topbar" %}
|
||||||
|
{% include "peer_list" %}
|
||||||
|
{% include "post_list" %}
|
||||||
|
{% include "post_content" %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,133 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>lykin</title>
|
|
||||||
<meta name="description" content="lykin: an SSB tutorial application">
|
|
||||||
<meta name="author" content="glyph">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.nav {
|
|
||||||
grid-area: nav;
|
|
||||||
border: 5px solid #19A974;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.peers {
|
|
||||||
grid-area: peers;
|
|
||||||
border: 5px solid #357EDD;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.posts {
|
|
||||||
grid-area: posts;
|
|
||||||
border: 5px solid #FF6300;
|
|
||||||
border-radius: 15px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
border: 5px solid #FFD700;
|
|
||||||
border-radius: 15px;
|
|
||||||
grid-area: content;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
grid-template-rows: 1fr 200px 4fr;
|
|
||||||
grid-template-areas:
|
|
||||||
'nav nav nav nav nav'
|
|
||||||
'peers posts posts posts posts'
|
|
||||||
'peers content content content content';
|
|
||||||
grid-gap: 10px;
|
|
||||||
padding-left: 15px;
|
|
||||||
padding-right: 15px;
|
|
||||||
padding-top: 5px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container > div {
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
/* text-align: center; */
|
|
||||||
/* padding: 20px 0; */
|
|
||||||
/* font-size: 30px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container > input {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a { text-decoration: none; color: black; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="container">
|
|
||||||
<a href="/"><h1 style="margin-left: 15px;">lykin</h1></a>
|
|
||||||
<div class="grid-container">
|
|
||||||
<div class="nav">
|
|
||||||
<div class="flex-container">
|
|
||||||
<a href="/"><img src="/icons/download.png" style="width: 55px;"></a>
|
|
||||||
<a href="/" style="margin-left: 20px;"><img src="/icons/read_post.png" style="width: 55px;"></a>
|
|
||||||
<a href="/" style="margin-left: 20px;"><img src="/icons/unread_post.png" style="width: 55px;"></a>
|
|
||||||
<a href="/" style="margin-left: 20px;"><img src="/icons/delete_post.png" style="width: 55px;"></a>
|
|
||||||
<form class="flex-container" style="margin-left: auto; margin-right: 10px;" action="/subscribe" method="post">
|
|
||||||
<label for="public_key">Public Key</label>
|
|
||||||
<input type="text" id="public_key" name="public_key" size=50 maxlength=53>
|
|
||||||
<input type="submit" value="Subscribe">
|
|
||||||
<input type="submit" value="Unsubscribe" formaction="/unsubscribe">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="peers" style="text-align: center;">
|
|
||||||
<ul style="padding-left: 0;">
|
|
||||||
{% for peer in peers -%}
|
|
||||||
<li style="list-style: none; font-size: 12px;">
|
|
||||||
<a href="/posts/{{ peer | replace(from="/", to="%2F") }}">
|
|
||||||
<code style="word-wrap: anywhere;{% if selected_peer and peer == selected_peer %} font-weight: bold;{% endif %}">{{ peer }}</code>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="posts">
|
|
||||||
{% if posts %}
|
|
||||||
<ul style="padding-left: 25px;">
|
|
||||||
{% for post in posts -%}
|
|
||||||
<li class="flex" style="list-style: none; font-size: 12px;">
|
|
||||||
<a href="/posts/{{ selected_peer | replace(from="/", to="%2F") }}/{{ post.key | replace(from="/", to="%2F") }}">
|
|
||||||
<code style="word-wrap: anywhere;{% if selected_post and post.key == selected_post %} font-weight: bold;{% endif %}">{{ post.key }}</code>
|
|
||||||
| {{ post.date }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{% if post %}
|
|
||||||
{{ post.text }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,32 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>lykin</title>
|
|
||||||
<meta name="description" content="lykin: an SSB tutorial application">
|
|
||||||
<meta name="author" content="glyph">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>lykin</h1>
|
|
||||||
<form action="/subscribe" method="post" style="width: 500px;">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Subscription Management</legend>
|
|
||||||
<input type="text" id="public_key" name="public_key" size=53 maxlength=53><br>
|
|
||||||
<label for="public_key">Public Key</label><br>
|
|
||||||
<br>
|
|
||||||
<input type="submit" value="Subscribe">
|
|
||||||
<input type="submit" value="Unsubscribe" formaction="/unsubscribe">
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<h2>Subscriptions</h2>
|
|
||||||
<ul>
|
|
||||||
{% for feed in feeds -%}
|
|
||||||
<li>{{ feed }}</li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
11
templates/peer_list.html.tera
Normal file
11
templates/peer_list.html.tera
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<div class="peers" style="text-align: center;">
|
||||||
|
<ul style="padding-left: 0;">
|
||||||
|
{% for peer in peers -%}
|
||||||
|
<li style="list-style: none; font-size: 12px;">
|
||||||
|
<a href="/posts/{{ peer | replace(from="/", to="%2F") }}">
|
||||||
|
<code style="word-wrap: anywhere;{% if selected_peer and peer == selected_peer %} font-weight: bold;{% endif %}">{{ peer }}</code>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
5
templates/post_content.html.tera
Normal file
5
templates/post_content.html.tera
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div class="content">
|
||||||
|
{% if post %}
|
||||||
|
{{ post.text | trim_start_matches(pat='"') | trim_end_matches(pat='"') | trim }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
14
templates/post_list.html.tera
Normal file
14
templates/post_list.html.tera
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<div class="posts">
|
||||||
|
{% if posts %}
|
||||||
|
<ul style="padding-left: 25px; padding-right: 25px;">
|
||||||
|
{% for post in posts -%}
|
||||||
|
<li style="list-style: none; font-size: 12px; margin-bottom: 5px;{% if selected_post and post.key == selected_post %} background-color: #FF6300;{% endif %}">
|
||||||
|
<a class="flex-container" style="justify-content: space-between;{% if not post.read %} font-weight: bold;{% endif %}" href="/posts/{{ selected_peer | urlencode_strict }}/{{ post.key | urlencode_strict }}">
|
||||||
|
<code style="word-wrap: anywhere;">{{ post.key }}</code>
|
||||||
|
<p style="margin: 0;">{{ post.date }}</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
21
templates/topbar.html.tera
Normal file
21
templates/topbar.html.tera
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div class="nav">
|
||||||
|
<div class="flex-container">
|
||||||
|
<a href="/" title="Download latest posts"><img src="/icons/download.png" style="width: 55px;"></a>
|
||||||
|
{% if post_is_selected %}
|
||||||
|
{% if post.read %}
|
||||||
|
{% set mark_unread_url = "/posts/" ~ selected_peer_encoded ~ "/" ~ selected_post_encoded ~ "/unread" %}
|
||||||
|
<a href={{ mark_unread_url }} style="margin-left: 20px;" title="Mark as unread"><img src="/icons/unread_post.png" style="width: 55px;"></a>
|
||||||
|
{% else %}
|
||||||
|
{% set mark_read_url = "/posts/" ~ selected_peer_encoded ~ "/" ~ selected_post_encoded ~ "/read" %}
|
||||||
|
<a href={{ mark_read_url }} style="margin-left: 20px;" title="Mark as read"><img src="/icons/read_post.png" style="width: 55px;"></a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="/" style="margin-left: 20px;" title="Delete post"><img src="/icons/delete_post.png" style="width: 55px;"></a>
|
||||||
|
{% endif %}
|
||||||
|
<form class="flex-container" style="margin-left: auto; margin-right: 10px;" action="/subscribe" method="post">
|
||||||
|
<label for="public_key">Public Key</label>
|
||||||
|
<input type="text" id="public_key" name="public_key" size=50 maxlength=53>
|
||||||
|
<input type="submit" value="Subscribe">
|
||||||
|
<input type="submit" value="Unsubscribe" formaction="/unsubscribe">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
Reference in New Issue
Block a user