feat: add profile language preferences and refine toast, dropdown and history UI
This commit is contained in:
1 parent
f036240d5d
commit
bf13587a71
42 files changed
+3971
-357
No files matched your search
+34
-2
@@ -11,7 +11,7 @@ mod pages;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{DefaultBodyLimit, Request},
|
||||
extract::{DefaultBodyLimit, Path, Request},
|
||||
http::{HeaderMap, HeaderValue, Method, StatusCode, header},
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Response},
|
||||
@@ -79,6 +79,8 @@ pub fn router(
|
||||
.route("/w/{workspace_slug}", get(workspace))
|
||||
.route("/w/{workspace_slug}/n/{note_slug}", get(note))
|
||||
.route("/health", get(health))
|
||||
.route("/lang", get(language_catalog))
|
||||
.route("/lang/{code}", get(language_bundle))
|
||||
.route("/robots.txt", get(robots_txt))
|
||||
.route("/favicon.ico", get(favicon))
|
||||
.route("/favicon.svg", get(favicon_svg))
|
||||
@@ -240,6 +242,36 @@ pub fn router(
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
async fn language_catalog() -> Response {
|
||||
let mut response = Json(crate::i18n::metadata()).into_response();
|
||||
response.headers_mut().insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=300"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
async fn language_bundle(Path(code): Path<String>) -> Response {
|
||||
let Some(source) = crate::i18n::source(&code) else {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(serde_json::json!({ "error": "Language not found" })),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
let mut response = source.into_response();
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json; charset=utf-8"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static("public, max-age=86400"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
async fn require_csrf_token(request: Request, next: Next) -> Response {
|
||||
let method = request.method();
|
||||
let unsafe_method = method == Method::POST
|
||||
@@ -324,7 +356,7 @@ fn is_file_path(path: &str) -> bool {
|
||||
}
|
||||
|
||||
fn is_asset_path(path: &str) -> bool {
|
||||
path == "/assets" || path.starts_with("/assets/")
|
||||
path == "/assets" || path.starts_with("/assets/") || path == "/lang" || path.starts_with("/lang/")
|
||||
}
|
||||
|
||||
fn is_icon_path(path: &str) -> bool {
|
||||
|
||||
+29
-9
@@ -130,6 +130,7 @@ fn render_editor_page(
|
||||
entrypoint: &str,
|
||||
resource_kind: &str,
|
||||
document_title: &str,
|
||||
document_title_i18n: Option<&str>,
|
||||
parent_title: &str,
|
||||
parent_url: &str,
|
||||
parent_class: &str,
|
||||
@@ -156,6 +157,13 @@ fn render_editor_page(
|
||||
.replace("__RESOURCE_ARIA_LABEL__", resource_aria_label)
|
||||
.replace("__RESOURCE_BREADCRUMB__", &resource_breadcrumb)
|
||||
.replace("__DOCUMENT_TITLE__", &escape_html(document_title))
|
||||
.replace(
|
||||
"__DOCUMENT_TITLE_I18N__",
|
||||
document_title_i18n
|
||||
.map(|key| format!(r#"data-i18n="{}""#, escape_html(key)))
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
)
|
||||
.replace("__PARENT_TITLE__", &escape_html(parent_title))
|
||||
.replace("__PARENT_URL__", &escape_html(parent_url))
|
||||
.replace("__PARENT_CLASS__", parent_class)
|
||||
@@ -289,6 +297,7 @@ pub(super) async fn pad(
|
||||
"pad",
|
||||
"pad",
|
||||
&pad.title,
|
||||
None,
|
||||
"RustPad",
|
||||
"/",
|
||||
"home-brand",
|
||||
@@ -363,14 +372,19 @@ pub(super) async fn workspace(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let html = include_str!("../../static/workspace.html").replace(
|
||||
"__WORKSPACE_TITLE__",
|
||||
&escape_html(if workspace.is_private != 0 {
|
||||
"Workspace"
|
||||
} else {
|
||||
&workspace.title
|
||||
}),
|
||||
);
|
||||
let html = include_str!("../../static/workspace.html")
|
||||
.replace(
|
||||
"__WORKSPACE_TITLE__",
|
||||
&escape_html(if workspace.is_private != 0 {
|
||||
"Workspace"
|
||||
} else {
|
||||
&workspace.title
|
||||
}),
|
||||
)
|
||||
.replace(
|
||||
"__WORKSPACE_TITLE_I18N__",
|
||||
if workspace.is_private != 0 { r#"data-i18n="common.workspace""# } else { "" },
|
||||
);
|
||||
assets::render_html(
|
||||
&html,
|
||||
&state.asset_version,
|
||||
@@ -446,6 +460,7 @@ pub(super) async fn note(
|
||||
} else {
|
||||
¬e.title
|
||||
},
|
||||
if workspace.is_private != 0 { Some("common.note") } else { None },
|
||||
if workspace.is_private != 0 {
|
||||
"Workspace"
|
||||
} else {
|
||||
@@ -531,11 +546,16 @@ pub(super) fn error_response(
|
||||
asset_version: &str,
|
||||
) -> Response {
|
||||
let html = include_str!("../../static/error.html")
|
||||
.replace("__APP_THEME_BOOTSTRAP__", assets::theme_bootstrap())
|
||||
.replace(
|
||||
"__APP_THEME_BOOTSTRAP__",
|
||||
&format!("{}{}", assets::theme_bootstrap(), assets::language_bootstrap()),
|
||||
)
|
||||
.replace(
|
||||
"__APP_STYLESHEET__",
|
||||
&assets::stylesheet_tag(asset_version, "styles"),
|
||||
)
|
||||
.replace("__APP_IMPORT_MAP__", &assets::import_map_tag(asset_version))
|
||||
.replace("__APP_I18N__", assets::i18n_entrypoint())
|
||||
.replace("__ERROR_CODE__", &escape_html(code))
|
||||
.replace("__ERROR_TITLE__", &escape_html(title))
|
||||
.replace("__ERROR_MESSAGE__", &escape_html(message))
|
||||
|
||||
Reference in new issue
Block a user