167 lines
4.7 KiB
Rust
167 lines
4.7 KiB
Rust
/*
|
|
* 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(
|
|
"</head>",
|
|
&format!(
|
|
r#"<link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="icon" href="/favicon.ico" sizes="any"><link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32"><link rel="apple-touch-icon" href="/apple-touch-icon.png">{frontend_config}</head>"#
|
|
),
|
|
);
|
|
|
|
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#"<script>(()=>{const key="rustpad:theme";let theme=matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";try{const saved=localStorage.getItem(key);if(saved==="light"||saved==="dark")theme=saved}catch{}const root=document.documentElement;root.dataset.theme=theme;root.style.colorScheme=theme;const meta=document.querySelector('meta[name="color-scheme"]');if(meta)meta.content=theme})();</script>"#
|
|
}
|
|
|
|
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#"<script>window.__RUSTPAD_CONFIG__=Object.freeze({{frontendLogLevel:"{}",uploadMaxSizeBytes:{},externalAuth:{},assetVersion:"{}"}});</script>"#,
|
|
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#"<link rel="stylesheet" href="{}">"#, self.url(path))
|
|
}
|
|
|
|
fn entrypoint(&self, name: &str) -> String {
|
|
format!(
|
|
r#"<script type="module" src="{}"></script>"#,
|
|
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::<Vec<_>>()
|
|
.join(",");
|
|
|
|
format!(r#"<script type="importmap">{{"imports":{{{imports}}}}}</script>"#)
|
|
}
|
|
}
|
|
|
|
fn escape_js_string(value: &str) -> String {
|
|
value
|
|
.replace('\\', "\\\\")
|
|
.replace('"', "\\\"")
|
|
.replace('<', "\\u003c")
|
|
}
|