add: better layout
This commit is contained in:
parent
2cc9ed7445
commit
dbd70d9592
19 changed files with 451 additions and 87 deletions
15
src/main.rs
15
src/main.rs
|
@ -7,7 +7,7 @@ use axum::{Extension, Router};
|
|||
use nanoneo::core::element::Render;
|
||||
use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc};
|
||||
use tera::{Tera, Value};
|
||||
use tetratto_core::sdk::DataClient;
|
||||
use tetratto_core::{html, sdk::DataClient};
|
||||
use tetratto_shared::hash::salt;
|
||||
use tokio::sync::RwLock;
|
||||
use tower_http::{
|
||||
|
@ -64,20 +64,29 @@ async fn main() {
|
|||
|
||||
// build lisp
|
||||
create_dir_if_not_exists!("./templates_build");
|
||||
create_dir_if_not_exists!("./icons");
|
||||
|
||||
for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") {
|
||||
match x {
|
||||
Ok(x) => std::fs::write(
|
||||
x.to_str()
|
||||
.unwrap()
|
||||
.replace("templates_src/", "templates_build/"),
|
||||
nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
|
||||
.render(&mut HashMap::new()),
|
||||
html::pull_icons(
|
||||
nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
|
||||
.render(&mut HashMap::new()),
|
||||
"./icons",
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.expect("failed to write template"),
|
||||
Err(e) => panic!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// create docs dir
|
||||
create_dir_if_not_exists!("./docs");
|
||||
|
||||
// ...
|
||||
let mut tera = match Tera::new(&format!("./templates_build/**/*")) {
|
||||
Ok(t) => t,
|
||||
|
|
|
@ -76,7 +76,7 @@ fn parse_text_color_line(output: &mut String, buffer: &mut String, line: &str) {
|
|||
// by this point, we have: !
|
||||
// %color_buffer%main_buffer%%
|
||||
output.push_str(&format!(
|
||||
"<span style=\"color: {color_buffer}\">{buffer}</span>"
|
||||
"<span style=\"color: {color_buffer}\" class=\"color_block\">{buffer}</span>"
|
||||
));
|
||||
|
||||
color_buffer.clear();
|
||||
|
@ -468,7 +468,14 @@ fn parse_image_line(output: &mut String, buffer: &mut String, line: &str) {
|
|||
if in_image {
|
||||
// end
|
||||
output.push_str(&format!(
|
||||
"<img loading=\"lazy\" alt=\"{alt}\" src=\"{buffer}\" />"
|
||||
"<img loading=\"lazy\" alt=\"{alt}\" src=\"{buffer}\" style=\"float: {}\" />",
|
||||
if buffer.ends_with("#left") {
|
||||
"left"
|
||||
} else if buffer.ends_with("#right") {
|
||||
"right"
|
||||
} else {
|
||||
"unset"
|
||||
}
|
||||
));
|
||||
|
||||
alt = String::new();
|
||||
|
|
13
src/model.rs
13
src/model.rs
|
@ -357,6 +357,17 @@ macro_rules! metadata_css {
|
|||
}
|
||||
};
|
||||
|
||||
($selector:literal, $property:literal !important, $self:ident.$field:ident->$output:ident) => {
|
||||
if !$self.$field.is_empty() {
|
||||
$output.push_str(&format!(
|
||||
"{} {{ {}: {} !important; }}\n",
|
||||
$selector,
|
||||
$property,
|
||||
EntryMetadata::css_escape(&$self.$field)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
($selector:literal, $property:literal, $format:literal, $self:ident.$field:ident->$output:ident) => {
|
||||
if !$self.$field.is_empty() {
|
||||
$output.push_str(&format!(
|
||||
|
@ -462,7 +473,7 @@ impl EntryMetadata {
|
|||
metadata_css!(".container", "border-radius", self.container_border_radius->output);
|
||||
metadata_css!(".container", "box-shadow", self.container_shadow->output);
|
||||
metadata_css!(".container", "text-shadow", self.content_text_shadow->output);
|
||||
metadata_css!(".container a", "color", self.content_text_link_color->output);
|
||||
metadata_css!("*, html *", "--color-link" !important, self.content_text_link_color->output);
|
||||
|
||||
if self.content_text_align != TextAlignment::Left {
|
||||
output.push_str(&format!(
|
||||
|
|
100
src/routes.rs
100
src/routes.rs
|
@ -8,6 +8,8 @@ use axum::{
|
|||
response::{Html, IntoResponse},
|
||||
routing::{get, get_service, post},
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use pathbufd::PathBufD;
|
||||
use serde::Deserialize;
|
||||
use serde_valid::Validate;
|
||||
use tera::Context;
|
||||
|
@ -32,6 +34,7 @@ pub fn routes() -> Router {
|
|||
get_service(tower_http::services::ServeDir::new("./public")),
|
||||
)
|
||||
.fallback(not_found_request)
|
||||
.route("/docs/{name}", get(view_doc_request))
|
||||
// pages
|
||||
.route("/", get(index_request))
|
||||
.route("/{slug}", get(view_request))
|
||||
|
@ -78,6 +81,39 @@ async fn index_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
async fn view_doc_request(
|
||||
Extension(data): Extension<State>,
|
||||
Path(name): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||
let path = PathBufD::current().extend(&["docs", &format!("{name}.md")]);
|
||||
|
||||
if !std::fs::exists(&path).unwrap_or(false) {
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
ctx.insert(
|
||||
"error",
|
||||
&Error::GeneralNotFound("entry".to_string()).to_string(),
|
||||
);
|
||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
let text = match std::fs::read_to_string(&path) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
ctx.insert("error", &Error::MiscError(e.to_string()).to_string());
|
||||
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||
}
|
||||
};
|
||||
|
||||
let mut ctx = default_context(&data, &build_code);
|
||||
|
||||
ctx.insert("text", &text);
|
||||
ctx.insert("file_name", &name);
|
||||
|
||||
return Html(tera.render("doc.lisp", &ctx).unwrap());
|
||||
}
|
||||
|
||||
async fn view_request(
|
||||
Extension(data): Extension<State>,
|
||||
Path(slug): Path<String>,
|
||||
|
@ -258,27 +294,46 @@ fn default_random() -> String {
|
|||
salt()
|
||||
}
|
||||
|
||||
/// The time that must be waited between each entry creation.
|
||||
const CREATE_WAIT_TIME: usize = 5000;
|
||||
|
||||
async fn create_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Json(req): Json<CreateEntry>,
|
||||
) -> impl IntoResponse {
|
||||
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||
let (ref data, _, _) = *data.read().await;
|
||||
|
||||
// check wait time
|
||||
if let Some(cookie) = jar.get("__Secure-Claim-Next") {
|
||||
if unix_epoch_timestamp()
|
||||
!= cookie
|
||||
.to_string()
|
||||
.replace("__Secure-Claim-Next=", "")
|
||||
.parse::<usize>()
|
||||
.unwrap_or(0)
|
||||
{
|
||||
return Err(Json(
|
||||
Error::MiscError("You must wait a bit to create another entry".to_string()).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// check lengths
|
||||
if req.slug.len() < 2 {
|
||||
return Json(Error::DataTooShort("slug".to_string()).into());
|
||||
return Err(Json(Error::DataTooShort("slug".to_string()).into()));
|
||||
}
|
||||
|
||||
if req.slug.len() > 32 {
|
||||
return Json(Error::DataTooLong("slug".to_string()).into());
|
||||
return Err(Json(Error::DataTooLong("slug".to_string()).into()));
|
||||
}
|
||||
|
||||
if req.content.len() < 2 {
|
||||
return Json(Error::DataTooShort("content".to_string()).into());
|
||||
return Err(Json(Error::DataTooShort("content".to_string()).into()));
|
||||
}
|
||||
|
||||
if req.content.len() > 150_000 {
|
||||
return Json(Error::DataTooLong("content".to_string()).into());
|
||||
return Err(Json(Error::DataTooLong("content".to_string()).into()));
|
||||
}
|
||||
|
||||
// check slug
|
||||
|
@ -288,17 +343,19 @@ async fn create_request(
|
|||
.unwrap();
|
||||
|
||||
if regex.captures(&req.slug).is_some() {
|
||||
return Json(Error::MiscError("This slug contains invalid characters".to_string()).into());
|
||||
return Err(Json(
|
||||
Error::MiscError("This slug contains invalid characters".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
// check metadata
|
||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
Err(e) => return Err(Json(Error::MiscError(e.to_string()).into())),
|
||||
};
|
||||
|
||||
if let Err(e) = metadata.validate() {
|
||||
return Json(Error::MiscError(e.to_string()).into());
|
||||
return Err(Json(Error::MiscError(e.to_string()).into()));
|
||||
}
|
||||
|
||||
// check for existing
|
||||
|
@ -310,7 +367,9 @@ async fn create_request(
|
|||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Json(Error::MiscError("Slug already in use".to_string()).into());
|
||||
return Err(Json(
|
||||
Error::MiscError("Slug already in use".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
// create
|
||||
|
@ -333,22 +392,31 @@ async fn create_request(
|
|||
)
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
|
||||
if let Err(e) = data
|
||||
.insert(format!("entries.views('{}')", req.slug), 0.to_string())
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
|
||||
// return
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: Some((req.slug, req.edit_code)),
|
||||
})
|
||||
Ok((
|
||||
[(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"__Secure-Claim-Next={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age=5",
|
||||
unix_epoch_timestamp() + CREATE_WAIT_TIME
|
||||
),
|
||||
)],
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: Some((req.slug, req.edit_code)),
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue