Browse Source

Write posts in Markdown

master
Dylan Baker 3 years ago
parent
commit
a60aae12ca

+ 21
- 0
Cargo.lock View File

951
  "async-std",
951
  "async-std",
952
  "chrono",
952
  "chrono",
953
  "dotenv",
953
  "dotenv",
954
+ "pulldown-cmark",
954
  "serde 1.0.115",
955
  "serde 1.0.115",
955
  "serde_json",
956
  "serde_json",
956
  "tera",
957
  "tera",
1154
  "unicode-xid",
1155
  "unicode-xid",
1155
 ]
1156
 ]
1156
 
1157
 
1158
+[[package]]
1159
+name = "pulldown-cmark"
1160
+version = "0.7.2"
1161
+source = "registry+https://github.com/rust-lang/crates.io-index"
1162
+checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55"
1163
+dependencies = [
1164
+ "bitflags",
1165
+ "memchr",
1166
+ "unicase",
1167
+]
1168
+
1157
 [[package]]
1169
 [[package]]
1158
 name = "quote"
1170
 name = "quote"
1159
 version = "1.0.7"
1171
 version = "1.0.7"
1766
  "unic-common",
1778
  "unic-common",
1767
 ]
1779
 ]
1768
 
1780
 
1781
+[[package]]
1782
+name = "unicase"
1783
+version = "2.6.0"
1784
+source = "registry+https://github.com/rust-lang/crates.io-index"
1785
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
1786
+dependencies = [
1787
+ "version_check",
1788
+]
1789
+
1769
 [[package]]
1790
 [[package]]
1770
 name = "unicode-bidi"
1791
 name = "unicode-bidi"
1771
 version = "0.3.4"
1792
 version = "0.3.4"

+ 1
- 0
microblog-lib/Cargo.toml View File

10
 async-std = { version = "1.6.0", features = ["attributes"] }
10
 async-std = { version = "1.6.0", features = ["attributes"] }
11
 chrono = { version = "0.4", features = ["serde", "rustc-serialize"] }
11
 chrono = { version = "0.4", features = ["serde", "rustc-serialize"] }
12
 dotenv = "0.15.0"
12
 dotenv = "0.15.0"
13
+pulldown-cmark = { version = "0.7", default-features = false }
13
 serde = "1.0.115"
14
 serde = "1.0.115"
14
 serde_json = "1.0.57"
15
 serde_json = "1.0.57"
15
 tera = "1.5.0"
16
 tera = "1.5.0"

+ 4
- 0
microblog-lib/src/lib.rs View File

5
 mod middleware;
5
 mod middleware;
6
 mod post;
6
 mod post;
7
 mod routes;
7
 mod routes;
8
+mod util;
8
 
9
 
9
 use middleware::*;
10
 use middleware::*;
10
 
11
 
41
         .with(require_guest)
42
         .with(require_guest)
42
         .get(routes::login_page)
43
         .get(routes::login_page)
43
         .post(routes::login);
44
         .post(routes::login);
45
+    app.at("/preview")
46
+        .with(require_auth)
47
+        .post(routes::preview_post);
44
     app.at("/logout").with(require_auth).post(routes::logout);
48
     app.at("/logout").with(require_auth).post(routes::logout);
45
 
49
 
46
     Ok(app)
50
     Ok(app)

+ 7
- 3
microblog-lib/src/post.rs View File

2
 use serde_json;
2
 use serde_json;
3
 use std::io::{Error, ErrorKind};
3
 use std::io::{Error, ErrorKind};
4
 
4
 
5
-use crate::fs;
5
+use crate::{fs, util};
6
 
6
 
7
 #[derive(Debug, Serialize, Deserialize)]
7
 #[derive(Debug, Serialize, Deserialize)]
8
 pub struct Post {
8
 pub struct Post {
9
     pub id: String,
9
     pub id: String,
10
     pub title: String,
10
     pub title: String,
11
     pub body: String,
11
     pub body: String,
12
+    pub html: String,
12
     pub date: String,
13
     pub date: String,
13
 }
14
 }
14
 
15
 
17
         fs::write_post_to_disk(self)
18
         fs::write_post_to_disk(self)
18
     }
19
     }
19
 
20
 
21
+    pub fn generate_html(&mut self) -> String {
22
+        util::generate_html(&self.body)
23
+    }
24
+
20
     pub fn from_str(blob: &str) -> Result<Post, Error> {
25
     pub fn from_str(blob: &str) -> Result<Post, Error> {
21
-        let mut post: Post = match serde_json::from_str(blob) {
26
+        let post: Post = match serde_json::from_str(blob) {
22
             Ok(p) => Ok(p),
27
             Ok(p) => Ok(p),
23
             Err(_) => Err(Error::new(
28
             Err(_) => Err(Error::new(
24
                 ErrorKind::Other,
29
                 ErrorKind::Other,
25
                 format!("Error deserializing post"),
30
                 format!("Error deserializing post"),
26
             )),
31
             )),
27
         }?;
32
         }?;
28
-        post.body = post.body.replace("\n", "<br>");
29
         Ok(post)
33
         Ok(post)
30
     }
34
     }
31
 }
35
 }

+ 10
- 1
microblog-lib/src/routes.rs View File

8
 
8
 
9
 use std::env;
9
 use std::env;
10
 
10
 
11
-use crate::{fs, post::Post, State};
11
+use crate::{fs, post::Post, util, State};
12
 
12
 
13
 #[derive(Debug, Serialize, Deserialize)]
13
 #[derive(Debug, Serialize, Deserialize)]
14
 struct User {
14
 struct User {
36
     let post_id = req.param("id")?;
36
     let post_id = req.param("id")?;
37
     let mut post = fs::get_one_post(post_id).await?;
37
     let mut post = fs::get_one_post(post_id).await?;
38
     post.body = post.body.replace("<br>", "\n");
38
     post.body = post.body.replace("<br>", "\n");
39
+    post.html = post.generate_html();
39
     context.insert("post", &post);
40
     context.insert("post", &post);
40
     render_html_response("edit.html", &context, req)
41
     render_html_response("edit.html", &context, req)
41
 }
42
 }
45
     post.id = Uuid::new_v4().to_string();
46
     post.id = Uuid::new_v4().to_string();
46
     post.date = Local::now().date().naive_local().to_string();
47
     post.date = Local::now().date().naive_local().to_string();
47
     post.body = post.body.trim().to_owned();
48
     post.body = post.body.trim().to_owned();
49
+    post.html = post.generate_html();
48
     post.save().await?;
50
     post.save().await?;
49
     Ok(Redirect::new("/").into())
51
     Ok(Redirect::new("/").into())
50
 }
52
 }
51
 
53
 
52
 pub async fn update_post(mut req: Request<State>) -> Result {
54
 pub async fn update_post(mut req: Request<State>) -> Result {
53
     let mut post: Post = req.body_form().await?;
55
     let mut post: Post = req.body_form().await?;
56
+    post.html = post.generate_html();
54
     post.save().await?;
57
     post.save().await?;
55
     req.session_mut()
58
     req.session_mut()
56
         .insert("flash_success", String::from("Post updated successfully"))?;
59
         .insert("flash_success", String::from("Post updated successfully"))?;
65
     render_json_response(json!({ "success": "true" }))
68
     render_json_response(json!({ "success": "true" }))
66
 }
69
 }
67
 
70
 
71
+pub async fn preview_post(mut req: Request<State>) -> Result {
72
+    let body: String = req.body_string().await?;
73
+    let html = util::generate_html(&body);
74
+    render_json_response(json!({ "body": html }))
75
+}
76
+
68
 pub async fn login_page(req: Request<State>) -> Result {
77
 pub async fn login_page(req: Request<State>) -> Result {
69
     render_html_response("login.html", &Context::new(), req)
78
     render_html_response("login.html", &Context::new(), req)
70
 }
79
 }

+ 9
- 0
microblog-lib/src/util.rs View File

1
+use pulldown_cmark::{html, Options, Parser};
2
+
3
+pub fn generate_html(s: &str) -> String {
4
+    let options = Options::all();
5
+    let parser = Parser::new_ext(s, options);
6
+    let mut html_output = String::new();
7
+    html::push_html(&mut html_output, parser);
8
+    html_output
9
+}

+ 16
- 8
static/script.js View File

49
 
49
 
50
     const form = document.querySelector('#post-form');
50
     const form = document.querySelector('#post-form');
51
     const textarea = form.querySelector('[name=body]');
51
     const textarea = form.querySelector('[name=body]');
52
-    const preview = document.createElement('div');
53
-    const content = document.createElement('div');
54
-    preview.id = 'preview';
52
+    const previewContainer = document.createElement('div');
53
+    const contentContainer = document.createElement('div');
54
+    const content = textarea.value.trim();
55
+    previewContainer.id = 'preview';
55
 
56
 
56
-    if (textarea.value.trim().length === 0) {
57
+    if (content.length === 0) {
57
       return;
58
       return;
58
     }
59
     }
59
 
60
 
60
     const closeButton = createCloseButton();
61
     const closeButton = createCloseButton();
61
 
62
 
62
-    content.innerHTML = textarea.value;
63
-    preview.append(closeButton);
64
-    preview.append(content);
65
-    form.append(preview);
63
+    fetch('/preview', {
64
+      method: 'POST',
65
+      body: content,
66
+    }).then((r) =>
67
+      r.json().then((r) => {
68
+        contentContainer.innerHTML = r.body;
69
+        previewContainer.append(closeButton);
70
+        previewContainer.append(contentContainer);
71
+        form.append(previewContainer);
72
+      }),
73
+    );
66
   });
74
   });
67
 }
75
 }

+ 4
- 0
static/style.css View File

170
   font-size: 18px;
170
   font-size: 18px;
171
 }
171
 }
172
 
172
 
173
+#preview {
174
+  margin: 1em 0;
175
+}
176
+
173
 @media (max-width: 500px) {
177
 @media (max-width: 500px) {
174
   .form__button {
178
   .form__button {
175
     width: 100%;
179
     width: 100%;

+ 2
- 1
templates/components.html View File

14
     </div>
14
     </div>
15
   </h3>
15
   </h3>
16
   <div class="post__body">
16
   <div class="post__body">
17
-    {{ post.body | safe }}
17
+    {{ post.html | safe }}
18
   </div>
18
   </div>
19
 </div>
19
 </div>
20
 {% endmacro %} {% macro form(post=false, action) %}
20
 {% endmacro %} {% macro form(post=false, action) %}
29
     name="date"
29
     name="date"
30
     value="{% if post %}{{ post.date }}{% endif %}"
30
     value="{% if post %}{{ post.date }}{% endif %}"
31
   />
31
   />
32
+  <input type="hidden" name="html" />
32
   <div class="form__field">
33
   <div class="form__field">
33
     <label for="title" class="form__label">Title</label>
34
     <label for="title" class="form__label">Title</label>
34
     <input
35
     <input

Loading…
Cancel
Save