WIP: Various UI updates from previous session
- Update UI server module - Update suite index and JavaScript files - Add public directory Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6b1dcc9d3f
commit
1bf9510c7d
8 changed files with 410 additions and 40 deletions
|
|
@ -163,15 +163,19 @@ pub async fn index(OriginalUri(uri): OriginalUri) -> Response {
|
|||
let fs_path = if path_parts.len() > 1 {
|
||||
let mut start_idx = 1;
|
||||
let known_dirs = ["suite", "js", "css", "vendor", "assets", "public", "partials", "settings", "auth", "about", "drive", "chat", "tasks", "admin", "mail", "calendar", "meet", "docs", "sheet", "slides", "paper", "research", "sources", "learn", "analytics", "dashboards", "monitoring", "people", "crm", "tickets", "billing", "products", "video", "player", "canvas", "social", "project", "goals", "workspace", "designer"];
|
||||
|
||||
|
||||
// Special case: /auth/suite/* should map to suite/* (auth is a route, not a directory)
|
||||
if path_parts.get(1) == Some(&"auth") && path_parts.get(2) == Some(&"suite") {
|
||||
start_idx = 2;
|
||||
}
|
||||
// Skip bot name if present (first segment is not a known dir, second segment is)
|
||||
if path_parts.len() > start_idx + 1
|
||||
&& !known_dirs.contains(&path_parts[start_idx])
|
||||
else if path_parts.len() > start_idx + 1
|
||||
&& !known_dirs.contains(&path_parts[start_idx])
|
||||
&& known_dirs.contains(&path_parts[start_idx + 1])
|
||||
{
|
||||
start_idx += 1;
|
||||
}
|
||||
|
||||
|
||||
path_parts[start_idx..].join("/")
|
||||
} else {
|
||||
path.to_string()
|
||||
|
|
@ -338,25 +342,39 @@ pub async fn serve_suite(bot_name: Option<String>) -> impl IntoResponse {
|
|||
|
||||
// Inject base tag and bot_name into the page
|
||||
if let Some(head_end) = html.find("</head>") {
|
||||
// Check if bot_name is actually an auth page (login.html, register.html, etc.)
|
||||
// These are not actual bots, so we should use "/" as base href
|
||||
let is_auth_page = bot_name.as_ref()
|
||||
.map(|n| n.ends_with(".html") || n == "login" || n == "register" || n == "forgot-password" || n == "reset-password")
|
||||
.unwrap_or(false);
|
||||
|
||||
// Set base href to include bot context if present (e.g., /edu/)
|
||||
let base_href = if let Some(ref name) = bot_name {
|
||||
// But NOT for auth pages - those use root
|
||||
let base_href = if is_auth_page {
|
||||
"/".to_string()
|
||||
} else if let Some(ref name) = bot_name {
|
||||
format!("/{}/", name)
|
||||
} else {
|
||||
"/".to_string()
|
||||
};
|
||||
let base_tag = format!(r#"<base href="{}">"#, base_href);
|
||||
html.insert_str(head_end, &base_tag);
|
||||
|
||||
if let Some(name) = bot_name {
|
||||
info!("serve_suite: Injecting bot_name '{}' into page with base href='{}'", name, base_href);
|
||||
let bot_script = format!(
|
||||
r#"<script>window.__INITIAL_BOT_NAME__ = "{}";</script>"#,
|
||||
&name
|
||||
);
|
||||
html.insert_str(head_end + base_tag.len(), &bot_script);
|
||||
info!("serve_suite: Successfully injected base tag and bot_name script");
|
||||
|
||||
// Only inject bot_name script for actual bots, not auth pages
|
||||
if !is_auth_page {
|
||||
if let Some(name) = bot_name {
|
||||
info!("serve_suite: Injecting bot_name '{}' into page with base href='{}'", name, base_href);
|
||||
let bot_script = format!(
|
||||
r#"<script>window.__INITIAL_BOT_NAME__ = "{}";</script>"#,
|
||||
&name
|
||||
);
|
||||
html.insert_str(head_end + base_tag.len(), &bot_script);
|
||||
info!("serve_suite: Successfully injected base tag and bot_name script");
|
||||
} else {
|
||||
info!("serve_suite: Successfully injected base tag (no bot_name)");
|
||||
}
|
||||
} else {
|
||||
info!("serve_suite: Successfully injected base tag (no bot_name)");
|
||||
info!("serve_suite: Auth page detected, skipping bot_name injection (base href='{}')", base_href);
|
||||
}
|
||||
} else {
|
||||
error!("serve_suite: Failed to find </head> tag to inject content");
|
||||
|
|
@ -1150,6 +1168,23 @@ async fn handle_embedded_root_asset(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "embed-ui")]
|
||||
async fn handle_auth_asset(axum::extract::Path(path): axum::extract::Path<String>) -> impl IntoResponse {
|
||||
let normalized_path = path.strip_prefix('/').unwrap_or(&path);
|
||||
let asset_path = format!("suite/auth/{}", normalized_path);
|
||||
match Assets::get(&asset_path) {
|
||||
Some(content) => {
|
||||
let mime = mime_guess::from_path(&asset_path).first_or_octet_stream();
|
||||
(
|
||||
[(axum::http::header::CONTENT_TYPE, mime.as_ref())],
|
||||
content.data,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_static_routes(router: Router<AppState>, _suite_path: &Path) -> Router<AppState> {
|
||||
#[cfg(feature = "embed-ui")]
|
||||
{
|
||||
|
|
@ -1195,6 +1230,16 @@ pub fn configure_router() -> Router {
|
|||
.route("/minimal", get(serve_minimal))
|
||||
.route("/suite", get(serve_suite));
|
||||
|
||||
#[cfg(not(feature = "embed-ui"))]
|
||||
{
|
||||
router = router.nest_service("/auth", ServeDir::new(suite_path.join("auth")));
|
||||
}
|
||||
|
||||
#[cfg(feature = "embed-ui")]
|
||||
{
|
||||
router = router.route("/auth/*path", get(handle_auth_asset));
|
||||
}
|
||||
|
||||
router = add_static_routes(router, &suite_path);
|
||||
|
||||
router.fallback(get(index)).with_state(state)
|
||||
|
|
|
|||
73
ui/public/themes/dark.css
Normal file
73
ui/public/themes/dark.css
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* Dark Theme for General Bots */
|
||||
:root {
|
||||
--color-primary: #d4f505;
|
||||
--color-secondary: #00d4aa;
|
||||
--color-accent: #818cf8;
|
||||
|
||||
--color-bg: #0f172a;
|
||||
--color-bg-secondary: #1e293b;
|
||||
--color-bg-tertiary: #334155;
|
||||
|
||||
--color-text: #f1f5f9;
|
||||
--color-text-secondary: #cbd5e1;
|
||||
--color-text-muted: #64748b;
|
||||
|
||||
--color-border: #334155;
|
||||
--color-border-light: #1e293b;
|
||||
|
||||
--color-success: #22c55e;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
--color-info: #3b82f6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
|
||||
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.4);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.5);
|
||||
|
||||
--radius-sm: 0.25rem;
|
||||
--radius: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(212, 245, 5, 0.1);
|
||||
}
|
||||
73
ui/public/themes/light.css
Normal file
73
ui/public/themes/light.css
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* Light Theme for General Bots */
|
||||
:root {
|
||||
--color-primary: #d4f505;
|
||||
--color-secondary: #00d4aa;
|
||||
--color-accent: #6366f1;
|
||||
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-secondary: #f8fafc;
|
||||
--color-bg-tertiary: #f1f5f9;
|
||||
|
||||
--color-text: #0f172a;
|
||||
--color-text-secondary: #475569;
|
||||
--color-text-muted: #94a3b8;
|
||||
|
||||
--color-border: #e2e8f0;
|
||||
--color-border-light: #f1f5f9;
|
||||
|
||||
--color-success: #22c55e;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
--color-info: #3b82f6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
|
||||
--radius-sm: 0.25rem;
|
||||
--radius: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(212, 245, 5, 0.1);
|
||||
}
|
||||
117
ui/public/themes/y2kglow.css
Normal file
117
ui/public/themes/y2kglow.css
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/* Y2K Glow Theme for General Bots */
|
||||
:root {
|
||||
--color-primary: #ff00ff;
|
||||
--color-secondary: #00ffff;
|
||||
--color-accent: #ffff00;
|
||||
|
||||
--color-bg: #0a0a1a;
|
||||
--color-bg-secondary: #1a0a2e;
|
||||
--color-bg-tertiary: #2d1b4e;
|
||||
|
||||
--color-text: #00ff00;
|
||||
--color-text-secondary: #ff00ff;
|
||||
--color-text-muted: #00ffff;
|
||||
|
||||
--color-border: #ff00ff;
|
||||
--color-border-light: #00ffff;
|
||||
|
||||
--color-success: #00ff00;
|
||||
--color-warning: #ffff00;
|
||||
--color-error: #ff0066;
|
||||
--color-info: #00ffff;
|
||||
|
||||
--shadow-glow: 0 0 10px #ff00ff, 0 0 20px #ff00ff, 0 0 30px #ff00ff;
|
||||
--shadow-sm: 0 0 5px rgba(255, 0, 255, 0.5);
|
||||
--shadow: 0 0 10px rgba(255, 0, 255, 0.7);
|
||||
--shadow-md: 0 0 15px rgba(255, 0, 255, 0.8);
|
||||
--shadow-lg: 0 0 25px rgba(255, 0, 255, 0.9);
|
||||
|
||||
--radius-sm: 0.25rem;
|
||||
--radius: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
text-shadow: 0 0 5px var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-secondary);
|
||||
text-shadow: 0 0 5px var(--color-secondary);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary);
|
||||
text-shadow: 0 0 10px var(--color-primary), 0 0 20px var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, var(--color-primary), var(--color-secondary));
|
||||
color: var(--color-bg);
|
||||
border: 2px solid var(--color-primary);
|
||||
box-shadow: var(--shadow-glow);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(45deg, var(--color-secondary), var(--color-accent));
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 0 15px var(--color-secondary), 0 0 30px var(--color-secondary);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(135deg, var(--color-bg-secondary), var(--color-bg-tertiary));
|
||||
border: 2px solid var(--color-primary);
|
||||
box-shadow: var(--shadow);
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from {
|
||||
box-shadow: 0 0 5px var(--color-primary), 0 0 10px var(--color-primary);
|
||||
}
|
||||
to {
|
||||
box-shadow: 0 0 10px var(--color-secondary), 0 0 20px var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
background: var(--color-bg-secondary);
|
||||
border: 2px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
box-shadow: 0 0 5px var(--color-border);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 10px var(--color-accent), 0 0 20px var(--color-accent), 0 0 30px var(--color-accent);
|
||||
}
|
||||
|
||||
input::placeholder, textarea::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
text-shadow: 0 0 3px var(--color-text-muted);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(var(--color-primary), var(--color-secondary));
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 10px var(--color-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(var(--color-secondary), var(--color-accent));
|
||||
}
|
||||
|
|
@ -50,10 +50,10 @@
|
|||
|
||||
<!-- SECURITY BOOTSTRAP - MUST load immediately after HTMX -->
|
||||
<!-- This provides centralized auth for ALL apps: HTMX, fetch, XHR -->
|
||||
<script src="suite/js/security-bootstrap.js?v=20260110"></script>
|
||||
<script src="suite/js/security-bootstrap.js?v=20260207b"></script>
|
||||
|
||||
<!-- ERROR REPORTER - Captures JS errors and sends to server log -->
|
||||
<script src="suite/js/error-reporter.js"></script>
|
||||
<script src="suite/js/error-reporter.js?v=20260207c"></script>
|
||||
|
||||
<!-- i18n -->
|
||||
<script src="suite/js/i18n.js"></script>
|
||||
|
|
|
|||
|
|
@ -36,13 +36,15 @@
|
|||
|
||||
if (!response.ok) {
|
||||
console.warn('[ErrorReporter] Failed to send errors:', response.status);
|
||||
} else {
|
||||
console.log('[ErrorReporter] Sent', errorsToReport.length, 'errors to server');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[ErrorReporter] Failed to send errors:', e.message);
|
||||
errorQueue.unshift(...errorsToReport);
|
||||
} finally {
|
||||
isReporting = false;
|
||||
|
||||
|
||||
if (errorQueue.length > 0) {
|
||||
setTimeout(reportErrors, 1000);
|
||||
}
|
||||
|
|
@ -76,6 +78,15 @@
|
|||
report: function(error, context) {
|
||||
queueError(formatError(error, context));
|
||||
},
|
||||
reportNetworkError: function(url, status, statusText) {
|
||||
queueError({
|
||||
type: 'NetworkError',
|
||||
message: `Failed to load ${url}: ${status} ${statusText}`,
|
||||
url: window.location.href,
|
||||
timestamp: new Date().toISOString(),
|
||||
context: { url, status, statusText }
|
||||
});
|
||||
},
|
||||
flush: function() {
|
||||
reportErrors();
|
||||
}
|
||||
|
|
@ -101,7 +112,7 @@
|
|||
url: window.location.href,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
|
||||
queueError({
|
||||
name: 'Navigation',
|
||||
message: `${method}: ${from} -> ${to}`,
|
||||
|
|
@ -110,26 +121,70 @@
|
|||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('click', function(e) {
|
||||
const target = e.target.closest('[data-section]');
|
||||
if (target) {
|
||||
const section = target.getAttribute('data-section');
|
||||
const currentHash = window.location.hash.slice(1) || '';
|
||||
if (section !== currentHash) {
|
||||
setTimeout(() => {
|
||||
window.NavigationLogger.log(currentHash || 'home', section, 'click');
|
||||
}, 100);
|
||||
}
|
||||
function initNavigationTracking() {
|
||||
if (!document.body) {
|
||||
setTimeout(initNavigationTracking, 50);
|
||||
return;
|
||||
}
|
||||
}, true);
|
||||
|
||||
window.addEventListener('hashchange', function(e) {
|
||||
const oldURL = new URL(e.oldURL);
|
||||
const newURL = new URL(e.newURL);
|
||||
const fromHash = oldURL.hash.slice(1) || '';
|
||||
const toHash = newURL.hash.slice(1) || '';
|
||||
window.NavigationLogger.log(fromHash || 'home', toHash, 'hashchange');
|
||||
if (document.body) {
|
||||
document.body.addEventListener('click', function(e) {
|
||||
const target = e.target.closest('[data-section]');
|
||||
if (target) {
|
||||
const section = target.getAttribute('data-section');
|
||||
const currentHash = window.location.hash.slice(1) || '';
|
||||
if (section !== currentHash) {
|
||||
setTimeout(() => {
|
||||
window.NavigationLogger.log(currentHash || 'home', section, 'click');
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', function(e) {
|
||||
const oldURL = new URL(e.oldURL);
|
||||
const newURL = new URL(e.newURL);
|
||||
const fromHash = oldURL.hash.slice(1) || '';
|
||||
const toHash = newURL.hash.slice(1) || '';
|
||||
window.NavigationLogger.log(fromHash || 'home', toHash, 'hashchange');
|
||||
});
|
||||
|
||||
console.log('[NavigationLogger] Navigation tracking initialized');
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initNavigationTracking);
|
||||
} else {
|
||||
initNavigationTracking();
|
||||
}
|
||||
|
||||
// Intercept link onload/onerror events to catch CSS/image load failures
|
||||
const originalCreateElement = document.createElement;
|
||||
document.createElement = function(tagName) {
|
||||
const element = originalCreateElement.call(document, tagName);
|
||||
if (tagName.toLowerCase() === 'link') {
|
||||
element.addEventListener('error', function() {
|
||||
if (this.href && window.ErrorReporter && window.ErrorReporter.reportNetworkError) {
|
||||
window.ErrorReporter.reportNetworkError(this.href, 'LOAD_FAILED', 'Resource failed to load');
|
||||
}
|
||||
});
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(() => {
|
||||
const failedResources = performance.getEntriesByType('resource').filter(entry =>
|
||||
entry.transferSize === 0 && entry.decodedBodySize > 0 && !entry.name.includes('anon') && entry.duration > 100
|
||||
);
|
||||
|
||||
if (failedResources.length > 0) {
|
||||
console.warn('[ErrorReporter] Detected potentially failed resources:', failedResources);
|
||||
failedResources.forEach(resource => {
|
||||
window.ErrorReporter.reportNetworkError(resource.name, 'FAILED', 'Resource load timeout/failure');
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
console.log('[NavigationLogger] Navigation tracking initialized');
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -210,10 +210,14 @@
|
|||
return originalFetch
|
||||
.call(window, input, init)
|
||||
.then(function (response) {
|
||||
var url = typeof input === "string" ? input : input.url;
|
||||
|
||||
if (response.status === 401) {
|
||||
var url = typeof input === "string" ? input : input.url;
|
||||
self.handleUnauthorized(url);
|
||||
} else if (!response.ok && window.ErrorReporter && window.ErrorReporter.reportNetworkError) {
|
||||
window.ErrorReporter.reportNetworkError(url, response.status, response.statusText);
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1024,7 +1024,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
// Skip SPA initialization on auth pages (login, register, etc.)
|
||||
if (window.location.pathname.startsWith("/auth/")) {
|
||||
console.log("[SPA] Skipping initialization on auth page");
|
||||
} else if (document.readyState === "complete") {
|
||||
setTimeout(initialLoad, 50);
|
||||
} else {
|
||||
window.addEventListener("load", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue