diff --git a/README.md b/README.md index e209ba7..54663f5 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ In the directory you're running Tetratto from, you should create a `tetratto.tom Tetratto **requires** Cloudflare Turnstile for registrations. Testing keys are listed [here](https://developers.cloudflare.com/turnstile/troubleshooting/testing/). You can _technically_ disable the captcha by using the always passing, invisible keys. +A `docs` directory will be generated in the same directory that you ran the `tetratto` binary in. **Markdown** files placed here will be served at `/doc/{*file_name}`. For other types of assets, you can place them in the generated `public` directory. This directory serves everything at `/public/{*file_name}`. + ## Usage (as a user) Tetratto is very simple once you get the hang of it! At the top of the page (or bottom if you're on mobile), you'll see the navigation bar. Once logged in, you'll be able to access "Home", "Popular", and "Communities" from there! You can also press your profile picture (on the right) to view your own profile, settings, or log out! diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 347fb65..5eba52a 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -34,6 +34,7 @@ pub const COMPONENTS: &str = include_str!("./public/html/components.html"); pub const MISC_INDEX: &str = include_str!("./public/html/misc/index.html"); pub const MISC_ERROR: &str = include_str!("./public/html/misc/error.html"); pub const MISC_NOTIFICATIONS: &str = include_str!("./public/html/misc/notifications.html"); +pub const MISC_MARKDOWN: &str = include_str!("./public/html/misc/markdown.html"); pub const AUTH_BASE: &str = include_str!("./public/html/auth/base.html"); pub const AUTH_LOGIN: &str = include_str!("./public/html/auth/login.html"); @@ -159,6 +160,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"misc/index.html"(crate::assets::MISC_INDEX) -d "misc" --config=config); write_template!(html_path->"misc/error.html"(crate::assets::MISC_ERROR) --config=config); write_template!(html_path->"misc/notifications.html"(crate::assets::MISC_NOTIFICATIONS) --config=config); + write_template!(html_path->"misc/markdown.html"(crate::assets::MISC_MARKDOWN) --config=config); write_template!(html_path->"auth/base.html"(crate::assets::AUTH_BASE) -d "auth" --config=config); write_template!(html_path->"auth/login.html"(crate::assets::AUTH_LOGIN) --config=config); @@ -191,6 +193,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { /// Set up extra directories. pub(crate) async fn init_dirs(config: &Config) { create_dir_if_not_exists!(&config.dirs.templates); + create_dir_if_not_exists!(&config.dirs.docs); // images create_dir_if_not_exists!(&config.dirs.media); diff --git a/crates/app/src/public/html/misc/markdown.html b/crates/app/src/public/html/misc/markdown.html new file mode 100644 index 0000000..f5a65d0 --- /dev/null +++ b/crates/app/src/public/html/misc/markdown.html @@ -0,0 +1,16 @@ +{% import "macros.html" as macros %} {% extends "root.html" %} {% block head %} +{{ file_name }} - {{ config.name }} +{% endblock %} {% block body %} {{ macros::nav(selected="notifications") }} +
+
+
+ + {{ icon "scroll-text" }} + {{ file_name }} + +
+ +
{{ file|markdown|safe }}
+
+
+{% endblock %} diff --git a/crates/app/src/routes/pages/misc.rs b/crates/app/src/routes/pages/misc.rs index b681eef..7105d66 100644 --- a/crates/app/src/routes/pages/misc.rs +++ b/crates/app/src/routes/pages/misc.rs @@ -1,12 +1,14 @@ use super::{PaginatedQuery, render_error}; use crate::{State, assets::initial_context, get_lang, get_user_from_token}; use axum::{ - Extension, - extract::Query, + extract::{Path, Query}, response::{Html, IntoResponse}, + Extension, }; use axum_extra::extract::CookieJar; use tetratto_core::model::Error; +use std::fs::read_to_string; +use pathbufd::PathBufD; pub async fn not_found(jar: CookieJar, Extension(data): Extension) -> impl IntoResponse { let data = data.read().await; @@ -112,3 +114,37 @@ pub async fn notifications_request( data.1.render("misc/notifications.html", &context).unwrap(), )) } + +/// `/doc/{file_name}` +pub async fn markdown_document_request( + jar: CookieJar, + Extension(data): Extension, + Path(name): Path, +) -> impl IntoResponse { + let data = data.read().await; + let user = get_user_from_token!(jar, data.0); + + if name.contains("//") | name.contains("..") | name.starts_with("/") { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &user).await, + )); + } + + let path = PathBufD::current().extend(&[&data.0.0.dirs.docs, &name]); + let file = match read_to_string(&path) { + Ok(f) => f, + Err(e) => { + return Err(Html( + render_error(Error::MiscError(e.to_string()), &jar, &data, &user).await, + )); + } + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &user).await; + context.insert("file", &file); + context.insert("file_name", &name); + + // return + Ok(Html(data.1.render("misc/markdown.html", &context).unwrap())) +} diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 6079e69..b1ae182 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -20,6 +20,7 @@ pub fn routes() -> Router { .route("/", get(misc::index_request)) .route("/popular", get(misc::popular_request)) .route("/notifs", get(misc::notifications_request)) + .route("/doc/{*file_name}", get(misc::markdown_document_request)) .fallback_service(get(misc::not_found)) // mod .route("/mod_panel/audit_log", get(mod_panel::audit_log_request)) diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 1b0e5de..608d4a6 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -46,6 +46,9 @@ pub struct DirsConfig { /// The icons files directory. #[serde(default = "default_dir_icons")] pub icons: String, + /// The markdown document files directory. + #[serde(default = "default_dir_docs")] + pub docs: String, } fn default_dir_templates() -> String { @@ -64,6 +67,10 @@ fn default_dir_icons() -> String { "icons".to_string() } +fn default_dir_docs() -> String { + "docs".to_string() +} + impl Default for DirsConfig { fn default() -> Self { Self { @@ -71,6 +78,7 @@ impl Default for DirsConfig { assets: default_dir_assets(), media: default_dir_media(), icons: default_dir_icons(), + docs: default_dir_docs(), } } } diff --git a/example/.gitignore b/example/.gitignore index 6ecb124..0d1efcf 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,11 +1,8 @@ atto.db* html/* -!html/.gitkeep - public/* -!public/.gitkeep - media/* icons/* langs/* +docs/* diff --git a/example/html/.gitkeep b/example/html/.gitkeep deleted file mode 100644 index e69de29..0000000