newWebsite2.0
This commit is contained in:
378
article.html
Normal file
378
article.html
Normal file
@@ -0,0 +1,378 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BR0TCRAFT | Artikel</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Inter:wght@300;400;600;800&family=Orbitron:wght@700&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar">
|
||||
<div class="brand">BR0TCRAFT</div>
|
||||
<div class="nav-links">
|
||||
<a href="index.html">Home</a>
|
||||
<a href="projects.html">Projekte</a>
|
||||
<a href="tutorials.html" class="active">Dev Notes</a>
|
||||
<a href="about.html">About</a>
|
||||
<a href="assets.html">Assets</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-overlay" id="overlay"></div>
|
||||
|
||||
<button class="mobile-menu-btn" id="menuToggle">
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<div class="container-docs">
|
||||
<div class="docs-layout">
|
||||
|
||||
<aside class="docs-sidebar" id="sidebar">
|
||||
<button class="sidebar-toggle-desktop" id="sidebarCollapseBtn">
|
||||
<span>←</span> Sidebar einklappen
|
||||
</button>
|
||||
<div class="sidebar-group">
|
||||
<h4>Kapitel</h4>
|
||||
<div id="auto-toc"></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<button class="sidebar-collapsed-trigger" id="sidebarExpandBtn" title="Sidebar zeigen">
|
||||
→
|
||||
</button>
|
||||
|
||||
<main class="section">
|
||||
<div id="markdown-content">
|
||||
<p style="color: #444; font-family: var(--font-code); font-size: 0.85rem;">Lade Inhalt...</p>
|
||||
</div>
|
||||
|
||||
<div id="doc-nav" class="doc-pagination"></div>
|
||||
|
||||
<div style="margin-top: 4rem; border-top: 1px solid #1e1e1e; padding-top: 2rem; display: flex; gap: 0.75rem;">
|
||||
<a href="tutorials.html" class="btn">← Dev Notes</a>
|
||||
<a href="index.html" class="btn"
|
||||
style="background: transparent; border: 1px solid #222; color: var(--text-muted);">Home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const fileParam = urlParams.get('file');
|
||||
const docParam = urlParams.get('doc');
|
||||
const partParam = urlParams.get('part');
|
||||
const contentDiv = document.getElementById('markdown-content');
|
||||
const toc = document.getElementById('auto-toc');
|
||||
|
||||
let docManifest = null;
|
||||
|
||||
// --- 1. Tabs System ---
|
||||
function initTabs() {
|
||||
document.querySelectorAll('.code-tabs').forEach((group, gIdx) => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'tab-wrapper';
|
||||
|
||||
const headerRow = document.createElement('div');
|
||||
headerRow.className = 'tab-header-row';
|
||||
wrapper.appendChild(headerRow);
|
||||
|
||||
const children = Array.from(group.children);
|
||||
let tabCount = 0;
|
||||
|
||||
children.forEach((child) => {
|
||||
if (child.tagName.startsWith('H')) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'tab-trigger';
|
||||
btn.textContent = child.textContent;
|
||||
btn.dataset.target = `panel-${gIdx}-${tabCount}`;
|
||||
if (tabCount === 0) btn.classList.add('active');
|
||||
|
||||
btn.onclick = (e) => {
|
||||
wrapper.querySelectorAll('.tab-trigger').forEach(b => b.classList.remove('active'));
|
||||
wrapper.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
wrapper.querySelector(`#${e.target.dataset.target}`).classList.add('active');
|
||||
};
|
||||
headerRow.appendChild(btn);
|
||||
} else if (child.tagName === 'PRE') {
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'tab-panel';
|
||||
panel.id = `panel-${gIdx}-${tabCount}`;
|
||||
if (tabCount === 0) panel.classList.add('active');
|
||||
panel.appendChild(child.cloneNode(true));
|
||||
wrapper.appendChild(panel);
|
||||
tabCount++;
|
||||
}
|
||||
});
|
||||
group.parentNode.replaceChild(wrapper, group);
|
||||
});
|
||||
}
|
||||
|
||||
// --- 2. Code Headers & Copy Button ---
|
||||
function enhanceCodeBlocks() {
|
||||
hljs.highlightAll();
|
||||
document.querySelectorAll('pre code').forEach((block) => {
|
||||
const parent = block.parentElement;
|
||||
if (parent.querySelector('.code-header')) return;
|
||||
|
||||
let lang = "CODE";
|
||||
block.classList.forEach(c => { if (c.startsWith('language-')) lang = c.split('-')[1].toUpperCase(); });
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'code-header';
|
||||
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'copy-btn';
|
||||
copyBtn.innerHTML = `<span>Copy</span>`;
|
||||
copyBtn.onclick = () => {
|
||||
navigator.clipboard.writeText(block.innerText);
|
||||
copyBtn.innerHTML = `<span style="color:var(--accent-primary)">Copied!</span>`;
|
||||
setTimeout(() => copyBtn.innerHTML = `<span>Copy</span>`, 2000);
|
||||
};
|
||||
|
||||
header.innerHTML = `<span class="lang-tag">${lang}</span>`;
|
||||
header.appendChild(copyBtn);
|
||||
parent.insertBefore(header, block);
|
||||
});
|
||||
}
|
||||
|
||||
// --- 3. Sidebar Generators ---
|
||||
|
||||
// Auto-generate TOC from headers (for single-file mode)
|
||||
function generateAutoTOC() {
|
||||
const headers = contentDiv.querySelectorAll('h2, h3');
|
||||
headers.forEach(h => {
|
||||
const link = document.createElement('a');
|
||||
link.className = 'sidebar-link';
|
||||
link.href = '#' + h.id;
|
||||
link.textContent = h.textContent.replace('#', '').trim();
|
||||
if (h.tagName === 'H3') link.style.paddingLeft = '25px';
|
||||
toc.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
// Generate Sidebar from Document Manifest
|
||||
function renderDocSidebar(chapters, container, depth = 0) {
|
||||
chapters.forEach(chapter => {
|
||||
if (chapter.children) {
|
||||
const groupTitle = document.createElement('div');
|
||||
groupTitle.className = 'sidebar-category';
|
||||
groupTitle.textContent = chapter.title;
|
||||
groupTitle.style.paddingLeft = (depth * 15) + 'px';
|
||||
|
||||
const childGroup = document.createElement('div');
|
||||
childGroup.className = 'sidebar-child-group';
|
||||
|
||||
// Check if current part is in this group to keep it open
|
||||
const hasActiveChild = (list) => list.some(c => c.id === partParam || (c.children && hasActiveChild(c.children)));
|
||||
if (!hasActiveChild(chapter.children)) {
|
||||
// groupTitle.classList.add('collapsed');
|
||||
// childGroup.classList.add('collapsed');
|
||||
}
|
||||
|
||||
groupTitle.onclick = () => {
|
||||
groupTitle.classList.toggle('collapsed');
|
||||
childGroup.classList.toggle('collapsed');
|
||||
};
|
||||
|
||||
container.appendChild(groupTitle);
|
||||
container.appendChild(childGroup);
|
||||
renderDocSidebar(chapter.children, childGroup, depth + 1);
|
||||
} else {
|
||||
const link = document.createElement('a');
|
||||
link.className = 'sidebar-link';
|
||||
if (partParam === chapter.id || (!partParam && depth === 0 && chapters.indexOf(chapter) === 0)) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
link.href = `?doc=${docParam}&part=${chapter.id}`;
|
||||
link.textContent = chapter.title;
|
||||
link.style.paddingLeft = (depth * 15 + 15) + 'px';
|
||||
container.appendChild(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Anchor Links ---
|
||||
function addAnchorLinks() {
|
||||
contentDiv.querySelectorAll('h1, h2, h3').forEach(h => {
|
||||
const slug = h.textContent.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, '');
|
||||
h.id = slug;
|
||||
const a = document.createElement('a');
|
||||
a.className = 'anchor-link';
|
||||
a.href = '#' + slug;
|
||||
a.innerHTML = '#';
|
||||
h.insertBefore(a, h.firstChild);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Navigation ---
|
||||
const menuBtn = document.getElementById('menuToggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const overlay = document.getElementById('overlay');
|
||||
const docsLayout = document.querySelector('.docs-layout');
|
||||
|
||||
function toggleMenu() {
|
||||
menuBtn.classList.toggle('active');
|
||||
sidebar.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
}
|
||||
|
||||
menuBtn.addEventListener('click', toggleMenu);
|
||||
overlay.addEventListener('click', toggleMenu);
|
||||
|
||||
// Desktop Toggle
|
||||
document.getElementById('sidebarCollapseBtn')?.addEventListener('click', () => {
|
||||
docsLayout.classList.add('sidebar-collapsed');
|
||||
});
|
||||
|
||||
document.getElementById('sidebarExpandBtn')?.addEventListener('click', () => {
|
||||
docsLayout.classList.remove('sidebar-collapsed');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('sidebar-link') && window.innerWidth <= 1024) {
|
||||
toggleMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// --- Update page title ---
|
||||
function updateTitle() {
|
||||
const h1 = contentDiv.querySelector('h1');
|
||||
if (h1) {
|
||||
document.title = 'BR0TCRAFT | ' + h1.textContent.replace('#', '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Loading Logic ---
|
||||
|
||||
function loadMarkdown(path) {
|
||||
fetch(path)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('Datei nicht gefunden');
|
||||
return r.text();
|
||||
})
|
||||
.then(md => {
|
||||
contentDiv.innerHTML = marked.parse(md);
|
||||
updateTitle();
|
||||
addAnchorLinks();
|
||||
initTabs();
|
||||
enhanceCodeBlocks();
|
||||
if (!docParam) generateAutoTOC();
|
||||
window.scrollTo(0, 0);
|
||||
})
|
||||
.catch(() => {
|
||||
contentDiv.innerHTML = `<p style="color:#555; font-family:var(--font-code); font-size:0.85rem;">
|
||||
Inhalt konnte nicht geladen werden.
|
||||
</p>`;
|
||||
});
|
||||
}
|
||||
|
||||
// --- Navigation Footer ---
|
||||
function renderPagination() {
|
||||
if (!docManifest) return;
|
||||
|
||||
const nav = document.getElementById('doc-nav');
|
||||
nav.innerHTML = '';
|
||||
|
||||
// Flatten structure to get linear sequence
|
||||
const flat = [];
|
||||
const flatten = (list) => {
|
||||
list.forEach(item => {
|
||||
if (item.file) flat.push(item);
|
||||
if (item.children) flatten(item.children);
|
||||
});
|
||||
};
|
||||
flatten(docManifest.chapters);
|
||||
|
||||
const currentIndex = flat.findIndex(item => item.id === (partParam || flat[0].id));
|
||||
|
||||
if (currentIndex > 0) {
|
||||
const prev = flat[currentIndex - 1];
|
||||
const a = document.createElement('a');
|
||||
a.href = `?doc=${docParam}&part=${prev.id}`;
|
||||
a.className = 'nav-page-btn prev';
|
||||
a.innerHTML = `<span class="nav-label">Zurück</span><span class="nav-title">${prev.title}</span>`;
|
||||
nav.appendChild(a);
|
||||
} else {
|
||||
nav.appendChild(document.createElement('div')); // Spacer
|
||||
}
|
||||
|
||||
if (currentIndex < flat.length - 1) {
|
||||
const next = flat[currentIndex + 1];
|
||||
const a = document.createElement('a');
|
||||
a.href = `?doc=${docParam}&part=${next.id}`;
|
||||
a.className = 'nav-page-btn next';
|
||||
a.innerHTML = `<span class="nav-label">Weiter</span><span class="nav-title">${next.title}</span>`;
|
||||
nav.appendChild(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (docParam) {
|
||||
const manifestPath = `content/${docParam}/index.json`;
|
||||
fetch(manifestPath)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('Manifest nicht gefunden');
|
||||
return r.json();
|
||||
})
|
||||
.then(data => {
|
||||
docManifest = data;
|
||||
document.querySelector('#sidebar h4').textContent = data.title || "Kapitel";
|
||||
renderDocSidebar(data.chapters, toc);
|
||||
|
||||
let targetFile = null;
|
||||
const findPart = (list) => {
|
||||
for (const item of list) {
|
||||
if (item.id === partParam) { targetFile = item.file; break; }
|
||||
if (item.children) findPart(item.children);
|
||||
if (targetFile) break;
|
||||
}
|
||||
};
|
||||
|
||||
if (partParam) {
|
||||
findPart(data.chapters);
|
||||
} else {
|
||||
const first = data.chapters[0];
|
||||
targetFile = first.children ? first.children[0].file : first.file;
|
||||
}
|
||||
|
||||
if (targetFile) {
|
||||
loadMarkdown(`content/${docParam}/${targetFile}`);
|
||||
renderPagination();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
contentDiv.innerHTML = `<div style="background: rgba(255,77,77,0.05); border: 1px solid var(--accent-primary); padding: 2rem; border-radius: 12px; margin-top: 2rem;">
|
||||
<h3 style="color: var(--accent-primary); margin-top: 0;">Ladefehler</h3>
|
||||
<p style="color:#aaa; font-size:0.9rem;">Die Dokumentation <b>${docParam}</b> konnte nicht geladen werden.</p>
|
||||
<ul style="color:#777; font-size:0.8rem; margin-top: 1rem;">
|
||||
<li>Prüfe, ob du die Seite über einen <b>Webserver</b> aufrufst (nicht nur lokal öffnen).</li>
|
||||
<li>Prüfe, ob die Datei <code>content/${docParam}/index.json</code> existiert.</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
} else if (fileParam) {
|
||||
loadMarkdown(fileParam);
|
||||
} else {
|
||||
contentDiv.innerHTML = `<p style="color:#555; font-family:var(--font-code); font-size:0.85rem;">
|
||||
Kein Artikel angegeben.
|
||||
</p>`;
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user