/* * Copyright (C) 2026 Mateusz GruszczyƄski @linuxiarz.pl * Source-Available Code / Dual-Licensed. * * Free for non-commercial and evaluation use under terms of BSL/GPLv3. * Commercial or production use requires a valid paid license. * See LICENSE file in repository root for details. */ use axum::{ http::{HeaderValue, header}, response::{Html, IntoResponse, Response}, }; const MODULES: &[&str] = &[ "api", "authorship", "auth-ui", "clipboard", "collaboration", "collaboration-session", "editor-format", "emoji-data", "emoji-picker", "image-alias", "image-upload", "logger", "line-links", "markdown", "modal", "note-api", "note-editor", "note-files", "preview-edit", "render-queue", "session", "socket", "toast", "theme", "url-state", "vendor-libs", "security", ]; pub fn render_html( template: &str, asset_version: &str, registration_enabled: bool, external_auth: bool, frontend_log_level: &str, upload_max_size_bytes: usize, entrypoint: &str, ) -> Response { let urls = AssetUrls::new(asset_version); let frontend_config = frontend_config( frontend_log_level, upload_max_size_bytes, external_auth, asset_version, ); let app_stylesheets = format!( "{}{}", urls.stylesheet("styles"), urls.stylesheet_path("libs/rustpad-player/player.css"), ); let html = template .replace("__APP_THEME_BOOTSTRAP__", theme_bootstrap()) .replace("__APP_STYLESHEET__", &app_stylesheets) .replace("__APP_IMPORT_MAP__", &urls.import_map()) .replace("__APP_ENTRYPOINT__", &urls.entrypoint(entrypoint)) .replace( "__REGISTRATION_ENABLED__", if registration_enabled { "true" } else { "false" }, ) .replace( "", &format!( r#"{frontend_config}"# ), ); let mut response = Html(html).into_response(); response.headers_mut().insert( header::CACHE_CONTROL, HeaderValue::from_static("private, no-cache, no-store"), ); response } pub fn theme_bootstrap() -> &'static str { r#""# } pub fn stylesheet_tag(asset_version: &str, name: &str) -> String { AssetUrls::new(asset_version).stylesheet(name) } fn frontend_config( frontend_log_level: &str, upload_max_size_bytes: usize, external_auth: bool, asset_version: &str, ) -> String { format!( r#""#, escape_js_string(frontend_log_level), upload_max_size_bytes, external_auth, escape_js_string(asset_version), ) } struct AssetUrls<'a> { version: &'a str, } impl<'a> AssetUrls<'a> { fn new(version: &'a str) -> Self { Self { version } } fn url(&self, path: &str) -> String { format!("/assets/{path}?v={}", self.version) } fn stylesheet(&self, name: &str) -> String { self.stylesheet_path(&format!("css/{name}.css")) } fn stylesheet_path(&self, path: &str) -> String { format!(r#""#, self.url(path)) } fn entrypoint(&self, name: &str) -> String { format!( r#""#, self.url(&format!("js/{name}.js")) ) } fn import_map(&self) -> String { let imports = MODULES .iter() .map(|module| { format!( r#""@rustpad/{module}":"{}""#, self.url(&format!("js/{module}.js")) ) }) .collect::>() .join(","); format!(r#""#) } } fn escape_js_string(value: &str) -> String { value .replace('\\', "\\\\") .replace('"', "\\\"") .replace('<', "\\u003c") }