Switch over to WebAssembly, Rust and Yew #35
@ -120,26 +120,6 @@ async fn copy_resources(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_data(out_dir: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
log::info!("Writing tags.json ...");
|
|
||||||
let tags = site::model::source::get_tags();
|
|
||||||
tokio::fs::write(
|
|
||||||
out_dir.as_ref().join("tags.json"),
|
|
||||||
serde_json::to_string(&tags)?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
log::info!("Writing posts.json ...");
|
|
||||||
let posts = site::model::source::get_posts();
|
|
||||||
tokio::fs::write(
|
|
||||||
out_dir.as_ref().join("posts.json"),
|
|
||||||
serde_json::to_string(&posts)?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
@ -176,8 +156,5 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
tokio::fs::write(path, html).await?;
|
tokio::fs::write(path, html).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Writing data");
|
|
||||||
write_data(&out_dir).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,87 @@
|
|||||||
#[cfg(feature = "hydration")]
|
use include_dir::{include_dir, Dir, File};
|
||||||
mod hydrating;
|
|
||||||
|
|
||||||
#[cfg(feature = "hydration")]
|
|
||||||
pub use hydrating::*;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "hydration"))]
|
|
||||||
mod filesystem;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
|
||||||
|
|
||||||
#[cfg(not(feature = "hydration"))]
|
use super::{frontmatter::parse_front_matter, PostInfo, Tag};
|
||||||
pub use filesystem::*;
|
|
||||||
|
|
||||||
use super::{PostInfo, Tag};
|
|
||||||
|
|
||||||
pub type TagsContext = HashMap<String, Tag>;
|
pub type TagsContext = HashMap<String, Tag>;
|
||||||
pub type PostsContext = Vec<PostInfo>;
|
pub type PostsContext = Vec<PostInfo>;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct ProvideTagsProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(ProvideTags)]
|
||||||
|
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
|
||||||
|
let tags = get_tags();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<ContextProvider<TagsContext> context={tags}>
|
||||||
|
{props.children.clone()}
|
||||||
|
</ContextProvider<TagsContext>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tags() -> TagsContext {
|
||||||
|
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
|
||||||
|
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
|
||||||
|
Ok(tags) => tags
|
||||||
|
.into_iter()
|
||||||
|
.map(|tag| (tag.slug.clone(), tag))
|
||||||
|
.collect::<TagsContext>(),
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Failed to parse tags.yaml: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct ProvidePostsProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(ProvidePosts)]
|
||||||
|
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
|
||||||
|
let posts = get_posts();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<ContextProvider<PostsContext> context={posts}>
|
||||||
|
{props.children.clone()}
|
||||||
|
</ContextProvider<PostsContext>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_posts() -> PostsContext {
|
||||||
|
let files = CONTENT_DIR.get_dir("posts").expect("posts dir").files();
|
||||||
|
let mut posts = files.filter_map(load_post_info).collect::<Vec<_>>();
|
||||||
|
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
|
||||||
|
posts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_post_info(file: &File) -> Option<PostInfo> {
|
||||||
|
let (Some(front_matter), matter) = parse_front_matter(file.contents()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let slug = file
|
||||||
|
.path()
|
||||||
|
.file_stem()
|
||||||
|
.expect("filename")
|
||||||
|
.to_str()
|
||||||
|
.expect("valid file name")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let reading_time = words_count::count(&matter.content).words / 200;
|
||||||
|
|
||||||
|
Some(PostInfo::from_front_matter(
|
||||||
|
slug,
|
||||||
|
Some(reading_time),
|
||||||
|
matter.excerpt,
|
||||||
|
front_matter,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
use include_dir::{include_dir, Dir, File};
|
|
||||||
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
|
|
||||||
|
|
||||||
use crate::model::{
|
|
||||||
frontmatter::parse_front_matter,
|
|
||||||
source::{PostsContext, TagsContext},
|
|
||||||
PostInfo, Tag,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
|
||||||
pub struct ProvideTagsProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(ProvideTags)]
|
|
||||||
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
|
|
||||||
let tags = get_tags();
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<ContextProvider<TagsContext> context={tags}>
|
|
||||||
{props.children.clone()}
|
|
||||||
</ContextProvider<TagsContext>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tags() -> TagsContext {
|
|
||||||
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
|
|
||||||
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
|
|
||||||
Ok(tags) => tags
|
|
||||||
.into_iter()
|
|
||||||
.map(|tag| (tag.slug.clone(), tag))
|
|
||||||
.collect::<TagsContext>(),
|
|
||||||
Err(err) => {
|
|
||||||
panic!("Failed to parse tags.yaml: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
|
||||||
pub struct ProvidePostsProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(ProvidePosts)]
|
|
||||||
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
|
|
||||||
let posts = get_posts();
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<ContextProvider<PostsContext> context={posts}>
|
|
||||||
{props.children.clone()}
|
|
||||||
</ContextProvider<PostsContext>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_posts() -> PostsContext {
|
|
||||||
let files = CONTENT_DIR.get_dir("posts").expect("posts dir").files();
|
|
||||||
let mut posts = files.filter_map(load_post_info).collect::<Vec<_>>();
|
|
||||||
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
|
|
||||||
posts
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_post_info(file: &File) -> Option<PostInfo> {
|
|
||||||
let (Some(front_matter), matter) = parse_front_matter(file.contents()) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let slug = file
|
|
||||||
.path()
|
|
||||||
.file_stem()
|
|
||||||
.expect("filename")
|
|
||||||
.to_str()
|
|
||||||
.expect("valid file name")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let reading_time = words_count::count(&matter.content).words / 200;
|
|
||||||
|
|
||||||
Some(PostInfo::from_front_matter(
|
|
||||||
slug,
|
|
||||||
Some(reading_time),
|
|
||||||
matter.excerpt,
|
|
||||||
front_matter,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");
|
|
@ -1,94 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use thiserror::Error;
|
|
||||||
use yew::{function_component, html, use_state, Children, ContextProvider, Html, Properties};
|
|
||||||
use yew_hooks::{use_async_with_options, UseAsyncOptions};
|
|
||||||
|
|
||||||
use crate::model::source::{PostsContext, TagsContext};
|
|
||||||
|
|
||||||
#[derive(Debug, Error, Clone, PartialEq)]
|
|
||||||
pub enum ApiError {
|
|
||||||
#[error("Unable to complete request")]
|
|
||||||
RequestError,
|
|
||||||
#[error("Unable to deserialize response")]
|
|
||||||
DeserializeError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
|
||||||
pub struct ProvideTagsProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(ProvideTags)]
|
|
||||||
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
|
|
||||||
let tags = use_state(HashMap::new);
|
|
||||||
|
|
||||||
{
|
|
||||||
let tags = tags.clone();
|
|
||||||
use_async_with_options::<_, (), ApiError>(
|
|
||||||
async move {
|
|
||||||
let res = reqwest::get("/tags.json")
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
log::error!("Failed to get '/tags.json': {err:?}");
|
|
||||||
ApiError::RequestError
|
|
||||||
})?
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
log::error!("Failed to deserialize '/tags.json': {err:?}");
|
|
||||||
ApiError::DeserializeError
|
|
||||||
})?;
|
|
||||||
tags.set(res);
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
UseAsyncOptions::enable_auto(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<ContextProvider<TagsContext> context={(*tags).clone()}>
|
|
||||||
{props.children.clone()}
|
|
||||||
</ContextProvider<TagsContext>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
|
||||||
pub struct ProvidePostsProps {
|
|
||||||
#[prop_or_default]
|
|
||||||
pub children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component(ProvidePosts)]
|
|
||||||
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
|
|
||||||
let posts = use_state(Vec::new);
|
|
||||||
|
|
||||||
{
|
|
||||||
let posts = posts.clone();
|
|
||||||
use_async_with_options::<_, (), ApiError>(
|
|
||||||
async move {
|
|
||||||
let res = reqwest::get("/posts.json")
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
log::error!("Failed to get '/posts.json': {err:?}");
|
|
||||||
ApiError::RequestError
|
|
||||||
})?
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
log::error!("Failed to deserialize '/posts.json': {err:?}");
|
|
||||||
ApiError::DeserializeError
|
|
||||||
})?;
|
|
||||||
posts.set(res);
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
UseAsyncOptions::enable_auto(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<ContextProvider<PostsContext> context={(*posts).clone()}>
|
|
||||||
{props.children.clone()}
|
|
||||||
</ContextProvider<PostsContext>>
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user