`;
};
+ // --- TABLE RENDERER (compatible marked 4.x ; en 5+ le token est un objet) ---
renderer.table = function (header, body) {
- return `
`;
+ const wrap = (html) => `
${html}
`;
+ if (typeof header === 'object' && header !== null) {
+ const token = header;
+ const thead = (typeof token.header === 'string') ? token.header : (Array.isArray(token.rows) ? token.rows[0] || '' : '');
+ const rows = Array.isArray(token.rows) ? token.rows.slice(1).join('') : (typeof body === 'string' ? body : '');
+ return wrap(`
`);
+ }
+ const h = typeof header === 'string' ? header : '';
+ const b = typeof body === 'string' ? body : '';
+ return wrap(`
`);
};
+ // --- EXTENSION POUR PROTÉGER LE LATEX DANS MARKED ---
+ const mathExtension = {
+ name: 'math',
+ level: 'inline',
+ start(src) {
+ const match = src.match(/\$/);
+ return match ? match.index : -1;
+ },
+ tokenizer(src, tokens) {
+ const rule = /^(\$\$[\s\S]+?\$\$|\$[\s\S]+?\$)/;
+ const match = rule.exec(src);
+ if (match) {
+ return {
+ type: 'math',
+ raw: match[0],
+ text: match[0]
+ };
+ }
+ },
+ renderer(token) {
+ return token.text;
+ }
+ };
+
+ marked.use({ extensions: [mathExtension] });
marked.use({ renderer });
marked.setOptions({ breaks: false, gfm: true });
// Safety check for CONFIG
let STATE = {
wikiData: {},
+ rootMdFiles: [],
+ wikiId: '',
contentCache: {},
searchIndex: [],
expandedSections: new Set(),
@@ -1121,6 +1310,13 @@
if (typeof CONFIG !== 'undefined') {
STATE.repo = CONFIG.repo;
STATE.branch = CONFIG.branch;
+ // Identifiant unique par repo pour éviter conflits de cache localStorage entre différentes GitHub Pages
+ const shortHash = (str) => {
+ let h = 0;
+ for (let i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i); h |= 0; }
+ return Math.abs(h).toString(36).slice(0, 12);
+ };
+ STATE.wikiId = (CONFIG.repo && shortHash(CONFIG.repo)) || '';
} else {
console.error("CRITICAL: CONFIG not loaded from wiki/config.js");
}
@@ -1148,7 +1344,8 @@
if (modalSearchInput) modalSearchInput.placeholder = CONFIG.ui.searchPlaceholder;
document.getElementById('label-changelog').innerText = CONFIG.ui.changelogTitle;
- document.getElementById('label-root-readme').innerText = CONFIG.ui.rootReadmeTitle;
+ const labelRootReadme = document.getElementById('label-root-readme');
+ if (labelRootReadme) labelRootReadme.innerText = CONFIG.ui.rootReadmeTitle;
document.getElementById('label-initializing').innerText = CONFIG.ui.initializingText;
document.getElementById('label-join-us').innerText = CONFIG.ui.joinUsTitle;
document.getElementById('label-on-this-page').innerText = CONFIG.ui.onThisPageTitle;
@@ -1164,15 +1361,14 @@
changelogBtn.style.display = CONFIG.features.showChangelog ? 'flex' : 'none';
}
- const rootReadmeBtn = document.getElementById('btn-root-readme');
- if (rootReadmeBtn) {
- rootReadmeBtn.style.display = CONFIG.features.showRootReadme ? 'flex' : 'none';
- }
-
const searchTrigger = document.querySelector('[onclick="openSearch()"]');
if (searchTrigger) {
searchTrigger.style.display = CONFIG.features.showSearch ? 'block' : 'none';
}
+ const searchBtnMobile = document.getElementById('search-btn-mobile');
+ if (searchBtnMobile) {
+ searchBtnMobile.style.display = CONFIG.features.showSearch ? '' : 'none';
+ }
const socialSection = document.getElementById('social-section');
if (socialSection) {
@@ -1219,7 +1415,7 @@
document.getElementById('app-name').content = CONFIG.projectName;
document.getElementById('ms-tile-color').content = CONFIG.themeColor || '#0B0C0E';
document.getElementById('theme-color-meta').content = CONFIG.themeColor || '#0B0C0E';
- document.getElementById('manifest-link').href = CONFIG.manifestPath || 'manifest.json';
+ document.getElementById('manifest-link').href = CONFIG.manifestPath || 'manifest.pwa.json';
renderCustomLinks();
renderFooter();
@@ -1410,29 +1606,43 @@
debugLog('[AcidWiki] 🚀 Initializing wiki...');
// Try GitHub API first for production
- let structure = await fetchWikiStructureFromAPI();
+ let apiResult = await fetchWikiStructureFromAPI();
+ let structure = apiResult && (apiResult.structure ?? apiResult);
+ const rootMdFiles = (apiResult && apiResult.rootMdFiles) || [];
// If API fails, try local filesystem scanning (for local HTTP servers)
- if (!structure) {
+ if (!structure && typeof structure !== 'object') {
debugLog('[AcidWiki] 🔄 Trying local filesystem scan...');
structure = await scanLocalFilesystem();
}
+ if (structure && typeof structure === 'object' && !Array.isArray(structure)) {
+ // When from scanLocalFilesystem we don't have rootMdFiles; keep []
+ } else {
+ structure = null;
+ }
if (!structure || Object.keys(structure).length === 0) {
if (CONFIG.features.showRootReadme) {
debugLog('[AcidWiki] ℹ️ No docs found, but root README is enabled. Proceeding...');
- structure = {}; // Empty but valid
+ structure = {};
} else {
console.error('[AcidWiki] ❌ No wiki content found!');
throw new Error("No wiki content found. Please add .md files to wiki/docs/");
}
}
- debugLog('[AcidWiki] ✅ Wiki structure loaded successfully');
- STATE.wikiData = structure;
+ STATE.rootMdFiles = rootMdFiles.length > 0 ? rootMdFiles : (CONFIG.features.showRootReadme ? [{ title: 'README.md'.replace(/\.md$/, '').replace(/_/g, ' '), filename: 'README.md' }] : []);
+ if (STATE.rootMdFiles.length > 0) {
+ const rootObj = Object.fromEntries(STATE.rootMdFiles.map(f => [f.title, f.filename]));
+ STATE.wikiData = { __root__: rootObj, ...structure };
+ STATE.expandedSections.add('__root__');
+ } else {
+ STATE.wikiData = structure;
+ }
const firstFolder = Object.keys(STATE.wikiData)[0];
if (firstFolder) STATE.expandedSections.add(firstFolder);
+ debugLog('[AcidWiki] ✅ Wiki structure loaded successfully');
renderSidebar();
buildSearchIndex();
@@ -1440,7 +1650,7 @@
window.onpopstate = (event) => {
if (event.state) {
if (event.state.page === 'versions') toggleVersionsPage(null, false);
- else loadContent(event.state.folder, event.state.title, event.state.filename, false);
+ else loadContent(event.state.folder, event.state.title, event.state.filename, false, event.state.folder === '');
} else {
handleInitialRoute();
}
@@ -1476,9 +1686,11 @@
const data = await res.json();
const structure = {};
let fileCount = 0;
+ const rootMdFiles = [];
data.tree.forEach(item => {
- if (item.path.startsWith('wiki/docs/') && item.type === 'blob' && item.path.endsWith('.md')) {
+ if (item.type !== 'blob' || !item.path.endsWith('.md')) return;
+ if (item.path.startsWith('wiki/docs/')) {
const relativePath = item.path.replace('wiki/docs/', '');
const parts = relativePath.split('/');
@@ -1493,9 +1705,20 @@
const title = decodeURIComponent(filename.replace(/\.md$/, '').replace(/_/g, ' '));
currentLevel[title] = filename;
fileCount++;
+ } else if (!item.path.includes('/')) {
+ const filename = item.path;
+ const title = decodeURIComponent(filename.replace(/\.md$/, '').replace(/_/g, ' '));
+ rootMdFiles.push({ title, filename });
}
});
+ rootMdFiles.sort((a, b) => {
+ if (a.filename.toLowerCase() === 'readme.md') return -1;
+ if (b.filename.toLowerCase() === 'readme.md') return 1;
+ return a.filename.localeCompare(b.filename);
+ });
+ if (rootMdFiles.length) debugLog(`[AcidWiki] 📂 Root .md files: ${rootMdFiles.map(f => f.filename).join(', ')}`);
+
// Sort folders and files recursively
function sortStructure(obj) {
const sorted = {};
@@ -1511,7 +1734,10 @@
const sortedStructure = sortStructure(structure);
debugLog(`[AcidWiki] ✅ GitHub discovery complete: Found ${fileCount} files`);
- return Object.keys(sortedStructure).length > 0 ? sortedStructure : null;
+ const hasStructure = Object.keys(sortedStructure).length > 0;
+ const hasRoot = rootMdFiles.length > 0;
+ if (!hasStructure && !hasRoot) return null;
+ return { structure: hasStructure ? sortedStructure : {}, rootMdFiles };
} catch (e) {
debugLog(`[AcidWiki] ❌ Error fetching branch "${branch}":`, e);
}
@@ -1563,8 +1789,6 @@
}
// Build expected URL prefix for files in the current folder
- // When scanning wiki/docs/, we expect links like /...path.../wiki/docs/file.md or /...path.../wiki/docs/folder/
- // When scanning wiki/docs/01_General/, we expect /...path.../wiki/docs/01_General/file.md
const baseDocsPath = '/wiki/docs/';
// Only process links that contain wiki/docs in their path
@@ -1581,8 +1805,6 @@
}
// Check if this is a direct child of our current folder
- // If we're scanning root (path=''), afterDocs should be like 'file.md' or 'folder/'
- // If we're scanning '01_General', afterDocs should start with '01_General/' and next segment is the child
const expectedPrefix = path ? `${path}/` : '';
if (path && !afterDocs.startsWith(expectedPrefix)) {
debugLog(`[AcidWiki] ⏭️ Skipped (not in current path "${path}"): afterDocs="${afterDocs}"`);
@@ -1678,6 +1900,14 @@
}
}
+ /** Convertit une URL virtuelle ?page=README_ik5745.md en nom réel README.md pour la recherche. */
+ function pageParamToRealForRoot(pageParam) {
+ if (!pageParam || !pageParam.endsWith('.md')) return pageParam;
+ const m = pageParam.match(/^(.+)_([a-z0-9]+)\.md$/);
+ if (m) return m[1] + '.md';
+ return pageParam;
+ }
+
function handleInitialRoute() {
const urlParams = new URLSearchParams(window.location.search);
const pageParam = urlParams.get('page');
@@ -1686,18 +1916,20 @@
toggleVersionsPage(null, false);
} else if (pageParam) {
const flatList = getFlatPageList();
- const found = flatList.find(item => `${item.folder}/${item.file}` === pageParam || item.file === pageParam);
+ const paramForRoot = pageParamToRealForRoot(pageParam);
+ const found = flatList.find(item => {
+ if (item.folder === '') return item.file === paramForRoot || item.file === pageParam;
+ return `${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('/');
+ loadContent(found.folder, found.title, found.file, false, found.folder === '');
+ if (found.folder === '') STATE.expandedSections.add('__root__');
+ const parts = found.folder.split('/').filter(Boolean);
let current = "";
parts.forEach(p => {
- if (p) {
- current = current ? `${current}/${p}` : p;
- STATE.expandedSections.add(current);
- }
+ current = current ? `${current}/${p}` : p;
+ STATE.expandedSections.add(current);
});
renderSidebar();
} else {
@@ -1708,11 +1940,13 @@
}
}
+ // AMÉLIORATION : Indexation via l'API GitHub Raw
async function buildSearchIndex() {
const promises = [];
function indexRecursive(data, currentPath = '') {
for (const [key, value] of Object.entries(data)) {
+ if (key === '__root__') continue;
if (typeof value === 'object' && value !== null) {
const folderPath = currentPath ? `${currentPath}/${key}` : key;
indexRecursive(value, folderPath);
@@ -1721,8 +1955,9 @@
const title = key;
const folder = currentPath;
+ // ICI : Utilisation de l'URL raw de GitHub
promises.push(
- fetch(`./wiki/docs/${folder}/${filename}`)
+ fetch(`https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/wiki/docs/${folder}/${filename}`)
.then(res => { if (!res.ok) return ''; return res.text(); })
.then(text => {
if (!text) return;
@@ -1740,26 +1975,26 @@
indexRecursive(STATE.wikiData);
- // 2. --- MANUAL INDEXING OF ROOT README ---
- if (CONFIG.features.showRootReadme) {
+ // 2. --- INDEXING ROOT .MD FILES (Project Home) ---
+ (STATE.rootMdFiles || []).forEach(({ title, filename }) => {
promises.push(
- fetch('README.md')
+ // ICI : Utilisation de l'URL raw de GitHub
+ fetch(`https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/${filename}`)
.then(res => { if (!res.ok) return ''; return res.text(); })
.then(text => {
if (!text) return;
- // On l'ajoute manuellement à l'index
STATE.searchIndex.push({
- folder: '', // Racine
- title: CONFIG.ui.rootReadmeTitle || "Project Home",
- filename: 'README.md',
+ folder: '',
+ title,
+ filename,
content: text.toLowerCase(),
- titleLower: (CONFIG.ui.rootReadmeTitle || "Project Home").toLowerCase()
+ titleLower: title.toLowerCase()
});
- debugLog("[AcidWiki] 🔍 Root README indexed.");
})
- .catch(e => console.warn("[AcidWiki] Failed to index README.md", e))
+ .catch(() => {})
);
- }
+ });
+ if (STATE.rootMdFiles && STATE.rootMdFiles.length) debugLog("[AcidWiki] 🔍 Root .md files indexed.");
// 3. --- CHANGELOG INDEXING (API) ---
if (CONFIG.features.showChangelog) {
@@ -1768,13 +2003,12 @@
.then(res => { if (!res.ok) return []; return res.json(); })
.then(data => {
if (!Array.isArray(data) || data.length === 0) return;
- // On concatène toutes les releases pour la recherche
const fullLog = data.map(r => `${r.tag_name} ${r.name || ''} ${r.body || ''}`).join(' ');
STATE.searchIndex.push({
- folder: 'SYSTEM', // Petit tag visuel
+ folder: 'SYSTEM',
title: CONFIG.ui.changelogTitle,
- filename: 'CHANGELOG_SPECIAL_ID', // ID unique pour le clic
+ filename: 'CHANGELOG_SPECIAL_ID',
content: fullLog.toLowerCase(),
titleLower: CONFIG.ui.changelogTitle.toLowerCase(),
isVirtual: true
@@ -1792,6 +2026,12 @@
const flatList = [];
function traverse(data, currentPath = '') {
for (const [key, value] of Object.entries(data)) {
+ if (key === '__root__' && typeof value === 'object' && value !== null) {
+ for (const [title, file] of Object.entries(value))
+ if (typeof file === 'string' && file.endsWith('.md'))
+ flatList.push({ folder: '', title, file });
+ continue;
+ }
if (typeof value === 'object' && value !== null) {
const folderPath = currentPath ? `${currentPath}/${key}` : key;
traverse(value, folderPath);
@@ -1805,14 +2045,10 @@
}
function loadDefault() {
- if (CONFIG.features.showRootReadme) {
- loadContent('', CONFIG.ui.rootReadmeTitle, 'README.md', true, true);
- return;
- }
const flatList = getFlatPageList();
if (flatList.length === 0) return;
const first = flatList[0];
- loadContent(first.folder, first.title, first.file, true);
+ loadContent(first.folder, first.title, first.file, true, first.folder === '');
}
function showErrorState(msg) {
@@ -1866,6 +2102,9 @@
let currentPath = "";
const folderParts = folder.split('/').filter(s => s);
+ if (folder === '' && (STATE.rootMdFiles || []).length > 0) {
+ breadcrumbParts.push(`
${CONFIG.ui.rootReadmeTitle || 'Project Home'}`);
+ }
segments.forEach((seg, i) => {
const partKey = folderParts[i];
currentPath = currentPath ? `${currentPath}/${partKey}` : partKey;
@@ -1920,8 +2159,8 @@
try {
let text;
- const cacheKey = `${folder}/${filename}`;
- const storageKey = `bjorn_content_${cacheKey}`;
+ const cacheKey = (isRoot && STATE.wikiId) ? `__root__${STATE.wikiId}/${filename}` : `${folder}/${filename}`;
+ const storageKey = `bjorn_content_${cacheKey.replace(/\//g, '_')}`;
const now = Date.now();
const TTL = 3600 * 1000 * 24; // 24 Hours Cache
@@ -1944,14 +2183,16 @@
}
}
- // 3. Network Fetch (Fallback)
+ // AMÉLIORATION : Network Fetch depuis l'API GitHub Raw
if (!text) {
try {
let path;
if (isRoot) {
- path = `./${filename}`;
+ path = `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/${filename}`;
} else {
- path = folder ? `./wiki/docs/${folder}/${filename}` : `./wiki/docs/${filename}`;
+ path = folder
+ ? `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/wiki/docs/${folder}/${filename}`
+ : `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/wiki/docs/${filename}`;
}
const res = await fetch(path);
@@ -1961,18 +2202,7 @@
throw new Error("404");
}
} catch (e) {
- // Try fallback if not root
- if (!isRoot) {
- const path2 = `./wiki/docs/${filename}`;
- const res2 = await fetch(path2);
- if (res2.ok) {
- text = await res2.text();
- } else {
- throw new Error(`Content not found.`);
- }
- } else {
- throw e;
- }
+ throw new Error(`Content not found. Make sure the branch ${STATE.branch} exists and contains the file.`);
}
// Save to caches
@@ -1988,6 +2218,17 @@
const cleanHTML = DOMPurify.sanitize(marked.parse(text));
viewer.innerHTML = cleanHTML;
+ // --- AJOUT : RENDU DES ÉQUATIONS LATEX (KaTeX) ---
+ renderMathInElement(viewer, {
+ delimiters: [
+ {left: '$$', right: '$$', display: true},
+ {left: '$', right: '$', display: false},
+ {left: '\\(', right: '\\)', display: false},
+ {left: '\\[', right: '\\]', display: true}
+ ],
+ throwOnError: false
+ });
+
// Update SEO Tags
const fullTitle = `${title} | ${CONFIG.projectName}`;
document.title = fullTitle;
@@ -2005,14 +2246,16 @@
renderPagination(folder, title);
if (pushHistory) {
- const newUrl = `?page=${folder}/${filename}`;
+ const newUrl = folder
+ ? `?page=${folder}/${filename}`
+ : (STATE.wikiId ? `?page=${filename.replace(/\.md$/, `_${STATE.wikiId}.md`)}` : `?page=${filename}`);
window.history.pushState({ folder, title, filename }, "", newUrl);
}
if (CONFIG.features.autoCollapseSidebar) {
- STATE.expandedSections = new Set([folder]);
+ STATE.expandedSections = new Set([folder || '__root__']);
} else {
- STATE.expandedSections.add(folder);
+ STATE.expandedSections.add(folder || '__root__');
}
renderSidebar();
@@ -2098,7 +2341,7 @@
return;
}
- const CACHE_KEY = `bjorn_upd_${folder}_${filename}`;
+ const CACHE_KEY = STATE.wikiId ? `bjorn_upd_${STATE.wikiId}_${folder}_${filename}`.replace(/\//g, '_') : `bjorn_upd_${folder}_${filename}`;
const now = Date.now();
const cached = localStorage.getItem(CACHE_KEY);
@@ -2129,18 +2372,23 @@
const idx = flatList.findIndex(item => item.folder === currentFolder && item.title === currentTitle);
[idx - 1, idx + 1].forEach(i => {
- if (flatList[i]) {
- const url = `./wiki/docs/${flatList[i].folder}/${flatList[i].file}`;
- const cacheKey = `${flatList[i].folder}/${flatList[i].file}`;
+ if (!flatList[i]) return;
+ const item = flatList[i];
+ const isRoot = item.folder === '';
+ const cacheKey = (isRoot && STATE.wikiId) ? `__root__${STATE.wikiId}/${item.file}` : `${item.folder}/${item.file}`;
+ const storageKey = `bjorn_content_${cacheKey.replace(/\//g, '_')}`;
+
+ // On vérifie le cache local pour éviter une requête inutile
+ if (localStorage.getItem(storageKey)) return;
+
+ // Préchargement via l'API raw
+ const url = isRoot
+ ? `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/${item.file}`
+ : `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/wiki/docs/${item.folder}/${item.file}`;
- // Check persistent cache first to avoid fetch
- const storageKey = `bjorn_content_${cacheKey}`;
- if (localStorage.getItem(storageKey)) return;
-
- fetch(url).then(r => r.text()).then(t => {
- STATE.contentCache[cacheKey] = t;
- }).catch(() => { });
- }
+ fetch(url).then(r => r.text()).then(t => {
+ STATE.contentCache[cacheKey] = t;
+ }).catch(() => { });
});
}
@@ -2231,7 +2479,7 @@
const btn = document.createElement('button');
btn.className = "pagination-card text-left p-4 rounded border border-hack-border hover:border-hack-green group transition-colors bg-hack-sidebar/50 hover:bg-hack-sidebar w-full flex flex-col items-start";
btn.innerHTML = `
Previous« ${prev.title}`;
- btn.onclick = () => loadContent(prev.folder, prev.title, prev.file);
+ btn.onclick = () => loadContent(prev.folder, prev.title, prev.file, true, prev.folder === '');
navContainer.appendChild(btn);
} else { navContainer.appendChild(document.createElement('div')); }
@@ -2240,7 +2488,7 @@
const btn = document.createElement('button');
btn.className = "pagination-card text-right p-4 rounded border border-hack-border hover:border-hack-green group transition-colors bg-hack-sidebar/50 hover:bg-hack-sidebar w-full flex flex-col items-end";
btn.innerHTML = `
Next${next.title} »`;
- btn.onclick = () => loadContent(next.folder, next.title, next.file);
+ btn.onclick = () => loadContent(next.folder, next.title, next.file, true, next.folder === '');
navContainer.appendChild(btn);
}
}
@@ -2398,8 +2646,40 @@
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 isRootSection = key === '__root__';
+ const isFolder = (typeof value === 'object' && value !== null) && !isRootSection;
+ const cleanName = isRootSection ? (CONFIG.ui.rootReadmeTitle || 'Project Home') : key.replace(/^\d+_/, '').replace(/_/g, ' ');
+
+ if (isRootSection && typeof value === 'object' && value !== null) {
+ const folderPath = '__root__';
+ if (searchResults && !searchResults['__root__']) return;
+
+ const group = document.createElement('div');
+ group.className = `nav-group mb-1 ${level > 0 ? 'ml-2' : ''}`;
+
+ 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);
+ };
+
+ 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);
+ return;
+ }
if (isFolder) {
const folderPath = currentPath ? `${currentPath}/${key}` : key;
@@ -2434,16 +2714,20 @@
hasContent = true;
const filename = value;
const link = document.createElement('a');
- const fullPath = currentPath ? `${currentPath}/${filename}` : filename;
+ const isRootFile = currentPath === '__root__';
+ const fullPath = isRootFile
+ ? (STATE.wikiId ? filename.replace(/\.md$/, `_${STATE.wikiId}.md`) : filename)
+ : (currentPath ? `${currentPath}/${filename}` : filename);
link.href = `?page=${fullPath}`;
- const isActive = STATE.currentFolder === currentPath && STATE.currentFilename === filename;
+ const linkFolder = isRootFile ? '' : currentPath;
+ const isActive = STATE.currentFolder === linkFolder && 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);
+ loadContent(linkFolder, key, filename, true, isRootFile);
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
link.classList.add('active');
};
@@ -2543,7 +2827,7 @@
${res.title}
- ${res.folder.replace(/_/g, ' ')}
+ ${(res.folder || (CONFIG.ui.rootReadmeTitle || 'Project Home')).replace(/_/g, ' ')}
${res.snippet ? `
${res.snippet}
` : ''}
@@ -2576,9 +2860,8 @@
toggleVersionsPage(null, true);
} else {
// Comportement normal avec HIGHLIGHT
- // On récupère la requête depuis l'input de recherche
const query = document.getElementById('modal-search-input').value.trim();
- loadContent(res.folder, res.title, res.filename, true, false, query);
+ loadContent(res.folder, res.title, res.filename, true, res.folder === '', query);
}
closeSearch();
}
@@ -2746,14 +3029,19 @@
const tocSidebar = document.getElementById('mobile-toc-sidebar');
const overlay = document.getElementById('overlay');
+ const kbMobileToggleBtn = document.getElementById('kb-mobile-toggle');
+ const kbMobileTocToggleBtn = document.getElementById('kb-mobile-toc-toggle');
+
function openMenu() {
sidebar.classList.remove('-translate-x-full');
closeTOC(); // Close TOC if open
overlay.classList.remove('hidden');
setTimeout(() => overlay.classList.remove('opacity-0'), 10);
+ kbMobileToggleBtn?.classList.add('kb-bottom-bar-btn-hidden');
}
function closeMenu() {
sidebar.classList.add('-translate-x-full');
+ kbMobileToggleBtn?.classList.remove('kb-bottom-bar-btn-hidden');
checkOverlay();
}
@@ -2766,9 +3054,11 @@
closeMenu(); // Close Menu if open
overlay.classList.remove('hidden');
setTimeout(() => overlay.classList.remove('opacity-0'), 10);
+ kbMobileTocToggleBtn?.classList.add('kb-bottom-bar-btn-hidden');
}
function closeTOC() {
tocSidebar.classList.add('translate-x-full');
+ kbMobileTocToggleBtn?.classList.remove('kb-bottom-bar-btn-hidden');
checkOverlay();
}
@@ -2783,8 +3073,9 @@
document.getElementById('menu-btn').onclick = openMenu;
document.getElementById('close-sidebar-btn').onclick = closeMenu;
document.getElementById('close-toc-btn').onclick = closeTOC;
- document.getElementById('toc-btn-mobile').onclick = openTOC;
- document.getElementById('toc-btn-desktop').onclick = openTOC;
+ document.getElementById('toc-btn-desktop')?.addEventListener('click', openTOC);
+ document.getElementById('kb-mobile-toggle')?.addEventListener('click', openMenu);
+ document.getElementById('kb-mobile-toc-toggle')?.addEventListener('click', openTOC);
// Unified overlay click closes whichever is open
overlay.onclick = () => {
@@ -2814,32 +3105,27 @@
const progressBar = document.getElementById('reading-progress-bar');
const scrollBtn = document.getElementById('scroll-top-btn');
- if (scrollContainer && progressBar) {
+ function updateScrollUI() {
+ if (!scrollContainer) return;
+ const scrollTop = scrollContainer.scrollTop;
+ const scrollHeight = scrollContainer.scrollHeight - scrollContainer.clientHeight;
+ const scrollPercent = (scrollHeight > 0) ? (scrollTop / scrollHeight) * 100 : 0;
+ if (progressBar) progressBar.style.width = scrollPercent + '%';
+ scrollBtn?.classList.toggle('visible', scrollTop > 200);
const breadcrumbContainer = document.getElementById('breadcrumb-sticky-container');
- scrollContainer.addEventListener('scroll', () => {
- const scrollTop = scrollContainer.scrollTop;
- const scrollHeight = scrollContainer.scrollHeight - scrollContainer.clientHeight;
- const scrollPercent = (scrollHeight > 0) ? (scrollTop / scrollHeight) * 100 : 0;
- progressBar.style.width = scrollPercent + '%';
-
- if (scrollTop > 300) scrollBtn.classList.remove('hidden');
- else scrollBtn.classList.add('hidden');
-
- if (breadcrumbContainer && CONFIG.features.stickyBreadcrumbs) {
- // Use a small threshold and requestAnimationFrame for smoothness
- if (scrollTop > 20) {
- breadcrumbContainer.classList.add('stuck');
- } else {
- breadcrumbContainer.classList.remove('stuck');
- }
- } else if (breadcrumbContainer) {
- breadcrumbContainer.classList.remove('stuck');
- }
-
- updateTOCActiveState();
- });
+ if (breadcrumbContainer && typeof CONFIG !== 'undefined' && CONFIG.features.stickyBreadcrumbs) {
+ breadcrumbContainer.classList.toggle('stuck', scrollTop > 20);
+ } else if (breadcrumbContainer) {
+ breadcrumbContainer.classList.remove('stuck');
+ }
+ if (typeof updateTOCActiveState === 'function') updateTOCActiveState();
}
- if (scrollBtn) {
+
+ if (scrollContainer) {
+ scrollContainer.addEventListener('scroll', updateScrollUI);
+ updateScrollUI();
+ }
+ if (scrollBtn && scrollContainer) {
scrollBtn.onclick = () => scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
}
}
@@ -2945,7 +3231,7 @@
// Mouse tracking for glow effects
document.addEventListener('mousemove', (e) => {
- const target = e.target.closest('.nav-link, .section-header, .copy-btn, .search-result-item, #menu-btn, #theme-toggle-desktop, #theme-toggle-mobile, .badge-sm, .pagination-card, #toc-btn-mobile, #toc-btn-desktop, #clear-highlight-btn');
+ const target = e.target.closest('.nav-link, .section-header, .copy-btn, .search-result-item, #menu-btn, #theme-toggle-desktop, #theme-toggle-mobile, .badge-sm, .pagination-card, #toc-btn-desktop, #search-btn-mobile, #clear-highlight-btn, .kb-mobile-toggle, .kb-mobile-toc-toggle, .kb-scroll-top-btn');
if (target) {
const rect = target.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
@@ -2958,4 +3244,4 @@