implement Markdown rendering

This commit is contained in:
Peter Cai 2020-04-09 14:47:46 +08:00
parent 02d64f4a18
commit 8ba086a9ec
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F
6 changed files with 158 additions and 2 deletions

46
Cargo.lock generated
View File

@ -1,5 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bumpalo"
version = "3.2.1"
@ -28,6 +34,12 @@ version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
[[package]]
name = "hex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "itoa"
version = "0.4.5"
@ -64,6 +76,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "memory_units"
version = "0.4.0"
@ -76,8 +94,10 @@ version = "0.1.0"
dependencies = [
"cfg-if",
"console_error_panic_hook",
"hex",
"js-sys",
"lazy_static",
"pulldown-cmark",
"serde",
"serde_json",
"wasm-bindgen",
@ -105,6 +125,17 @@ dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "pulldown-cmark"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c2d7fd131800e0d63df52aff46201acaab70b431a4a1ec6f0343fe8e64f35a4"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "0.6.13"
@ -177,6 +208,15 @@ dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-xid"
version = "0.1.0"
@ -189,6 +229,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "version_check"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
[[package]]
name = "wasm-bindgen"
version = "0.2.60"

View File

@ -13,16 +13,20 @@ default = ["console_error_panic_hook", "wee_alloc"]
[dependencies]
cfg-if = "0.1.2"
lazy_static = "1.4"
hex = "0.4"
js-sys = "0.3"
pulldown-cmark = { version = "0.7", default-features = false }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"Crypto",
"Headers",
"Request",
"Response",
"ResponseInit",
"SubtleCrypto",
"Url",
"UrlSearchParams"
] }

View File

@ -6,6 +6,7 @@
// unnecessary from KV.
use crate::store;
use crate::utils::*;
use pulldown_cmark::*;
use serde::{Serialize, Deserialize};
use std::vec::Vec;
@ -113,3 +114,88 @@ impl Post {
store::put_obj(&Self::uuid_to_post_key(&self.uuid), self).await
}
}
const CACHE_VERSION: &'static str = "0001";
// Cached version of rendered blog content HTMLs
// compiled from Markdown
#[derive(Serialize, Deserialize)]
pub struct PostContentCache {
// UUID of the original post
uuid: String,
// If version != CACHE_VERSION, the cache is invalidated
version: String,
// Digest of the original content
orig_digest: String,
// Compiled content in HTML
pub content: String
}
impl PostContentCache {
fn uuid_to_cache_key(uuid: &str) -> String {
format!("content_cache_{}", uuid)
}
async fn find_by_uuid(uuid: &str) -> MyResult<PostContentCache> {
store::get_obj(&Self::uuid_to_cache_key(uuid)).await
}
pub async fn find_by_post(post: &Post) -> Option<PostContentCache> {
let cache = match Self::find_by_uuid(&post.uuid).await {
Ok(cache) => cache,
Err(_) => return None
};
if cache.version != CACHE_VERSION {
return None;
}
if cache.orig_digest != crate::utils::sha1(&post.content).await {
return None;
}
Some(cache)
}
// Only renders the content and spits out a cache object
// can be used to display the page or to write to cache
// Despite the signature, this function BLOCKS
// async only comes from digesting via SubtleCrypto
pub async fn render(post: &Post) -> PostContentCache {
// TODO: enable some options; pre-process posts to enable
// inline image caching; also generate a summary (?)
// from first few paragraphs
let parser = Parser::new(&post.content);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
PostContentCache {
uuid: post.uuid.clone(),
version: CACHE_VERSION.to_owned(),
orig_digest: crate::utils::sha1(&post.content).await,
content: html_output
}
}
// Tries to find the rendered content cache of post
// if a valid cache cannot be found, this method
// will render the content, write that into cache
// and return this newly-rendered one
// This will block if it tries to render; if that's a
// concern, use find_by_post
pub async fn find_or_render(post: &Post) -> PostContentCache {
match Self::find_by_post(post).await {
Some(cache) => cache,
None => {
let ret = Self::render(post).await;
// Ignore save error since if save failed, it can be regenerated anyway
let _ = ret.save().await;
ret
}
}
}
// Save the current cache object to KV
pub async fn save(&self) -> MyResult<()> {
store::put_obj(&Self::uuid_to_cache_key(&self.uuid), self).await
}
}

View File

@ -77,7 +77,7 @@ async fn default_route(_req: Request, url: Url) -> MyResult<Response> {
} else {
// TODO: Actually render the page...
return Response::new_with_opt_str_and_init(
Some(&post.content),
Some(&blog::PostContentCache::find_or_render(&post).await.content),
ResponseInit::new()
.status(200)
.headers(headers!{

View File

@ -225,6 +225,9 @@ async fn create_or_update_post(req: Request, url: Url) -> MyResult<Response> {
} else {
list.remove_post(&post.uuid).await?;
}
// Also pre-render the post
blog::PostContentCache::find_or_render(&post).await;
// Finally, save the post
post.write_to_kv().await?;
Response::new_with_opt_str_and_init(

View File

@ -1,6 +1,9 @@
use cfg_if::cfg_if;
use serde::Deserialize;
use web_sys::Headers;
use js_sys::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::*;
use web_sys::*;
cfg_if! {
// When the `console_error_panic_hook` feature is enabled, we can call the
@ -61,6 +64,20 @@ pub fn title_to_url(uuid: &str, title: &str) -> String {
format!("{}/{}", &uuid[0..4], title_part)
}
#[wasm_bindgen]
extern "C" {
static crypto: Crypto;
}
// SHA-1 digest (hexed) via SubtleCrypto
pub async fn sha1(s: &str) -> String {
let mut bytes: Vec<u8> = s.bytes().collect();
let promise = crypto.subtle().digest_with_str_and_u8_array("SHA-1", &mut bytes).unwrap();
let buffer: ArrayBuffer = JsFuture::from(promise).await.unwrap().into();
let digest_arr = Uint8Array::new(&buffer).to_vec();
hex::encode(digest_arr)
}
pub trait HeadersExt {
fn add_cors(self) -> Self;
}