add: markdown docs directory

This commit is contained in:
trisua 2025-04-05 17:28:51 -04:00
parent b31d8c38b9
commit a167da017e
8 changed files with 69 additions and 6 deletions

View file

@ -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!

View file

@ -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);

View file

@ -0,0 +1,16 @@
{% import "macros.html" as macros %} {% extends "root.html" %} {% block head %}
<title>{{ file_name }} - {{ config.name }}</title>
{% endblock %} {% block body %} {{ macros::nav(selected="notifications") }}
<main class="flex flex-col gap-2">
<div class="card-nest">
<div class="card small flex items-center justify-between gap-2">
<span class="flex items-center gap-2">
{{ icon "scroll-text" }}
<span>{{ file_name }}</span>
</span>
</div>
<div class="card">{{ file|markdown|safe }}</div>
</div>
</main>
{% endblock %}

View file

@ -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<State>) -> 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<State>,
Path(name): Path<String>,
) -> 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()))
}

View file

@ -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))

View file

@ -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(),
}
}
}

5
example/.gitignore vendored
View file

@ -1,11 +1,8 @@
atto.db*
html/*
!html/.gitkeep
public/*
!public/.gitkeep
media/*
icons/*
langs/*
docs/*

View file