use chrono::prelude::*;
use std::{env, vec};
use blogdb::posts::Post;
use blogdb::BlogDb;
use lol_html::html_content::ContentType;
use lol_html::{comments, element, rewrite_str, RewriteStrSettings};
use crate::routes::api::editor::Blocks;
use crate::routes::SearchResponse;
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
/// Gets template html string from `src/templates`
///
/// # Examples
///
/// ```
/// // `src/templates/blog-roll.html`
/// //
/// //
Blog
/// //
///
/// let template = template!("blog-roll");
/// assert_eq!(template, "
Blog
\n
");
/// ```
macro_rules! template {
($t:tt) => {
TEMPLATES.get($t).unwrap_or(&"")
};
}
/// Makes a page with default settings and content inserted into
/// (or replacing if content has a tag)
pub(crate) fn make_page(content: S, settings: PageSettings) -> String
where
S: AsRef,
{
rewrite_str(
template!("default"),
RewriteStrSettings {
element_content_handlers: vec![
element!("head", |head| {
head.prepend(
&["", &settings.title, ""].concat(),
ContentType::Html,
);
if let Some(stylesheets) = &settings.stylesheets {
for url in stylesheets {
head.append(
&[r#""#].concat(),
ContentType::Html,
)
}
}
Ok(())
}),
element!("body", |body| {
body.prepend("", ContentType::Html);
Ok(())
}),
element!("main", |main| {
if content.as_ref().contains("") {
main.replace(content.as_ref(), ContentType::Html);
} else {
main.set_inner_content(content.as_ref(), ContentType::Html);
}
Ok(())
}),
element!("site-header", |site_header| {
if settings.site_header {
site_header.replace(template!("site-header"), ContentType::Html);
}
Ok(())
}),
element!("site-footer", |site_footer| {
if settings.site_footer {
site_footer.replace(template!("site-footer"), ContentType::Html);
}
Ok(())
}),
element!("br", |br| {
br.remove();
Ok(())
}),
comments!("*", |comments| {
comments.remove();
Ok(())
}),
],
..RewriteStrSettings::new()
},
)
.unwrap_or_default()
}
pub(crate) fn rewrite_el(input: S, name: T, with: R) -> String
where
S: AsRef,
T: AsRef,
R: AsRef,
{
rewrite_str(
input.as_ref(),
RewriteStrSettings {
element_content_handlers: vec![element!(name.as_ref(), |el| {
el.replace(with.as_ref(), ContentType::Html);
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default()
}
pub(crate) fn remove_el(input: S, name: T) -> String
where
S: AsRef,
T: AsRef,
{
rewrite_str(
input.as_ref(),
RewriteStrSettings {
element_content_handlers: vec![element!(name.as_ref(), |el| {
el.remove();
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default()
}
fn remove_comments>(input: S) -> String {
rewrite_str(
input.as_ref(),
RewriteStrSettings {
element_content_handlers: vec![comments!("*", |co| {
co.remove();
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default()
}
pub(crate) fn homepage_editor>(homepage_content: S) -> String {
editor(homepage_content, "/api/editor/update/home")
}
pub(crate) fn about_editor>(about_content: S) -> String {
editor(about_content, "/api/editor/update/about")
}
pub(crate) fn draft_editor(post_content: S, post_id: T) -> String
where
S: AsRef,
T: AsRef,
{
editor(
post_content,
["/api/editor/update/", post_id.as_ref()].concat(),
)
}
fn editor(content: S, endpoint: T) -> String
where
S: AsRef,
T: AsRef,
{
let content = content.as_ref();
let data = if !content.is_empty() && content.starts_with("[") && content.ends_with("]") {
content
} else {
"[]"
};
let settings = [
"const data={blocks:",
data,
"};const onChange=()=>{editor.save().then(async(savedData)=>{const endpoint = \"",endpoint.as_ref(),"\"; const response = await fetch(endpoint, { method: \"POST\", mode: \"same-origin\", headers: {\"Content-Type\": \"application/json\"}, body: JSON.stringify(savedData.blocks) });})};"
].concat();
let html = rewrite_str(
template!("post-editor"),
RewriteStrSettings {
element_content_handlers: vec![element!("#editor", move |script| {
script.prepend(&settings, ContentType::Html);
Ok(())
})],
strict: false,
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
make_page(
html,
PageSettings::new("Editor", Some(vec!["/assets/css/editor.css"]), true, false),
)
}
/// Rewrites `` with html from template, and sets the `next` hidden
/// input element to the next location
pub(crate) fn login_status(next: &str, success: bool) -> String {
let status = if success {
""
} else {
template!("login-status")
};
let next = next.strip_prefix("/").unwrap_or(next);
let html = rewrite_el(template!("login"), "login-status", status);
let html = rewrite_str(
&html,
RewriteStrSettings {
element_content_handlers: vec![element!(r#"input[name="next"]"#, |input| {
input.set_attribute("value", next).unwrap_or_default();
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
let html = rewrite_str(
&html,
RewriteStrSettings {
element_content_handlers: vec![element!(r#"input[name="next"]"#, |input| {
input.set_attribute("value", next).unwrap_or_default();
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
make_page(
html,
PageSettings::new("Login", Some(vec!["/assets/css/login.css"]), false, false),
)
}
pub(crate) async fn admin_page(session_id: i64, db: &BlogDb) -> String {
let content = admin_widgets(template!("admin"), session_id, db).await;
make_page(
&content,
PageSettings::new("Admin", Some(vec!["/assets/css/admin.css"]), true, false),
)
}
pub(crate) async fn admin_widgets(input: &str, session_id: i64, db: &BlogDb) -> String {
let posts_html = admin_entries(EntryType::Post, session_id, db).await;
let drafts_html = admin_entries(EntryType::Draft, session_id, db).await;
let user_html = template!("admin-widgets/user");
rewrite_str(
input,
RewriteStrSettings {
element_content_handlers: vec![element!("admin-widget", move |a| {
match a.get_attribute("type") {
Some(attr) => {
match attr.as_str() {
"drafts" => a.replace(&drafts_html, ContentType::Html),
"posts" => a.replace(&posts_html, ContentType::Html),
"user" => a.replace(user_html, ContentType::Html),
_ => a.remove(),
}
Ok(())
}
None => todo!(),
}
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default()
}
enum EntryType {
Post,
Draft,
}
async fn admin_entries(entry_type: EntryType, session_id: i64, db: &BlogDb) -> String {
let (list_template, entry_template) = match entry_type {
EntryType::Post => (
template!("admin-widgets/posts"),
template!("admin-widgets/post"),
),
EntryType::Draft => (
template!("admin-widgets/drafts"),
template!("admin-widgets/draft"),
),
};
let is_empty: bool;
let entries_html = match entry_type {
EntryType::Post => {
let mut entries = db.get_posts().await.unwrap_or(vec![]);
entries.reverse();
let mut entry_list_html = String::new();
is_empty = entries.is_empty();
if !is_empty {
for entry in entries {
let href = ["/blog/", &entry.id].concat();
let mut entry_html = admin_entry(
entry_template,
&entry.id,
&entry.title,
&href,
&entry.content,
);
entry_html = rewrite_str(
&entry_html,
RewriteStrSettings {
element_content_handlers: vec![element!("time", |time| {
time.set_inner_content(&entry.date, ContentType::Text);
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
entry_list_html.push_str(&entry_html);
}
}
entry_list_html
}
EntryType::Draft => {
let mut entries = db.get_drafts(session_id).await.unwrap_or(vec![]);
entries.reverse();
let mut entry_list_html = String::new();
is_empty = entries.is_empty();
if !is_empty {
for entry in entries {
let href = ["/editor/", &entry.id].concat();
let entry_html = admin_entry(
entry_template,
&entry.id,
&entry.title,
&href,
&entry.content,
);
entry_list_html.push_str(&entry_html);
}
}
entry_list_html
}
};
let final_html = rewrite_str(
list_template,
RewriteStrSettings {
element_content_handlers: vec![element!("ul", |ul| {
if is_empty {
match entry_type {
EntryType::Post => ul.replace("