2025-11-15 19:08:26 -03:00
|
|
|
|
const sections = {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
drive: "drive/drive.html",
|
|
|
|
|
|
tasks: "tasks/tasks.html",
|
|
|
|
|
|
mail: "mail/mail.html",
|
|
|
|
|
|
chat: "chat/chat.html",
|
2025-11-15 19:08:26 -03:00
|
|
|
|
};
|
2025-11-17 10:16:01 -03:00
|
|
|
|
const sectionCache = {};
|
2025-11-15 10:16:09 -03:00
|
|
|
|
|
2025-11-17 12:11:13 -03:00
|
|
|
|
function getBasePath() {
|
|
|
|
|
|
// All static assets (HTML, CSS, JS) are served from the site root.
|
2025-11-20 16:02:48 -03:00
|
|
|
|
// Returning empty string for relative paths when served from same directory
|
|
|
|
|
|
return "";
|
2025-11-17 12:11:13 -03:00
|
|
|
|
}
|
2025-11-17 12:16:53 -03:00
|
|
|
|
|
|
|
|
|
|
// Preload chat CSS to avoid flash on first load
|
|
|
|
|
|
function preloadChatCSS() {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const chatCssPath = getBasePath() + "chat/chat.css";
|
2025-11-17 12:16:53 -03:00
|
|
|
|
const existing = document.querySelector(`link[href="${chatCssPath}"]`);
|
|
|
|
|
|
if (!existing) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const link = document.createElement("link");
|
|
|
|
|
|
link.rel = "stylesheet";
|
2025-11-17 12:16:53 -03:00
|
|
|
|
link.href = chatCssPath;
|
|
|
|
|
|
document.head.appendChild(link);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-15 19:08:26 -03:00
|
|
|
|
async function loadSectionHTML(path) {
|
2025-11-17 12:11:13 -03:00
|
|
|
|
const fullPath = getBasePath() + path;
|
|
|
|
|
|
const response = await fetch(fullPath);
|
2025-11-20 14:35:23 -03:00
|
|
|
|
if (!response.ok) throw new Error("Failed to load section: " + fullPath);
|
2025-11-15 19:08:26 -03:00
|
|
|
|
return await response.text();
|
|
|
|
|
|
}
|
2025-11-15 10:16:09 -03:00
|
|
|
|
|
2025-11-20 18:29:55 -03:00
|
|
|
|
async function loadScript(jsPath) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const existingScript = document.querySelector(`script[src="${jsPath}"]`);
|
|
|
|
|
|
if (existingScript) {
|
|
|
|
|
|
console.log(`Script already loaded: ${jsPath}`);
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const script = document.createElement("script");
|
|
|
|
|
|
script.src = jsPath;
|
|
|
|
|
|
script.onload = () => {
|
|
|
|
|
|
console.log(`✓ Script loaded: ${jsPath}`);
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
};
|
|
|
|
|
|
script.onerror = (err) => {
|
|
|
|
|
|
console.error(`✗ Script failed to load: ${jsPath}`, err);
|
|
|
|
|
|
reject(err);
|
|
|
|
|
|
};
|
|
|
|
|
|
document.body.appendChild(script);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-15 19:08:26 -03:00
|
|
|
|
async function switchSection(section) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const mainContent = document.getElementById("main-content");
|
2025-11-17 10:16:01 -03:00
|
|
|
|
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Validate section exists
|
|
|
|
|
|
if (!sections[section]) {
|
|
|
|
|
|
console.warn(`Section "${section}" does not exist, defaulting to chat`);
|
|
|
|
|
|
section = "chat";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up any existing WebSocket connections from chat
|
|
|
|
|
|
if (
|
|
|
|
|
|
window.chatAppInstance &&
|
|
|
|
|
|
typeof window.chatAppInstance.cleanup === "function"
|
|
|
|
|
|
) {
|
|
|
|
|
|
window.chatAppInstance.cleanup();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-15 19:08:26 -03:00
|
|
|
|
try {
|
2025-11-15 21:52:53 -03:00
|
|
|
|
const htmlPath = sections[section];
|
2025-11-20 14:35:23 -03:00
|
|
|
|
console.log("Loading section:", section, "from", htmlPath);
|
|
|
|
|
|
const cssPath = getBasePath() + htmlPath.replace(".html", ".css");
|
2025-11-20 18:29:55 -03:00
|
|
|
|
const jsPath = getBasePath() + htmlPath.replace(".html", ".js");
|
2025-11-17 10:16:01 -03:00
|
|
|
|
|
2025-11-17 12:16:53 -03:00
|
|
|
|
// Preload chat CSS if the target is chat
|
2025-11-20 14:35:23 -03:00
|
|
|
|
if (section === "chat") {
|
2025-11-17 12:16:53 -03:00
|
|
|
|
preloadChatCSS();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-15 21:52:53 -03:00
|
|
|
|
// Remove any existing section CSS
|
2025-11-20 14:35:23 -03:00
|
|
|
|
document
|
|
|
|
|
|
.querySelectorAll("link[data-section-css]")
|
|
|
|
|
|
.forEach((link) => link.remove());
|
2025-11-17 10:16:01 -03:00
|
|
|
|
|
|
|
|
|
|
// Load CSS first (skip if already loaded)
|
|
|
|
|
|
let cssLink = document.querySelector(`link[href="${cssPath}"]`);
|
|
|
|
|
|
if (!cssLink) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
cssLink = document.createElement("link");
|
|
|
|
|
|
cssLink.rel = "stylesheet";
|
2025-11-17 10:16:01 -03:00
|
|
|
|
cssLink.href = cssPath;
|
2025-11-20 14:35:23 -03:00
|
|
|
|
cssLink.setAttribute("data-section-css", "true");
|
2025-11-17 10:16:01 -03:00
|
|
|
|
document.head.appendChild(cssLink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Hide previously loaded sections and show the requested one
|
|
|
|
|
|
// Ensure a container exists for sections
|
2025-11-20 14:35:23 -03:00
|
|
|
|
let container = document.getElementById("section-container");
|
2025-11-17 10:16:01 -03:00
|
|
|
|
if (!container) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
container = document.createElement("div");
|
|
|
|
|
|
container.id = "section-container";
|
2025-11-17 10:16:01 -03:00
|
|
|
|
mainContent.appendChild(container);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const targetDiv = document.getElementById(`section-${section}`);
|
|
|
|
|
|
|
|
|
|
|
|
if (targetDiv) {
|
|
|
|
|
|
// Section already loaded: hide others, show this one
|
2025-11-20 14:35:23 -03:00
|
|
|
|
container.querySelectorAll(".section").forEach((div) => {
|
|
|
|
|
|
div.style.display = "none";
|
2025-11-17 10:16:01 -03:00
|
|
|
|
});
|
2025-11-20 14:35:23 -03:00
|
|
|
|
targetDiv.style.display = "block";
|
2025-11-17 10:16:01 -03:00
|
|
|
|
} else {
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Remove any existing loading divs first
|
|
|
|
|
|
container.querySelectorAll(".loading").forEach((div) => {
|
|
|
|
|
|
div.remove();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-17 10:16:01 -03:00
|
|
|
|
// Show loading placeholder inside the container
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const loadingDiv = document.createElement("div");
|
|
|
|
|
|
loadingDiv.className = "loading";
|
|
|
|
|
|
loadingDiv.textContent = "Loading…";
|
2025-11-17 10:16:01 -03:00
|
|
|
|
container.appendChild(loadingDiv);
|
|
|
|
|
|
|
2025-11-20 18:29:55 -03:00
|
|
|
|
// For Alpine sections, load JavaScript FIRST before HTML
|
|
|
|
|
|
const isAlpineSection = ["drive", "tasks", "mail"].includes(section);
|
|
|
|
|
|
|
|
|
|
|
|
if (isAlpineSection) {
|
|
|
|
|
|
console.log(`Loading JS before HTML for Alpine section: ${section}`);
|
|
|
|
|
|
await loadScript(jsPath);
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for the component function to be registered
|
|
|
|
|
|
const appFunctionName = section + "App";
|
|
|
|
|
|
let retries = 0;
|
2025-11-20 20:39:20 -03:00
|
|
|
|
while (typeof window[appFunctionName] !== "function" && retries < 100) {
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
2025-11-20 18:29:55 -03:00
|
|
|
|
retries++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof window[appFunctionName] !== "function") {
|
|
|
|
|
|
console.error(`${appFunctionName} function not found after waiting!`);
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
`Component function ${appFunctionName} not available`,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✓ Component function registered: ${appFunctionName}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 10:16:01 -03:00
|
|
|
|
// Load HTML
|
|
|
|
|
|
const html = await loadSectionHTML(htmlPath);
|
2025-11-20 18:29:55 -03:00
|
|
|
|
|
2025-11-17 10:16:01 -03:00
|
|
|
|
// Create wrapper for the new section
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const wrapper = document.createElement("div");
|
2025-11-17 10:16:01 -03:00
|
|
|
|
wrapper.id = `section-${section}`;
|
2025-11-20 14:35:23 -03:00
|
|
|
|
wrapper.className = "section";
|
2025-11-20 18:29:55 -03:00
|
|
|
|
|
|
|
|
|
|
// For Alpine sections, mark for manual initialization
|
|
|
|
|
|
if (isAlpineSection) {
|
|
|
|
|
|
wrapper.setAttribute("x-ignore", "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 10:16:01 -03:00
|
|
|
|
wrapper.innerHTML = html;
|
|
|
|
|
|
|
|
|
|
|
|
// Hide any existing sections
|
2025-11-20 14:35:23 -03:00
|
|
|
|
container.querySelectorAll(".section").forEach((div) => {
|
|
|
|
|
|
div.style.display = "none";
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Dispatch a custom event to notify sections they're being hidden
|
|
|
|
|
|
div.dispatchEvent(new CustomEvent("section-hidden"));
|
2025-11-17 10:16:01 -03:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Remove loading placeholder if it still exists
|
|
|
|
|
|
if (loadingDiv && loadingDiv.parentNode) {
|
|
|
|
|
|
container.removeChild(loadingDiv);
|
|
|
|
|
|
}
|
2025-11-17 10:16:01 -03:00
|
|
|
|
|
|
|
|
|
|
// Add the new section to the container and cache it
|
|
|
|
|
|
container.appendChild(wrapper);
|
|
|
|
|
|
sectionCache[section] = wrapper;
|
|
|
|
|
|
|
2025-11-20 18:29:55 -03:00
|
|
|
|
// For Alpine sections, initialize after DOM insertion
|
|
|
|
|
|
if (isAlpineSection && window.Alpine) {
|
|
|
|
|
|
console.log(`Initializing Alpine for section: ${section}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Remove x-ignore to allow Alpine to process
|
|
|
|
|
|
wrapper.removeAttribute("x-ignore");
|
|
|
|
|
|
|
2025-11-20 20:39:20 -03:00
|
|
|
|
// Verify component function is available
|
|
|
|
|
|
const appFunctionName = section + "App";
|
|
|
|
|
|
if (typeof window[appFunctionName] !== "function") {
|
|
|
|
|
|
console.error(`${appFunctionName} not available during Alpine init!`);
|
|
|
|
|
|
throw new Error(`Component ${appFunctionName} missing`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-20 18:29:55 -03:00
|
|
|
|
// Small delay to ensure DOM is ready
|
2025-11-20 20:39:20 -03:00
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
2025-11-20 18:29:55 -03:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-11-20 20:39:20 -03:00
|
|
|
|
console.log(`Calling Alpine.initTree for ${section}`);
|
2025-11-20 18:29:55 -03:00
|
|
|
|
window.Alpine.initTree(wrapper);
|
|
|
|
|
|
console.log(`✓ Alpine initialized for ${section}`);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(`Error initializing Alpine for ${section}:`, err);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (!isAlpineSection) {
|
|
|
|
|
|
// For non-Alpine sections (like chat), load JS after HTML
|
|
|
|
|
|
await loadScript(jsPath);
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Dispatch a custom event to notify the section it's being shown
|
|
|
|
|
|
wrapper.dispatchEvent(new CustomEvent("section-shown"));
|
|
|
|
|
|
|
2025-11-17 12:16:53 -03:00
|
|
|
|
// Ensure the new section is visible with a fast GSAP fade-in
|
|
|
|
|
|
gsap.fromTo(
|
|
|
|
|
|
wrapper,
|
|
|
|
|
|
{ opacity: 0 },
|
2025-11-20 14:35:23 -03:00
|
|
|
|
{ opacity: 1, duration: 0.15, ease: "power2.out" },
|
2025-11-17 12:16:53 -03:00
|
|
|
|
);
|
2025-11-17 10:16:01 -03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-20 14:35:23 -03:00
|
|
|
|
window.history.pushState({}, "", `#${section}`);
|
2025-11-20 14:40:15 -03:00
|
|
|
|
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const inputEl = document.getElementById("messageInput");
|
2025-11-17 12:16:53 -03:00
|
|
|
|
if (inputEl) {
|
|
|
|
|
|
inputEl.focus();
|
|
|
|
|
|
}
|
2025-11-15 19:08:26 -03:00
|
|
|
|
} catch (err) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
console.error("Error loading section:", err);
|
2025-11-15 19:08:26 -03:00
|
|
|
|
mainContent.innerHTML = `<div class="error">Failed to load ${section} section</div>`;
|
2025-11-15 10:16:09 -03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-15 19:08:26 -03:00
|
|
|
|
// Handle initial load based on URL hash
|
2025-11-17 12:11:13 -03:00
|
|
|
|
function getInitialSection() {
|
|
|
|
|
|
// 1️⃣ Prefer hash fragment (e.g., #chat)
|
|
|
|
|
|
let section = window.location.hash.substring(1);
|
|
|
|
|
|
// 2️⃣ Fallback to pathname segments (e.g., /chat)
|
|
|
|
|
|
if (!section) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const parts = window.location.pathname.split("/").filter((p) => p);
|
2025-11-17 12:11:13 -03:00
|
|
|
|
const last = parts[parts.length - 1];
|
2025-11-20 14:35:23 -03:00
|
|
|
|
if (["drive", "tasks", "mail", "chat"].includes(last)) {
|
2025-11-17 12:11:13 -03:00
|
|
|
|
section = last;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3️⃣ As a last resort, inspect the full URL for known sections
|
|
|
|
|
|
if (!section) {
|
2025-11-20 14:35:23 -03:00
|
|
|
|
const match = window.location.href.match(
|
|
|
|
|
|
/\/(drive|tasks|mail|chat)(?:\.html)?(?:[?#]|$)/i,
|
|
|
|
|
|
);
|
2025-11-17 12:11:13 -03:00
|
|
|
|
if (match) {
|
|
|
|
|
|
section = match[1].toLowerCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-17 12:16:53 -03:00
|
|
|
|
// Default to chat if nothing matches
|
2025-11-20 14:35:23 -03:00
|
|
|
|
return section || "chat";
|
2025-11-17 12:11:13 -03:00
|
|
|
|
}
|
2025-11-20 18:29:55 -03:00
|
|
|
|
|
2025-11-20 14:35:23 -03:00
|
|
|
|
window.addEventListener("DOMContentLoaded", () => {
|
2025-11-20 18:29:55 -03:00
|
|
|
|
console.log("DOM Content Loaded");
|
|
|
|
|
|
|
|
|
|
|
|
const initApp = () => {
|
2025-11-20 16:29:10 -03:00
|
|
|
|
const section = getInitialSection();
|
2025-11-20 18:29:55 -03:00
|
|
|
|
console.log(`Initializing app with section: ${section}`);
|
|
|
|
|
|
|
2025-11-20 16:29:10 -03:00
|
|
|
|
// Ensure valid section
|
|
|
|
|
|
if (!sections[section]) {
|
2025-11-20 18:29:55 -03:00
|
|
|
|
console.warn(`Invalid section: ${section}, defaulting to chat`);
|
2025-11-20 16:29:10 -03:00
|
|
|
|
window.location.hash = "#chat";
|
|
|
|
|
|
switchSection("chat");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
switchSection(section);
|
|
|
|
|
|
}
|
2025-11-20 18:29:55 -03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Check if Alpine sections might be needed and wait for Alpine
|
|
|
|
|
|
const hash = window.location.hash.substring(1);
|
|
|
|
|
|
if (["drive", "tasks", "mail"].includes(hash)) {
|
|
|
|
|
|
console.log(`Waiting for Alpine to load for section: ${hash}`);
|
|
|
|
|
|
|
|
|
|
|
|
const waitForAlpine = () => {
|
|
|
|
|
|
if (window.Alpine) {
|
|
|
|
|
|
console.log("Alpine is ready");
|
|
|
|
|
|
setTimeout(initApp, 100);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log("Waiting for Alpine...");
|
|
|
|
|
|
setTimeout(waitForAlpine, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Also listen for alpine:init event
|
|
|
|
|
|
document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
console.log("Alpine initialized via event");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
waitForAlpine();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// For chat, don't need to wait for Alpine
|
|
|
|
|
|
setTimeout(initApp, 100);
|
|
|
|
|
|
}
|
2025-11-15 19:08:26 -03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Handle browser back/forward navigation
|
2025-11-20 14:35:23 -03:00
|
|
|
|
window.addEventListener("popstate", () => {
|
2025-11-20 16:29:10 -03:00
|
|
|
|
const section = getInitialSection();
|
|
|
|
|
|
// Ensure valid section
|
|
|
|
|
|
if (!sections[section]) {
|
|
|
|
|
|
window.location.hash = "#chat";
|
|
|
|
|
|
switchSection("chat");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
switchSection(section);
|
|
|
|
|
|
}
|
2025-11-15 19:08:26 -03:00
|
|
|
|
});
|
2025-11-20 16:02:48 -03:00
|
|
|
|
|
|
|
|
|
|
// Make switchSection globally accessible
|
|
|
|
|
|
window.switchSection = switchSection;
|