@@ -744,15 +761,18 @@
@@ -793,7 +813,10 @@
searchIndex: [],
expandedSections: new Set(),
repo: CONFIG.repo,
- branch: CONFIG.branch
+ branch: CONFIG.branch,
+ currentTitle: "",
+ currentFolder: "",
+ currentFilename: ""
};
// --- 1.5 INITIALIZE CONFIG ---
@@ -1074,20 +1097,24 @@
if (pageParam === 'changelog') {
toggleVersionsPage(null, false);
} else if (pageParam) {
- let found = false;
- for (const [folder, files] of Object.entries(STATE.wikiData)) {
- for (const [title, file] of Object.entries(files)) {
- if (`${folder}/${file}` === pageParam || file === pageParam) {
- loadContent(folder, title, file, false);
- STATE.expandedSections.add(folder);
- renderSidebar();
- found = true;
- break;
+ const flatList = getFlatPageList();
+ const found = flatList.find(item => `${item.folder}/${item.file}` === pageParam || item.file === pageParam);
+
+ if (found) {
+ loadContent(found.folder, found.title, found.file, false);
+ // Expand all parent folders
+ const parts = found.folder.split('/');
+ let current = "";
+ parts.forEach(p => {
+ if (p) {
+ current = current ? `${current}/${p}` : p;
+ STATE.expandedSections.add(current);
}
- }
- if (found) break;
+ });
+ renderSidebar();
+ } else {
+ loadDefault();
}
- if (!found) loadDefault();
} else {
loadDefault();
}
@@ -1095,37 +1122,59 @@
async function buildSearchIndex() {
const promises = [];
- for (const [folder, files] of Object.entries(STATE.wikiData)) {
- for (const [title, filename] of Object.entries(files)) {
- if (!filename.endsWith('.md')) continue;
- promises.push(
- fetch(`./wiki/${folder}/${filename}`)
- .then(res => { if (!res.ok) return ''; return res.text(); })
- .then(text => {
- if (!text) return;
- STATE.searchIndex.push({
- folder, title, filename,
- content: text.toLowerCase(),
- titleLower: title.toLowerCase()
- });
- })
- .catch(err => console.log("Indexing skip:", filename))
- );
+ function indexRecursive(data, currentPath = '') {
+ for (const [key, value] of Object.entries(data)) {
+ if (typeof value === 'object' && value !== null) {
+ const folderPath = currentPath ? `${currentPath}/${key}` : key;
+ indexRecursive(value, folderPath);
+ } else if (typeof value === 'string' && value.endsWith('.md')) {
+ const filename = value;
+ const title = key;
+ const folder = currentPath;
+
+ promises.push(
+ fetch(`./wiki/${folder}/${filename}`)
+ .then(res => { if (!res.ok) return ''; return res.text(); })
+ .then(text => {
+ if (!text) return;
+ STATE.searchIndex.push({
+ folder, title, filename,
+ content: text.toLowerCase(),
+ titleLower: title.toLowerCase()
+ });
+ })
+ .catch(err => console.log("Indexing skip:", filename))
+ );
+ }
}
}
+
+ indexRecursive(STATE.wikiData);
await Promise.all(promises);
}
- function loadDefault() {
- const folders = Object.keys(STATE.wikiData);
- if (folders.length === 0) return;
- const firstFolder = folders[0];
- const files = Object.keys(STATE.wikiData[firstFolder]);
- if (files.length === 0) return;
+ function getFlatPageList() {
+ const flatList = [];
+ function traverse(data, currentPath = '') {
+ for (const [key, value] of Object.entries(data)) {
+ if (typeof value === 'object' && value !== null) {
+ const folderPath = currentPath ? `${currentPath}/${key}` : key;
+ traverse(value, folderPath);
+ } else if (typeof value === 'string' && value.endsWith('.md')) {
+ flatList.push({ folder: currentPath, title: key, file: value });
+ }
+ }
+ }
+ traverse(STATE.wikiData);
+ return flatList;
+ }
- const firstTitle = files[0];
- loadContent(firstFolder, firstTitle, STATE.wikiData[firstFolder][firstTitle], true);
+ function loadDefault() {
+ const flatList = getFlatPageList();
+ if (flatList.length === 0) return;
+ const first = flatList[0];
+ loadContent(first.folder, first.title, first.file, true);
}
function showErrorState(msg) {
@@ -1155,11 +1204,63 @@
const pageNav = document.getElementById('page-nav');
const scrollContainer = document.getElementById('scroll-container');
- const cleanFolder = folder.replace(/^\d+_/, '').replace(/_/g, ' ');
- document.getElementById('breadcrumbs').innerHTML = `
-
wiki /
-
${cleanFolder} / ${title}
+ STATE.currentTitle = title;
+ STATE.currentFolder = folder;
+ STATE.currentFilename = filename;
+ document.getElementById('label-on-this-page').innerText = title;
+ document.getElementById('label-mobile-toc').innerText = title;
+
+ const flatList = getFlatPageList();
+ const idx = flatList.findIndex(item => item.folder === folder && item.title === title);
+ const prev = idx > 0 ? flatList[idx - 1] : null;
+ const next = idx < flatList.length - 1 ? flatList[idx + 1] : null;
+
+ const segments = folder.split('/').filter(s => s).map(s => s.replace(/^\d+_/, '').replace(/_/g, ' '));
+ const breadcrumbs = document.getElementById('breadcrumbs');
+
+ let breadcrumbParts = [];
+ breadcrumbParts.push(`
wiki`);
+
+ if (segments.length > 2) {
+ breadcrumbParts.push(`
...`);
+
+ // For the last folder segment, find its first page
+ const lastFolderKey = folder.split('/').pop();
+ const folderData = folder.split('/').reduce((obj, key) => obj[key], STATE.wikiData);
+ const firstPage = Object.entries(folderData).find(([k, v]) => typeof v === 'string');
+
+ if (firstPage) {
+ breadcrumbParts.push(`
${segments[segments.length - 1]}`);
+ } else {
+ breadcrumbParts.push(`
${segments[segments.length - 1]}`);
+ }
+ } else {
+ let currentPath = "";
+ const folderParts = folder.split('/').filter(s => s);
+
+ segments.forEach((seg, i) => {
+ const partKey = folderParts[i];
+ currentPath = currentPath ? `${currentPath}/${partKey}` : partKey;
+
+ // Find first page in this specific folder level
+ const folderData = currentPath.split('/').reduce((obj, key) => obj[key], STATE.wikiData);
+ const firstPage = Object.entries(folderData).find(([k, v]) => typeof v === 'string');
+
+ if (firstPage) {
+ breadcrumbParts.push(`
${seg}`);
+ } else {
+ breadcrumbParts.push(`
${seg}`);
+ }
+ });
+ }
+ breadcrumbParts.push(`
${title}`);
+
+ breadcrumbs.innerHTML = `
+ ${prev ? `
` : ''}
+ ${breadcrumbParts.join('
/ ')}
+ ${next ? `
` : ''}
`;
+ lucide.createIcons();
// Only show loader if not in RAM cache
if (!STATE.contentCache[`${folder}/${filename}`]) {
@@ -1303,12 +1404,7 @@
}
function preloadAdjacent(currentFolder, currentTitle) {
- let flatList = [];
- for (const [folder, files] of Object.entries(STATE.wikiData)) {
- for (const [title, file] of Object.entries(files)) {
- flatList.push({ folder, title, file });
- }
- }
+ const flatList = getFlatPageList();
const idx = flatList.findIndex(item => item.folder === currentFolder && item.title === currentTitle);
[idx - 1, idx + 1].forEach(i => {
@@ -1403,12 +1499,7 @@
function renderPagination(currentFolder, currentTitle) {
const navContainer = document.getElementById('page-nav');
- let flatList = [];
- for (const [folder, files] of Object.entries(STATE.wikiData)) {
- for (const [title, file] of Object.entries(files)) {
- flatList.push({ folder, title, file });
- }
- }
+ const flatList = getFlatPageList();
const idx = flatList.findIndex(item => item.folder === currentFolder && item.title === currentTitle);
if (idx === -1) return;
@@ -1440,8 +1531,18 @@
const createTOCItem = (h, isMobile) => {
const li = document.createElement('li');
const link = document.createElement('a');
- link.textContent = h.childNodes[0].textContent;
- link.href = `#${h.id}`;
+
+ // Extract text content excluding anchor links and other UI elements
+ let text = "";
+ h.childNodes.forEach(node => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ text += node.textContent;
+ } else if (node.nodeType === Node.ELEMENT_NODE && !node.classList.contains('anchor-link')) {
+ text += node.innerText || node.textContent;
+ }
+ });
+ link.textContent = text.trim();
+ link.href = h.id ? `#${h.id}` : '#';
// Indentation based on tag
let indentClass = '';
@@ -1457,8 +1558,13 @@
link.onclick = (e) => {
e.preventDefault();
if (isMobile) closeTOC();
- document.getElementById(h.id).scrollIntoView({ behavior: 'smooth' });
- history.pushState(null, null, `#${h.id}`);
+ if (h.id) {
+ document.getElementById(h.id).scrollIntoView({ behavior: 'smooth' });
+ history.pushState(null, null, `#${h.id}`);
+ } else {
+ document.getElementById('scroll-container').scrollTo({ top: 0, behavior: 'smooth' });
+ history.pushState(null, null, window.location.pathname + window.location.search);
+ }
};
li.appendChild(link);
return li;
@@ -1567,50 +1673,64 @@
container.innerHTML = '';
let hasContent = false;
- Object.keys(STATE.wikiData).forEach(folder => {
- if (searchResults && !searchResults[folder]) return;
+ function renderRecursive(data, parentContainer, currentPath = '', level = 0) {
+ Object.keys(data).forEach(key => {
+ const value = data[key];
+ const isFolder = typeof value === 'object' && value !== null;
+ const cleanName = key.replace(/^\d+_/, '').replace(/_/g, ' ');
- const cleanName = folder.replace(/^\d+_/, '').replace(/_/g, ' ');
- const group = document.createElement('div');
- group.className = 'nav-group mb-1';
+ if (isFolder) {
+ const folderPath = currentPath ? `${currentPath}/${key}` : key;
+ if (searchResults && !searchResults[key]) return;
- const btn = document.createElement('button');
- const isExpanded = STATE.expandedSections.has(folder) || searchResults;
- btn.className = `section-header w-full flex items-center justify-between px-2 py-2 text-gray-500 hover:text-hack-heading transition-colors rounded hover:bg-hack-bg focus:outline-none focus:bg-hack-bg ${isExpanded ? 'active' : ''}`;
- btn.innerHTML = `
-
${cleanName}
-
- `;
- btn.onclick = () => {
- if (STATE.expandedSections.has(folder)) STATE.expandedSections.delete(folder);
- else STATE.expandedSections.add(folder);
- renderSidebar(searchResults);
- };
+ const group = document.createElement('div');
+ group.className = `nav-group mb-1 ${level > 0 ? 'ml-2' : ''}`;
- const list = document.createElement('div');
- list.className = `nav-list pl-2 border-l border-hack-border/30 ml-1 ${isExpanded ? 'expanded' : 'collapsed'}`;
+ const btn = document.createElement('button');
+ const isExpanded = STATE.expandedSections.has(folderPath) || searchResults;
+ btn.className = `section-header w-full flex items-center justify-between px-2 py-2 text-gray-500 hover:text-hack-heading transition-colors rounded hover:bg-hack-bg focus:outline-none focus:bg-hack-bg ${isExpanded ? 'active' : ''}`;
+ btn.innerHTML = `
+
${cleanName}
+
+ `;
+ btn.onclick = () => {
+ if (STATE.expandedSections.has(folderPath)) STATE.expandedSections.delete(folderPath);
+ else STATE.expandedSections.add(folderPath);
+ renderSidebar(searchResults);
+ };
- Object.keys(STATE.wikiData[folder]).forEach(title => {
- if (searchResults && !searchResults[folder][title]) return;
- hasContent = true;
- const filename = STATE.wikiData[folder][title];
- const link = document.createElement('a');
- link.href = `?page=${folder}/${filename}`;
- link.className = 'nav-link group flex items-center gap-3 px-2 py-1.5 rounded-md text-sm font-medium text-gray-400 hover:text-hack-heading hover:bg-hack-bg transition-all outline-none focus:bg-hack-bg';
- link.innerHTML = `
${title}`;
- link.onclick = (e) => {
- e.preventDefault();
- loadContent(folder, title, filename);
- document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
- link.classList.add('active');
- };
- list.appendChild(link);
+ const list = document.createElement('div');
+ list.className = `nav-list ${isExpanded ? 'expanded' : 'collapsed'}`;
+
+ renderRecursive(value, list, folderPath, level + 1);
+
+ group.appendChild(btn);
+ group.appendChild(list);
+ parentContainer.appendChild(group);
+ } else {
+ if (searchResults && !searchResults[currentPath]?.[key]) return;
+ hasContent = true;
+ const filename = value;
+ const link = document.createElement('a');
+ const fullPath = currentPath ? `${currentPath}/${filename}` : filename;
+ link.href = `?page=${fullPath}`;
+
+ const isActive = STATE.currentFolder === currentPath && STATE.currentFilename === filename;
+ link.className = `nav-link group flex items-center gap-3 px-2 py-1.5 rounded-md text-sm font-medium text-gray-400 hover:text-hack-heading hover:bg-hack-bg transition-all outline-none focus:bg-hack-bg ${isActive ? 'active' : ''}`;
+
+ link.innerHTML = `
${key}`;
+ link.onclick = (e) => {
+ e.preventDefault();
+ loadContent(currentPath, key, filename);
+ document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
+ link.classList.add('active');
+ };
+ parentContainer.appendChild(link);
+ }
});
+ }
- group.appendChild(btn);
- group.appendChild(list);
- container.appendChild(group);
- });
+ renderRecursive(STATE.wikiData, container);
if (!hasContent && searchResults) noResults.classList.remove('hidden');
else noResults.classList.add('hidden');
@@ -1673,12 +1793,27 @@
async function performToggleVersionsPage(btn, pushHistory = true) {
const viewer = document.getElementById('markdown-viewer');
+
+ // Update state and UI labels
+ STATE.currentFolder = "";
+ STATE.currentFilename = "";
+ STATE.currentTitle = CONFIG.ui.changelogTitle;
+ document.getElementById('label-on-this-page').innerText = CONFIG.ui.changelogTitle;
+ document.getElementById('label-mobile-toc').innerText = CONFIG.ui.changelogTitle;
+
document.getElementById('breadcrumbs').innerHTML = `
${CONFIG.projectName} / ${CONFIG.ui.changelogTitle}`;
document.getElementById('page-nav').innerHTML = '';
document.getElementById('reading-time').innerHTML = '';
document.getElementById('last-updated').innerHTML = '';
viewer.innerHTML = `
${CONFIG.ui.changelogTitle}
${CONFIG.ui.fetchingReleasesText}
`;
+
+ // Update TOC with initial H1
+ enhanceMarkdownContent();
+ generateTOC();
+ // Update sidebar to clear active links
+ renderSidebar();
+
if (pushHistory) window.history.pushState({ page: 'versions' }, "", "?page=changelog");
try {
@@ -1704,8 +1839,15 @@
`;
list.appendChild(div);
});
+
+ // Update TOC with new H2s
+ enhanceMarkdownContent();
+ generateTOC();
+
} catch (e) {
document.getElementById('versions-list').innerHTML = `
Unable to load changelog. Check back later.
`;
+ enhanceMarkdownContent();
+ generateTOC();
}
}
diff --git a/wiki/02_Setup/Sub_Installation.md b/wiki/02_Setup/Sub_Installation.md
new file mode 100644
index 0000000..c0d25a1
--- /dev/null
+++ b/wiki/02_Setup/Sub_Installation.md
@@ -0,0 +1 @@
+# Test1
\ No newline at end of file
diff --git a/wiki/02_Setup/Sub_Sub_Installation.md b/wiki/02_Setup/Sub_Sub_Installation.md
new file mode 100644
index 0000000..c0d25a1
--- /dev/null
+++ b/wiki/02_Setup/Sub_Sub_Installation.md
@@ -0,0 +1 @@
+# Test1
\ No newline at end of file
diff --git a/wiki/03_Test_CSS/Long_Page.md b/wiki/03_Test_CSS/Long_Page.md
new file mode 100644
index 0000000..51962eb
--- /dev/null
+++ b/wiki/03_Test_CSS/Long_Page.md
@@ -0,0 +1,72 @@
+# Long Page Test CSS
+
+This page is designed to test the CSS rendering of various markdown elements, especially headings and long content.
+
+## Level 2 Heading
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+
+### Level 3 Heading
+
+Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+#### Level 4 Heading
+
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+
+##### Level 5 Heading
+
+Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+###### Level 6 Heading
+
+Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.
+
+## Lists and Formatting
+
+### Unordered List
+- Item 1
+- Item 2
+ - Sub-item 2.1
+ - Sub-item 2.2
+- Item 3
+
+### Ordered List
+1. First item
+2. Second item
+3. Third item
+
+### Blockquotes
+
+> This is a blockquote.
+> It can span multiple lines.
+> -- Someone Famous
+
+### Code Blocks
+
+```javascript
+function helloWorld() {
+ console.log("Hello, world!");
+}
+```
+
+Inline code: `const x = 10;`
+
+### Tables
+
+| Header 1 | Header 2 | Header 3 |
+|----------|----------|----------|
+| Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 |
+| Row 2 Col 1 | Row 2 Col 2 | Row 2 Col 3 |
+
+## More Content to Test Scrolling
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia ac.
+
+### Another H3
+
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.
+
+#### Another H4
+
+Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus.
diff --git a/wiki/03_Test_CSS/Sub_Category/Sub_Page.md b/wiki/03_Test_CSS/Sub_Category/Sub_Page.md
new file mode 100644
index 0000000..4a349df
--- /dev/null
+++ b/wiki/03_Test_CSS/Sub_Category/Sub_Page.md
@@ -0,0 +1,7 @@
+# Sub Page
+
+This is a sub page located in a sub-category to test nested navigation.
+
+## Content
+
+Testing nested sidebar levels.
diff --git a/wiki/03_Test_CSS/Sub_Category/Sub_Sub_Category/Sub_Sub_Page.md b/wiki/03_Test_CSS/Sub_Category/Sub_Sub_Category/Sub_Sub_Page.md
new file mode 100644
index 0000000..b906014
--- /dev/null
+++ b/wiki/03_Test_CSS/Sub_Category/Sub_Sub_Category/Sub_Sub_Page.md
@@ -0,0 +1,7 @@
+# Sub Sub Page
+
+This is a sub-sub page located in a sub-sub-category to test deep nested navigation.
+
+## Content
+
+Testing deep nested sidebar levels (3 levels deep).
diff --git a/wiki/structure.json b/wiki/structure.json
index e9a8001..8e73681 100644
--- a/wiki/structure.json
+++ b/wiki/structure.json
@@ -4,5 +4,14 @@
},
"02_Setup": {
"Installation": "Installation.md"
+ },
+ "03_Test_CSS": {
+ "Long Page": "Long_Page.md",
+ "Sub_Category": {
+ "Sub Page": "Sub_Page.md",
+ "Sub_Sub_Category": {
+ "Sub Sub Page": "Sub_Sub_Page.md"
+ }
+ }
}
}
\ No newline at end of file