implement Markdown rendering
This commit is contained in:
parent
02d64f4a18
commit
8ba086a9ec
6 changed files with 158 additions and 2 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
] }
|
||||
|
|
86
src/blog.rs
86
src/blog.rs
|
@ -6,6 +6,7 @@
|
|||
// unnecessary from KV.
|
||||
use crate::store;
|
||||
use crate::utils::*;
|
||||
use pulldown_cmark::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::vec::Vec;
|
||||
|
||||
|
@ -112,4 +113,89 @@ impl Post {
|
|||
Self::create_url_mapping(&self.url, &self.uuid).await?;
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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!{
|
||||
|
|
|
@ -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(
|
||||
|
|
19
src/utils.rs
19
src/utils.rs
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue