|
<script type="application/javascript"> |
|
window.addEventListener("load", async () => { |
|
const placeholder = document.querySelector(".children-placeholder"); |
|
|
|
// Check if the placeholder element exists |
|
if (!placeholder) { |
|
console.log("🛈 No .children-placeholder found."); |
|
return; |
|
} |
|
|
|
// Read configuration from data attributes |
|
const limit = parseInt(placeholder.getAttribute("data-limit") || "100", 10); |
|
const maxDepth = parseInt(placeholder.getAttribute("data-depth") || "1", 10); |
|
const sortAttr = placeholder.getAttribute("data-sort") || "path:asc"; |
|
const debug = placeholder.getAttribute("data-debug") === "true"; |
|
|
|
const [sortField, sortDirection] = sortAttr.split(":"); |
|
const sortAsc = sortDirection !== "desc"; |
|
|
|
const log = (...args) => debug && console.log(...args); |
|
|
|
// Parse the URL path to determine the base path and locale |
|
let fullPath = window.location.pathname; |
|
let [, locale, ...pathParts] = fullPath.split("/"); |
|
locale = locale || "de"; |
|
let path = pathParts.join("/").replace(/^\/+|\/+$/g, ""); |
|
const basePath = path ? `${path}/` : ""; |
|
|
|
log("🌍 Locale:", locale); |
|
log("🔍 Searching for subpages of path:", basePath); |
|
|
|
// Show loading message |
|
placeholder.innerHTML = "Loading subpages…"; |
|
|
|
// GraphQL query to fetch pages |
|
const query = { |
|
query: ` |
|
query ($query: String!, $locale: String!) { |
|
pages { |
|
search(query: $query, locale: $locale) { |
|
results { |
|
title |
|
path |
|
description |
|
} |
|
} |
|
} |
|
} |
|
`, |
|
variables: { |
|
query: basePath, |
|
locale: locale |
|
} |
|
}; |
|
|
|
try { |
|
// Send GraphQL query to server |
|
const response = await fetch("/graphql", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify(query) |
|
}); |
|
|
|
const json = await response.json(); |
|
|
|
// Check for errors in response |
|
if (!response.ok || json.errors) { |
|
throw new Error("GraphQL error: " + JSON.stringify(json.errors)); |
|
} |
|
|
|
const results = json?.data?.pages?.search?.results ?? []; |
|
|
|
log("📄 Found pages:", results.map(p => p.path)); |
|
|
|
// Filter and sort child pages |
|
const children = results |
|
.filter(p => p.path !== path) |
|
.filter(p => p.path.startsWith(path + "/")) |
|
.sort((a, b) => { |
|
const aVal = a[sortField]?.toLowerCase?.() || ""; |
|
const bVal = b[sortField]?.toLowerCase?.() || ""; |
|
if (aVal < bVal) return sortAsc ? -1 : 1; |
|
if (aVal > bVal) return sortAsc ? 1 : -1; |
|
return 0; |
|
}) |
|
.slice(0, limit); |
|
|
|
log("✅ Filtered & sorted subpages:", children.map(p => p.path)); |
|
|
|
// Show message if no children were found |
|
if (children.length === 0) { |
|
placeholder.innerHTML = "<em>No subpages available.</em>"; |
|
return; |
|
} |
|
|
|
// Build a tree structure from the page paths |
|
const tree = {}; |
|
children.forEach(page => { |
|
const relPath = page.path.slice(basePath.length).replace(/^\/+|\/+$/g, ""); |
|
const parts = relPath.split("/"); |
|
let node = tree; |
|
parts.forEach((part, idx) => { |
|
if (!node[part]) { |
|
node[part] = { __meta: null, __children: {} }; |
|
} |
|
if (idx === parts.length - 1) { |
|
node[part].__meta = page; |
|
} |
|
node = node[part].__children; |
|
}); |
|
}); |
|
|
|
// Escape HTML to prevent XSS |
|
function escapeHtml(str) { |
|
return str.replace(/[&<>"']/g, (m) => |
|
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[m]) |
|
); |
|
} |
|
|
|
// Recursively render the tree into a nested list |
|
function renderTree(treeObj, depth = 1) { |
|
if (depth > maxDepth) return null; |
|
const ul = document.createElement("ul"); |
|
ul.className = `children-tree level-${depth}`; |
|
|
|
for (const key of Object.keys(treeObj)) { |
|
const node = treeObj[key]; |
|
const hasChildren = Object.keys(node.__children).length > 0; |
|
const hasMeta = !!node.__meta; |
|
if (!hasMeta && !hasChildren) continue; |
|
|
|
const li = document.createElement("li"); |
|
li.className = "children-item"; |
|
|
|
if (hasMeta) { |
|
const p = node.__meta; |
|
li.innerHTML = `<a href="/${p.path}">${escapeHtml(p.title)}</a><br><small>${escapeHtml(p.description || "")}</small>`; |
|
} else { |
|
li.innerHTML = `<strong>${key}</strong>`; |
|
} |
|
|
|
const childList = renderTree(node.__children, depth + 1); |
|
if (childList) li.appendChild(childList); |
|
ul.appendChild(li); |
|
} |
|
|
|
return ul; |
|
} |
|
|
|
// Create the final HTML structure and replace the placeholder |
|
const wrapper = document.createElement("div"); |
|
wrapper.className = "children-list"; |
|
const treeHtml = renderTree(tree); |
|
if (treeHtml) wrapper.appendChild(treeHtml); |
|
|
|
placeholder.replaceWith(wrapper); |
|
log("🌲 Tree structure successfully rendered."); |
|
} catch (err) { |
|
console.error("❌ Error loading subpages:", err); |
|
placeholder.innerHTML = "<em>Error loading subpages.</em>"; |
|
} |
|
}); |
|
</script> |