Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions index.html

This file was deleted.

272 changes: 148 additions & 124 deletions src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -1,195 +1,219 @@
// src/dashboard/dashboard.js

// Hash route -> view file
const routes = {
const ROUTES = {
dashboard: "./dashboard/dashboard.html",
formular: "./formular/formular.html",
tables: "./tables/tables.html",
};

const DEFAULT_ROUTE = "dashboard";
const VIEW_ID = "view";
const SIDEBAR_ID = "sidebarNav";

const loadedScripts = new Set();

function getRouteFromHash() {
const raw = (location.hash || "#dashboard").replace("#", "").trim();
return routes[raw] ? raw : "dashboard";
/* =========================================================
Routing
========================================================= */

function getCurrentRoute() {
const hash = location.hash.replace("#", "").trim();
return ROUTES[hash] ? hash : DEFAULT_ROUTE;
}

function getBaseDir(filePath) {
const i = filePath.lastIndexOf("/");
return i >= 0 ? filePath.slice(0, i + 1) : "./";
function getRouteFile(route) {
return ROUTES[route];
}

function isAbsoluteUrl(url) {
return (
/^(https?:)?\/\//i.test(url) ||
url.startsWith("data:") ||
url.startsWith("blob:")
);
/* =========================================================
Path Utilities
========================================================= */

function getBaseDirectory(path) {
const index = path.lastIndexOf("/");
return index >= 0 ? path.slice(0, index + 1) : "./";
}

function isExternalUrl(url) {
return /^(https?:)?\/\//i.test(url);
}

function isRootAbsolute(url) {
function isRootPath(url) {
return url.startsWith("/");
}

function isSpecialUrl(url) {
return (
url.startsWith("#") ||
url.startsWith("mailto:") ||
url.startsWith("tel:") ||
url.startsWith("javascript:")
function isSpecialProtocol(url) {
return ["#", "mailto:", "tel:", "javascript:"].some((prefix) =>
url.startsWith(prefix),
);
}

function toResolvedPath(url, baseDir) {
// baseDir ist z. B. "./dashboard/"
// Dokument liegt bei /src/app.html -> daraus wird /src/dashboard/...
function shouldRewrite(url) {
if (!url) return false;
if (isExternalUrl(url)) return false;
if (isRootPath(url)) return false;
if (isSpecialProtocol(url)) return false;
if (url.startsWith("data:") || url.startsWith("blob:")) return false;
return true;
}

function resolvePath(url, baseDir) {
const base = new URL(baseDir, window.location.href);
const resolved = new URL(url, base);

// Nur path + query + hash zurückgeben (kein kompletter Origin nötig)
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
}

function rewriteRelativeAssets(containerEl, baseDir) {
const nodes = containerEl.querySelectorAll(
"link[href], script[src], img[src], source[src], iframe[src]",
/* =========================================================
Asset Handling
========================================================= */

function rewriteRelativeAssets(container, baseDir) {
const elements = container.querySelectorAll(
"link[href], script[src], img[src], source[src], iframe[src]"
);

nodes.forEach((el) => {
const attr = el.hasAttribute("href") ? "href" : "src";
const val = (el.getAttribute(attr) || "").trim();
elements.forEach((element) => {
const attribute = element.hasAttribute("href") ? "href" : "src";
const value = element.getAttribute(attribute)?.trim();

if (!val) {
return;
}
if (!shouldRewrite(value)) return;

// Absolute / Root / Sonderfälle nicht anfassen
if (isAbsoluteUrl(val) || isRootAbsolute(val) || isSpecialUrl(val)) {
return;
}

// ALLE relativen Pfade auflösen (nicht nur "./...")
const newVal = toResolvedPath(val, baseDir);
el.setAttribute(attr, newVal);
element.setAttribute(attribute, resolvePath(value, baseDir));
});
}

function runInlineScripts(containerEl) {
// Inline <script> inside innerHTML is NOT executed automatically.
const scripts = Array.from(containerEl.querySelectorAll("script")).filter(
(s) => !s.src,
);
/* =========================================================
Script Execution
========================================================= */

function executeInlineScripts(container) {
const scripts = [...container.querySelectorAll("script:not([src])")];

scripts.forEach((oldScript) => {
const newScript = document.createElement("script");

if (oldScript.type) {
newScript.type = oldScript.type;
}

newScript.type = oldScript.type || "text/javascript";
newScript.textContent = oldScript.textContent || "";
oldScript.replaceWith(newScript);
});
}

function runExternalScripts(containerEl) {
const scripts = Array.from(containerEl.querySelectorAll("script[src]"));
function loadExternalScript(src, type) {
if (!src || loadedScripts.has(src)) {
return Promise.resolve();
}

const loaders = scripts.map((oldScript) => {
const src = (oldScript.getAttribute("src") || "").trim();
loadedScripts.add(src);

if (!src) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.defer = true;
if (type) script.type = type;

// Schon geladen -> nicht nochmal laden
if (loadedScripts.has(src)) {
return Promise.resolve();
}
script.onload = resolve;
script.onerror = () =>
reject(new Error(`Failed to load script: ${src}`));

loadedScripts.add(src);
document.body.appendChild(script);
});
}

return new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = src;
s.defer = true;
function executeExternalScripts(container) {
const scripts = [...container.querySelectorAll("script[src]")];

if (oldScript.type) {
s.type = oldScript.type;
}
return Promise.all(
scripts.map((script) =>
loadExternalScript(
script.getAttribute("src")?.trim(),
script.type
)
)
);
}

s.onload = () => resolve();
s.onerror = () => reject(new Error(`Failed to load script: ${src}`));
/* =========================================================
View Rendering
========================================================= */

document.body.appendChild(s);
});
});
async function fetchView(file) {
const response = await fetch(file, { cache: "no-store" });

return Promise.all(loaders);
}
if (!response.ok) {
throw new Error(`Failed to load ${file} (HTTP ${response.status})`);
}

async function loadRoute(routeName) {
console.log("Loading route:", routeName);
return response.text();
}

const host = document.getElementById("view");
const file = routes[routeName];
function renderError(container, message) {
container.innerHTML = `
<div class="alert alert-danger mb-0">
${message}
</div>
`;
}

if (!host) {
console.error('Container with id="view" not found.');
return;
}
function updateSidebar(route) {
document
.querySelectorAll(`#${SIDEBAR_ID} .nav-link`)
.forEach((link) =>
link.classList.toggle("active", link.dataset.page === route)
);
}

console.log("Fetching view file:", file);
/* =========================================================
Main Route Loader
========================================================= */

const res = await fetch(file, { cache: "no-store" });
async function loadRoute(route) {
const container = document.getElementById(VIEW_ID);
if (!container) return;

if (!res.ok) {
host.innerHTML = `<div class="alert alert-danger mb-0">
Could not load <b>${file}</b> (HTTP ${res.status})
</div>`;
console.error("Fetch failed:", res.status, res.statusText);
return;
}
const file = getRouteFile(route);

host.innerHTML = await res.text();
console.log("View injected into #view");
try {
const html = await fetchView(file);
container.innerHTML = html;

const baseDir = getBaseDir(file);
const baseDir = getBaseDirectory(file);

// WICHTIG: relative Pfade im geladenen Partial korrigieren
rewriteRelativeAssets(host, baseDir);
rewriteRelativeAssets(container, baseDir);
executeInlineScripts(container);
await executeExternalScripts(container);

// Scripts aus dynamisch geladenem HTML ausführen
runInlineScripts(host);
await runExternalScripts(host);
if (route === "tables") {
window.tablesInit?.();
}

if (routeName === "tables") {
window.tablesInit?.();
updateSidebar(route);
} catch (error) {
renderError(container, error.message);
console.error(error);
}

// Sidebar active state
document.querySelectorAll("#sidebarNav .nav-link").forEach((a) => {
a.classList.toggle("active", a.dataset.page === routeName);
});
}

document.addEventListener("DOMContentLoaded", () => {
console.log("dashboard.js loaded");
/* =========================================================
Initialization
========================================================= */

loadRoute(getRouteFromHash());
function initRouting() {
loadRoute(getCurrentRoute());

window.addEventListener("hashchange", () => {
loadRoute(getRouteFromHash());
loadRoute(getCurrentRoute());
});

document.getElementById("sidebarNav")?.addEventListener("click", (e) => {
const link = e.target.closest("a[data-page]");
document
.getElementById(SIDEBAR_ID)
?.addEventListener("click", handleSidebarClick);
}

if (!link) {
return;
}
function handleSidebarClick(event) {
const link = event.target.closest("a[data-page]");
if (!link) return;

e.preventDefault();
location.hash = link.dataset.page;
});
});
event.preventDefault();
location.hash = link.dataset.page;
}

document.addEventListener("DOMContentLoaded", initRouting);
Loading