v2.2.0 - Pirate Stache - Markdown and Clensing

Message format has been changed so that all text boxes will be the same
width, rather than they change depending on name length.
TL;DR: changed location of name tag

You can now format your messages with cmark flavored markdown. There
still may be rendering bugs, but I will fix them as they come.

Messages are now properly sanitized with the ammonia package.

Messages now have proper timestamps with help from chrono. Right now
previous versions are not compatible with the new timestamp format. A
patch will be introduced in the next couple of updates to remedy that
issue.

Did some housekeeping in `static/index.css`
This commit is contained in:
saw 2021-08-06 01:01:22 -04:00
parent b92d1e6236
commit 093b6cd868
12 changed files with 124 additions and 90 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "board_server" name = "board_server"
version = "2.1.0" version = "2.2.0"
edition = "2018" edition = "2018"
license = "Zlib" license = "Zlib"
repository = "https://git.solow.xyz/cgit.cgi/Boarders/" repository = "https://git.solow.xyz/cgit.cgi/Boarders/"
@ -22,6 +22,9 @@ futures = "0.3.15"
handlebars = "4.0.1" handlebars = "4.0.1"
maplit = "1.0.2" maplit = "1.0.2"
simple_logger = "1.11.0" simple_logger = "1.11.0"
chrono = "0.4.19"
ammonia = "3.1.2"
pulldown-cmark = "0.8.0"
[lib] [lib]
name = "boarders" name = "boarders"

View File

@ -18,10 +18,11 @@ use crate::{
}; };
use handlebars::to_json; use handlebars::to_json;
use maplit::btreemap; use maplit::btreemap;
use super::TimeDerive; use super::TimeDerive;
use chrono::{DateTime, Utc};
use ammonia::clean;
use pulldown_cmark::{Parser, Options, html::push_html};
#[get("/boards/")] #[get("/boards/")]
pub async fn list_boards( pub async fn list_boards(
@ -76,7 +77,30 @@ pub async fn get_board(
}; };
if let Some(a) = Board::search(boards.iter(), board_id) { if let Some(a) = Board::search(boards.iter(), board_id) {
let data = btreemap!( let data = btreemap!(
"messages" => to_json(&a.messages), "messages" => {
let mut msg_json = to_json(&a.messages);
let mut op = Options::empty();
op.insert(Options::ENABLE_TABLES);
let msg_json = msg_json.as_array_mut().unwrap().iter_mut().zip(a.messages.borrow().iter()).map(|x| {
let parse = Parser::new_ext(&x.1.text, op);
let o = x.0.as_object_mut().unwrap();
o.insert(
"time_meta".to_string(),
to_json(DateTime::<Utc>::from(x.1.timestamp).to_rfc2822().trim_matches(|c| c=='0' || c=='+' || c==' '))
);
o.insert(
"render_text".to_string(),
{
let mut dirty_html = String::new();
push_html(&mut dirty_html, parse);
to_json(dirty_html)
}
);
x.0
}).collect::<Vec<&mut serde_json::Value>>();
to_json(msg_json)
},
"user" => match uid { "user" => match uid {
Ok(a) => to_json(data.user_manager.lock().unwrap().get_user_by_id(a.id)), Ok(a) => to_json(data.user_manager.lock().unwrap().get_user_by_id(a.id)),
Err(_) => to_json(None::<Option<u8>>) Err(_) => to_json(None::<Option<u8>>)
@ -112,7 +136,6 @@ pub async fn board_post(
let um = data.user_manager.lock().unwrap(); let um = data.user_manager.lock().unwrap();
match board { match board {
Some(ref mut a) => { Some(ref mut a) => {
//let a: &mut Board = board.unwrap();
let msg = mf.create_message({ let msg = mf.create_message({
let user = um.get_user_by_id(form.user_id).ok_or_else(|| { let user = um.get_user_by_id(form.user_id).ok_or_else(|| {
HttpResponse::NotFound().body("Not Found: User does not exist") HttpResponse::NotFound().body("Not Found: User does not exist")
@ -122,8 +145,7 @@ pub async fn board_post(
} else { } else {
return Err(HttpResponse::Unauthorized().body("Unauthorized: Bad login attempt, invalid auth token")); return Err(HttpResponse::Unauthorized().body("Unauthorized: Bad login attempt, invalid auth token"));
} }
}, form.content.clone().trim()); }, clean(&form.content));
println!("new message");
a.add_message(msg); a.add_message(msg);
Ok(HttpResponse::SeeOther().header("location", format!("/board/{}/", a.id)).finish()) Ok(HttpResponse::SeeOther().header("location", format!("/board/{}/", a.id)).finish())
}, },

View File

@ -33,7 +33,7 @@ impl Board {
/* backwards compatability */ /* backwards compatability */
if let Ok(ref mut b) = b { if let Ok(ref mut b) = b {
if b.last_active.is_none() { if b.last_active.is_none() {
b.last_active = b.messages.borrow().last().map(|x| x.timestamp); b.last_active = b.messages.borrow().last().map(|x| x.as_secs());
} }
} }
b b
@ -50,7 +50,7 @@ impl Board {
} }
pub fn add_message(&mut self, msg: Message) -> &Board { pub fn add_message(&mut self, msg: Message) -> &Board {
self.last_active = Some(msg.timestamp); self.last_active = Some(msg.as_secs());
self.messages.borrow_mut().push(msg); self.messages.borrow_mut().push(msg);
self self
} }

View File

@ -14,10 +14,16 @@ use super::{
pub struct Message { pub struct Message {
pub text: String, pub text: String,
pub id: u32, pub id: u32,
pub timestamp: u64, pub timestamp: SystemTime,
pub author: RefCell<User> pub author: RefCell<User>
} }
impl Message {
pub fn as_secs(&self) -> u64 {
self.timestamp.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct MessageFactory { pub struct MessageFactory {
pub next_id: u32, pub next_id: u32,
@ -36,11 +42,10 @@ impl MessageFactory {
pub fn create_message<S>(&mut self, user: User, content: S) -> Message where S: AsRef<str> { pub fn create_message<S>(&mut self, user: User, content: S) -> Message where S: AsRef<str> {
self.next_id += 1; self.next_id += 1;
let time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
Message { Message {
text: String::from(content.as_ref()), text: String::from(content.as_ref()),
id: self.next_id-1, id: self.next_id-1,
timestamp: time, timestamp: SystemTime::now(),
author: RefCell::new(user) author: RefCell::new(user)
} }
} }

View File

@ -42,7 +42,7 @@ use tokio::{
select select
}; };
use handlebars::Handlebars; use handlebars::{Handlebars, handlebars_helper};
use console::style; use console::style;
use maplit::btreemap; use maplit::btreemap;
@ -274,13 +274,16 @@ async fn main() -> std::io::Result<()> {
* variable at compile time * variable at compile time
*/ */
/* register handlebars partials here */ /* register handlebars partials here */
h.register_partial("login_status", include_str!("res/snippets/login_status.hbs")).unwrap(); handlebars_helper!(msg_helper: |s: str| format!("helped: {}", s.to_string()));
h.register_partial("board_bar", include_str!("res/snippets/board_bar.hbs")).unwrap(); h.register_helper("msg", Box::new(msg_helper));
h.register_partial("head", include_str!("res/snippets/head.hbs")).unwrap();
h.register_partial("login_status", include_str!("res/snippets/login_status.hbs")).unwrap();
h.register_partial("board_bar", include_str!("res/snippets/board_bar.hbs")).unwrap();
/* register handlebars templates here */ /* register handlebars templates here */
h.register_template_string("board_index", include_str!("res/board_index.hbs")).unwrap(); h.register_template_string("board_index", include_str!("res/board_index.hbs")).unwrap();
h.register_template_string("welcome", include_str!("res/welcome.hbs")).unwrap(); h.register_template_string("welcome", include_str!("res/welcome.hbs")).unwrap();
h.register_template_string("auth", include_str!("res/auth.hbs")).unwrap(); h.register_template_string("auth", include_str!("res/auth.hbs")).unwrap();
h.register_template_string("board", include_str!("res/board.hbs")).unwrap(); h.register_template_string("board", include_str!("res/board.hbs")).unwrap();
h h
} }
}).app_data(web_data.clone()) }).app_data(web_data.clone())

View File

@ -1,8 +1,6 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css"> {{> head}}
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body> <body>
<div id="title" class="bar"> <div id="title" class="bar">
{{#if user.nick}} {{#if user.nick}}

View File

@ -1,8 +1,6 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css"> {{> head}}
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body style="overflow-x: hidden;"> <body style="overflow-x: hidden;">
{{> board_bar board=board user=user}} {{> board_bar board=board user=user}}
<div id="index"> <div id="index">
@ -18,16 +16,16 @@
<b>{{msg.author.username}}</b> <b>{{msg.author.username}}</b>
{{/if}} {{/if}}
<div class="message-content"> <div class="message-content">
<pre>{{msg.text}}</pre> <pre>{{{msg.render_text}}}</pre>
</div> </div>
<div class="message-time"> <div class="message-time">
{{msg.timestamp}} {{msg.time_meta}}
</div> </div>
</div> </div>
{{/each}} {{/each}}
</div> </div>
<form action="/boards/post" method="post" style="display: flex; align-items: center; flex-direction: column;"> <form action="/boards/post" method="post" style="display: flex; align-items: center; flex-direction: column;">
<label for="content">Content:</label><br/> <label for="content">Write a message:</label><br/>
<input type="hidden" id="board_id" name="board_id" value="{{board.id}}"> <input type="hidden" id="board_id" name="board_id" value="{{board.id}}">
<input type="hidden" id="user_id" name="user_id" value="{{user.user_id}}"> <input type="hidden" id="user_id" name="user_id" value="{{user.user_id}}">
<textarea name="content" id="content" rows=12 cols=50 placeholder="Enter message here..."></textarea> <textarea name="content" id="content" rows=12 cols=50 placeholder="Enter message here..."></textarea>

View File

@ -1,8 +1,6 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css"> {{> head}}
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body style="overflow-x: hidden;"> <body style="overflow-x: hidden;">
<div id="title" class="bar"> <div id="title" class="bar">
<div> <div>

View File

@ -1,8 +1,6 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css"> {{> head}}
<link rel="stylesheet" type="text/css" href="/static/index.css">
</head>
<body> <body>
<div id="title" class="bar"> <div id="title" class="bar">
<h1>Hello, {{name}}</h1> <h1>Hello, {{name}}</h1>

View File

@ -0,0 +1,9 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/>
{{#if nolocal}}
{{else}}
<link rel="stylesheet" type="text/css" href="/static/index.css">
{{/if}}
</head>

View File

@ -1,7 +1,6 @@
<html> <!DOCTYPE html>
<head> <html lang="en">
<link rel="stylesheet" type="text/css" href="https://orbitalfox.us/Music/index.css"> {{> head nolocal=1}}
</head>
<body> <body>
<h1> <h1>
<div style="display: flex; align-items: center; flex-direction: column;"> <div style="display: flex; align-items: center; flex-direction: column;">

View File

@ -60,56 +60,17 @@ body {
font-size: 2.5em !important; font-size: 2.5em !important;
} }
#nav {
width: 3em;
position: fixed;
right: 0;
top: 0;
margin-left: 3rem;
height: 100%;
}
.arrow {
width: 2.5rem;
background-color: #07a6ea;
color: white;
padding: 0.25rem;
border-radius: 0.5em;
}
.orange-arrow {
width: 2.5rem;
color: orange;
padding: 0.25rem;
border-radius: 0.5em;
}
.down {
transform: rotate(180deg);
-webkit-transform:rotate(180deg);
-moz-transform: rotate(180deg);
-ms-transform: rotate(180deg);
-o-transform: rotate(180deg);
}
#navbar > img.arrow.down {
unset: top;
bottom: 0;
margin-bottom: 0.5em;
}
#navbar > img.arrow {
position: relative;
}
.message { .message {
margin-top: 0.5em; margin-top: 0.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
border-bottom: 1px solid #555; border-bottom: 1px solid #555;
display: flex; display: flex;
flex-direction: row; flex-direction: inherit;
justify-content: flex-start; justify-content: flex-start;
align-items: center; }
.message > b {
padding-left: 1em;
} }
.message > * { .message > * {
@ -197,7 +158,12 @@ tr.board:hover {
.message-content { .message-content {
flex-grow: 1; flex-grow: 1;
max-width: 90%; /*max-width: 90%; */
}
.message-content > pre {
margin-top: 0.1rem;
margin-bottom: 0.1rem;
} }
pre { pre {
@ -207,3 +173,38 @@ pre {
white-space: -o-pre-wrap; /* Opera 7 */ white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */ word-wrap: break-word; /* Internet Explorer 5.5+ */
} }
pre > h1 {
font-size: 2rem;
}
pre > h2 {
font-size: 1.75rem;
}
pre > h3 {
font-size: 1.5rem;
}
pre > h4 {
font-size: 1.25rem;
}
pre > h5 {
font-size: 1rem;
}
pre > *:last-child {
margin-bottom: unset;
}
code {
background-color: #222;
padding: 0.5rem;
border-radius: 0.25rem;
}
.message-time {
font-size: 0.75rem;
color: #555
}