2 Commits

Author SHA1 Message Date
Fabien POLLY
f8c26288ab Update fmt.Println message from 'Hello' to 'Goodbye' 2026-02-27 22:03:07 +01:00
Fabien POLLY
8fd63ce5b1 Change default branch from 'wiki' to 'main' 2026-02-27 21:56:35 +01:00
2 changed files with 440 additions and 154 deletions

View File

@@ -33,7 +33,7 @@
<meta name="msapplication-TileColor" id="ms-tile-color" content="#0B0C0E"> <meta name="msapplication-TileColor" id="ms-tile-color" content="#0B0C0E">
<meta name="theme-color" id="theme-color-meta" content="#0B0C0E"> <meta name="theme-color" id="theme-color-meta" content="#0B0C0E">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" id="manifest-link" href="wiki/manifest.json"> <link rel="manifest" id="manifest-link" href="wiki/manifest.pwa.json">
<script src="wiki/config.js"></script> <script src="wiki/config.js"></script>
@@ -43,7 +43,7 @@
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
@@ -51,6 +51,10 @@
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"> href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js"></script>
<link <link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"> rel="stylesheet">
@@ -174,9 +178,12 @@
#theme-toggle-mobile, #theme-toggle-mobile,
.badge-sm, .badge-sm,
.pagination-card, .pagination-card,
#toc-btn-mobile,
#toc-btn-desktop, #toc-btn-desktop,
#clear-highlight-btn { #search-btn-mobile,
#clear-highlight-btn,
.kb-mobile-toggle,
.kb-mobile-toc-toggle,
.kb-scroll-top-btn {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
@@ -190,9 +197,12 @@
#theme-toggle-mobile::after, #theme-toggle-mobile::after,
.badge-sm::after, .badge-sm::after,
.pagination-card::after, .pagination-card::after,
#toc-btn-mobile::after,
#toc-btn-desktop::after, #toc-btn-desktop::after,
#clear-highlight-btn::after { #search-btn-mobile::after,
#clear-highlight-btn::after,
.kb-mobile-toggle::after,
.kb-mobile-toc-toggle::after,
.kb-scroll-top-btn::after {
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -211,9 +221,12 @@
#theme-toggle-mobile:hover::after, #theme-toggle-mobile:hover::after,
.badge-sm:hover::after, .badge-sm:hover::after,
.pagination-card:hover::after, .pagination-card:hover::after,
#toc-btn-mobile:hover::after,
#toc-btn-desktop:hover::after, #toc-btn-desktop:hover::after,
#clear-highlight-btn:hover::after { #search-btn-mobile:hover::after,
#clear-highlight-btn:hover::after,
.kb-mobile-toggle:hover::after,
.kb-mobile-toc-toggle:hover::after,
.kb-scroll-top-btn:hover::after {
opacity: 1; opacity: 1;
} }
@@ -374,6 +387,29 @@
font-weight: 700; font-weight: 700;
} }
.markdown-body table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.markdown-body table th,
.markdown-body table td {
border: 1px solid var(--border-color);
padding: 0.5rem 0.75rem;
text-align: left;
}
.markdown-body table th {
font-weight: 600;
background: var(--accent-dim);
color: var(--text-heading);
}
.markdown-body table tr:hover td {
background: var(--accent-dim);
}
.markdown-body img { .markdown-body img {
max-width: 100%; max-width: 100%;
border-radius: 8px; border-radius: 8px;
@@ -385,6 +421,14 @@
border-color: var(--accent-green); border-color: var(--accent-green);
} }
/* --- KATEX STYLES --- */
.katex-display {
overflow-x: auto;
overflow-y: hidden;
padding: 1rem 0;
scrollbar-width: thin;
}
#lightbox { #lightbox {
transition: opacity 0.3s ease, visibility 0.3s; transition: opacity 0.3s ease, visibility 0.3s;
} }
@@ -496,14 +540,122 @@
opacity: 1; opacity: 1;
} }
#scroll-top-btn { /* Bottom bar: Menu | Scroll to top (center) | TOC — aligne les 3 boutons à la même hauteur */
.kb-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 1rem 1.5rem;
padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
padding-left: calc(1.5rem + env(safe-area-inset-left, 0px));
padding-right: calc(1.5rem + env(safe-area-inset-right, 0px));
display: flex;
align-items: center;
justify-content: space-between;
z-index: 80;
pointer-events: none;
}
.kb-bottom-bar-cell {
display: flex;
align-items: center;
justify-content: center;
min-width: 50px;
height: 50px;
pointer-events: auto;
}
.kb-bottom-bar-left {
justify-content: flex-start;
}
.kb-bottom-bar-center {
flex: 1;
justify-content: center;
}
.kb-bottom-bar-right {
justify-content: flex-end;
}
.kb-bottom-bar-btn {
width: 50px;
height: 50px;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: var(--bg-sidebar);
color: var(--text-main);
border: 1px solid var(--border-color);
border-radius: 50%;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: all 0.2s;
}
.kb-bottom-bar-btn:hover {
border-color: var(--accent-green);
color: var(--accent-green);
}
.kb-bottom-bar-btn.kb-bottom-bar-btn-hidden {
visibility: hidden;
pointer-events: none;
opacity: 0;
}
.kb-scroll-top-btn {
transition: opacity 0.3s, transform 0.3s; transition: opacity 0.3s, transform 0.3s;
} }
#scroll-top-btn.hidden { #scroll-top-btn:not(.visible) {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transform: translateY(20px); transform: translateY(8px);
}
#scroll-top-btn.visible {
opacity: 0.9;
pointer-events: auto;
transform: translateY(0);
}
/* Menu (gauche) : visible uniquement en mobile (sidebar masquée) */
.kb-mobile-toggle {
display: flex;
}
@media (min-width: 769px) {
.kb-bottom-bar-left .kb-mobile-toggle {
display: none !important;
}
.kb-bottom-bar-left {
min-width: 0;
width: 0;
overflow: hidden;
}
}
/* TOC (droite) : visible en mobile et quand la TOC sidebar n'est pas affichée (< xl) */
.kb-mobile-toc-toggle {
display: flex;
}
@media (min-width: 1280px) {
.kb-bottom-bar-right .kb-mobile-toc-toggle {
display: none !important;
}
.kb-bottom-bar-right {
min-width: 0;
width: 0;
overflow: hidden;
}
} }
/* --- CLEAR HIGHLIGHT BTN (NEW) --- */ /* --- CLEAR HIGHLIGHT BTN (NEW) --- */
@@ -582,7 +734,7 @@
#mobile-toc-sidebar, #mobile-toc-sidebar,
header, header,
#theme-toggle-desktop, #theme-toggle-desktop,
#scroll-top-btn, #kb-bottom-bar,
#reading-progress-bar, #reading-progress-bar,
#overlay, #overlay,
.copy-btn, .copy-btn,
@@ -852,9 +1004,9 @@
<span id="label-menu" class="text-xs font-mono font-bold uppercase tracking-wider">Menu</span> <span id="label-menu" class="text-xs font-mono font-bold uppercase tracking-wider">Menu</span>
<i data-lucide="menu" class="w-5 h-5 text-hack-green"></i> <i data-lucide="menu" class="w-5 h-5 text-hack-green"></i>
</button> </button>
<button id="toc-btn-mobile" class="text-gray-400 hover:text-hack-green p-2 transition-colors" <button id="search-btn-mobile" type="button" class="text-gray-400 hover:text-hack-green p-2 transition-colors"
aria-label="Toggle Table of Contents"> title="Search (Ctrl+K)" aria-label="Search" onclick="openSearch()">
<i data-lucide="hash" class="w-5 h-5"></i> <i data-lucide="search" class="w-5 h-5"></i>
</button> </button>
</div> </div>
</header> </header>
@@ -862,6 +1014,24 @@
<div id="overlay" class="fixed inset-0 bg-black/80 backdrop-blur-[2px] z-[65] hidden transition-opacity opacity-0"> <div id="overlay" class="fixed inset-0 bg-black/80 backdrop-blur-[2px] z-[65] hidden transition-opacity opacity-0">
</div> </div>
<div id="kb-bottom-bar" class="kb-bottom-bar">
<div class="kb-bottom-bar-cell kb-bottom-bar-left">
<button id="kb-mobile-toggle" type="button" class="kb-bottom-bar-btn kb-mobile-toggle" title="Menu" aria-label="Open Menu">
<i data-lucide="menu" class="w-5 h-5"></i>
</button>
</div>
<div class="kb-bottom-bar-cell kb-bottom-bar-center">
<button id="scroll-top-btn" type="button" class="kb-bottom-bar-btn kb-scroll-top-btn p-3 rounded-full bg-hack-greenDim border border-hack-border text-hack-green hover:bg-hack-green hover:text-white shadow-lg" title="Back to Top" aria-label="Back to Top">
<i data-lucide="arrow-up" class="w-5 h-5"></i>
</button>
</div>
<div class="kb-bottom-bar-cell kb-bottom-bar-right">
<button id="kb-mobile-toc-toggle" type="button" class="kb-bottom-bar-btn kb-mobile-toc-toggle" title="Table of Contents" aria-label="Toggle Table of Contents">
<i data-lucide="list" class="w-5 h-5"></i>
</button>
</div>
</div>
<aside id="sidebar" <aside id="sidebar"
class="sidebar-mobile fixed top-0 bottom-0 left-0 z-[70] w-[280px] bg-hack-sidebar border-r border-hack-border transform -translate-x-full md:translate-x-0 md:static md:h-full flex flex-col shadow-2xl md:shadow-none transition-transform duration-300"> class="sidebar-mobile fixed top-0 bottom-0 left-0 z-[70] w-[280px] bg-hack-sidebar border-r border-hack-border transform -translate-x-full md:translate-x-0 md:static md:h-full flex flex-col shadow-2xl md:shadow-none transition-transform duration-300">
@@ -913,18 +1083,6 @@
class="w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity"></i> class="w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity"></i>
</button> </button>
</div> </div>
<div class="nav-group mb-1">
<button id="btn-root-readme"
class="w-full text-left px-2 py-2 flex items-center justify-between text-gray-500 hover:text-hack-heading transition-colors cursor-pointer group rounded hover:bg-hack-bg focus:bg-hack-bg outline-none"
onclick="loadContent('', CONFIG.ui.rootReadmeTitle, 'README.md', true, true)" tabindex="0">
<div
class="flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest font-mono text-hack-green">
<i data-lucide="home" class="w-3 h-3"></i> <span id="label-root-readme">Project Home</span>
</div>
<i data-lucide="chevron-right"
class="w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity"></i>
</button>
</div>
</div> </div>
<nav class="pb-4 px-3 space-y-2 flex-none" id="nav-container"> <nav class="pb-4 px-3 space-y-2 flex-none" id="nav-container">
@@ -1036,12 +1194,6 @@
title="Clear Search Highlights" aria-label="Clear Search Highlights"> title="Clear Search Highlights" aria-label="Clear Search Highlights">
<i data-lucide="eraser" class="w-5 h-5"></i> <i data-lucide="eraser" class="w-5 h-5"></i>
</button> </button>
<button id="scroll-top-btn"
class="absolute bottom-6 right-6 p-3 rounded-full bg-hack-greenDim border border-hack-border text-hack-green hover:bg-hack-green hover:text-white shadow-lg hidden z-30"
title="Back to Top" aria-label="Back to Top">
<i data-lucide="arrow-up" class="w-5 h-5"></i>
</button>
</div> </div>
<div id="search-modal-overlay" <div id="search-modal-overlay"
@@ -1098,16 +1250,53 @@
return `<img src="${href}" alt="${text || ''}" title="${title || ''}" loading="lazy" class="rounded-lg border border-hack-border bg-black/20">`; return `<img src="${href}" alt="${text || ''}" title="${title || ''}" loading="lazy" class="rounded-lg border border-hack-border bg-black/20">`;
}; };
// --- TABLE RENDERER (compatible marked 4.x ; en 5+ le token est un objet) ---
renderer.table = function (header, body) { renderer.table = function (header, body) {
return `<div class="overflow-x-auto my-4 border border-hack-border rounded-lg"><table class="w-full text-left text-sm border-collapse"><thead>${header}</thead><tbody>${body}</tbody></table></div>`; const wrap = (html) => `<div class="overflow-x-auto my-4 border border-hack-border rounded-lg">${html}</div>`;
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(`<table class="w-full text-left text-sm border-collapse"><thead>${thead}</thead><tbody>${rows}</tbody></table>`);
}
const h = typeof header === 'string' ? header : '';
const b = typeof body === 'string' ? body : '';
return wrap(`<table class="w-full text-left text-sm border-collapse"><thead>${h}</thead><tbody>${b}</tbody></table>`);
}; };
// --- 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.use({ renderer });
marked.setOptions({ breaks: false, gfm: true }); marked.setOptions({ breaks: false, gfm: true });
// Safety check for CONFIG // Safety check for CONFIG
let STATE = { let STATE = {
wikiData: {}, wikiData: {},
rootMdFiles: [],
wikiId: '',
contentCache: {}, contentCache: {},
searchIndex: [], searchIndex: [],
expandedSections: new Set(), expandedSections: new Set(),
@@ -1121,6 +1310,13 @@
if (typeof CONFIG !== 'undefined') { if (typeof CONFIG !== 'undefined') {
STATE.repo = CONFIG.repo; STATE.repo = CONFIG.repo;
STATE.branch = CONFIG.branch; 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 { } else {
console.error("CRITICAL: CONFIG not loaded from wiki/config.js"); console.error("CRITICAL: CONFIG not loaded from wiki/config.js");
} }
@@ -1148,7 +1344,8 @@
if (modalSearchInput) modalSearchInput.placeholder = CONFIG.ui.searchPlaceholder; if (modalSearchInput) modalSearchInput.placeholder = CONFIG.ui.searchPlaceholder;
document.getElementById('label-changelog').innerText = CONFIG.ui.changelogTitle; 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-initializing').innerText = CONFIG.ui.initializingText;
document.getElementById('label-join-us').innerText = CONFIG.ui.joinUsTitle; document.getElementById('label-join-us').innerText = CONFIG.ui.joinUsTitle;
document.getElementById('label-on-this-page').innerText = CONFIG.ui.onThisPageTitle; document.getElementById('label-on-this-page').innerText = CONFIG.ui.onThisPageTitle;
@@ -1164,15 +1361,14 @@
changelogBtn.style.display = CONFIG.features.showChangelog ? 'flex' : 'none'; 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()"]'); const searchTrigger = document.querySelector('[onclick="openSearch()"]');
if (searchTrigger) { if (searchTrigger) {
searchTrigger.style.display = CONFIG.features.showSearch ? 'block' : 'none'; 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'); const socialSection = document.getElementById('social-section');
if (socialSection) { if (socialSection) {
@@ -1219,7 +1415,7 @@
document.getElementById('app-name').content = CONFIG.projectName; document.getElementById('app-name').content = CONFIG.projectName;
document.getElementById('ms-tile-color').content = CONFIG.themeColor || '#0B0C0E'; document.getElementById('ms-tile-color').content = CONFIG.themeColor || '#0B0C0E';
document.getElementById('theme-color-meta').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(); renderCustomLinks();
renderFooter(); renderFooter();
@@ -1410,29 +1606,43 @@
debugLog('[AcidWiki] 🚀 Initializing wiki...'); debugLog('[AcidWiki] 🚀 Initializing wiki...');
// Try GitHub API first for production // 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 API fails, try local filesystem scanning (for local HTTP servers)
if (!structure) { if (!structure && typeof structure !== 'object') {
debugLog('[AcidWiki] 🔄 Trying local filesystem scan...'); debugLog('[AcidWiki] 🔄 Trying local filesystem scan...');
structure = await scanLocalFilesystem(); 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 (!structure || Object.keys(structure).length === 0) {
if (CONFIG.features.showRootReadme) { if (CONFIG.features.showRootReadme) {
debugLog('[AcidWiki] No docs found, but root README is enabled. Proceeding...'); debugLog('[AcidWiki] No docs found, but root README is enabled. Proceeding...');
structure = {}; // Empty but valid structure = {};
} else { } else {
console.error('[AcidWiki] ❌ No wiki content found!'); console.error('[AcidWiki] ❌ No wiki content found!');
throw new Error("No wiki content found. Please add .md files to wiki/docs/"); throw new Error("No wiki content found. Please add .md files to wiki/docs/");
} }
} }
debugLog('[AcidWiki] ✅ Wiki structure loaded successfully'); 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; STATE.wikiData = structure;
}
const firstFolder = Object.keys(STATE.wikiData)[0]; const firstFolder = Object.keys(STATE.wikiData)[0];
if (firstFolder) STATE.expandedSections.add(firstFolder); if (firstFolder) STATE.expandedSections.add(firstFolder);
debugLog('[AcidWiki] ✅ Wiki structure loaded successfully');
renderSidebar(); renderSidebar();
buildSearchIndex(); buildSearchIndex();
@@ -1440,7 +1650,7 @@
window.onpopstate = (event) => { window.onpopstate = (event) => {
if (event.state) { if (event.state) {
if (event.state.page === 'versions') toggleVersionsPage(null, false); 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 { } else {
handleInitialRoute(); handleInitialRoute();
} }
@@ -1476,9 +1686,11 @@
const data = await res.json(); const data = await res.json();
const structure = {}; const structure = {};
let fileCount = 0; let fileCount = 0;
const rootMdFiles = [];
data.tree.forEach(item => { 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 relativePath = item.path.replace('wiki/docs/', '');
const parts = relativePath.split('/'); const parts = relativePath.split('/');
@@ -1493,9 +1705,20 @@
const title = decodeURIComponent(filename.replace(/\.md$/, '').replace(/_/g, ' ')); const title = decodeURIComponent(filename.replace(/\.md$/, '').replace(/_/g, ' '));
currentLevel[title] = filename; currentLevel[title] = filename;
fileCount++; 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 // Sort folders and files recursively
function sortStructure(obj) { function sortStructure(obj) {
const sorted = {}; const sorted = {};
@@ -1511,7 +1734,10 @@
const sortedStructure = sortStructure(structure); const sortedStructure = sortStructure(structure);
debugLog(`[AcidWiki] ✅ GitHub discovery complete: Found ${fileCount} files`); 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) { } catch (e) {
debugLog(`[AcidWiki] ❌ Error fetching branch "${branch}":`, e); debugLog(`[AcidWiki] ❌ Error fetching branch "${branch}":`, e);
} }
@@ -1563,8 +1789,6 @@
} }
// Build expected URL prefix for files in the current folder // 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/'; const baseDocsPath = '/wiki/docs/';
// Only process links that contain wiki/docs in their path // 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 // 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}/` : ''; const expectedPrefix = path ? `${path}/` : '';
if (path && !afterDocs.startsWith(expectedPrefix)) { if (path && !afterDocs.startsWith(expectedPrefix)) {
debugLog(`[AcidWiki] ⏭️ Skipped (not in current path "${path}"): afterDocs="${afterDocs}"`); 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() { function handleInitialRoute() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const pageParam = urlParams.get('page'); const pageParam = urlParams.get('page');
@@ -1686,18 +1916,20 @@
toggleVersionsPage(null, false); toggleVersionsPage(null, false);
} else if (pageParam) { } else if (pageParam) {
const flatList = getFlatPageList(); 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) { if (found) {
loadContent(found.folder, found.title, found.file, false); loadContent(found.folder, found.title, found.file, false, found.folder === '');
// Expand all parent folders if (found.folder === '') STATE.expandedSections.add('__root__');
const parts = found.folder.split('/'); const parts = found.folder.split('/').filter(Boolean);
let current = ""; let current = "";
parts.forEach(p => { parts.forEach(p => {
if (p) {
current = current ? `${current}/${p}` : p; current = current ? `${current}/${p}` : p;
STATE.expandedSections.add(current); STATE.expandedSections.add(current);
}
}); });
renderSidebar(); renderSidebar();
} else { } else {
@@ -1708,11 +1940,13 @@
} }
} }
// AMÉLIORATION : Indexation via l'API GitHub Raw
async function buildSearchIndex() { async function buildSearchIndex() {
const promises = []; const promises = [];
function indexRecursive(data, currentPath = '') { function indexRecursive(data, currentPath = '') {
for (const [key, value] of Object.entries(data)) { for (const [key, value] of Object.entries(data)) {
if (key === '__root__') continue;
if (typeof value === 'object' && value !== null) { if (typeof value === 'object' && value !== null) {
const folderPath = currentPath ? `${currentPath}/${key}` : key; const folderPath = currentPath ? `${currentPath}/${key}` : key;
indexRecursive(value, folderPath); indexRecursive(value, folderPath);
@@ -1721,8 +1955,9 @@
const title = key; const title = key;
const folder = currentPath; const folder = currentPath;
// ICI : Utilisation de l'URL raw de GitHub
promises.push( 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(res => { if (!res.ok) return ''; return res.text(); })
.then(text => { .then(text => {
if (!text) return; if (!text) return;
@@ -1740,26 +1975,26 @@
indexRecursive(STATE.wikiData); indexRecursive(STATE.wikiData);
// 2. --- MANUAL INDEXING OF ROOT README --- // 2. --- INDEXING ROOT .MD FILES (Project Home) ---
if (CONFIG.features.showRootReadme) { (STATE.rootMdFiles || []).forEach(({ title, filename }) => {
promises.push( 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(res => { if (!res.ok) return ''; return res.text(); })
.then(text => { .then(text => {
if (!text) return; if (!text) return;
// On l'ajoute manuellement à l'index
STATE.searchIndex.push({ STATE.searchIndex.push({
folder: '', // Racine folder: '',
title: CONFIG.ui.rootReadmeTitle || "Project Home", title,
filename: 'README.md', filename,
content: text.toLowerCase(), 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) --- // 3. --- CHANGELOG INDEXING (API) ---
if (CONFIG.features.showChangelog) { if (CONFIG.features.showChangelog) {
@@ -1768,13 +2003,12 @@
.then(res => { if (!res.ok) return []; return res.json(); }) .then(res => { if (!res.ok) return []; return res.json(); })
.then(data => { .then(data => {
if (!Array.isArray(data) || data.length === 0) return; 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(' '); const fullLog = data.map(r => `${r.tag_name} ${r.name || ''} ${r.body || ''}`).join(' ');
STATE.searchIndex.push({ STATE.searchIndex.push({
folder: 'SYSTEM', // Petit tag visuel folder: 'SYSTEM',
title: CONFIG.ui.changelogTitle, title: CONFIG.ui.changelogTitle,
filename: 'CHANGELOG_SPECIAL_ID', // ID unique pour le clic filename: 'CHANGELOG_SPECIAL_ID',
content: fullLog.toLowerCase(), content: fullLog.toLowerCase(),
titleLower: CONFIG.ui.changelogTitle.toLowerCase(), titleLower: CONFIG.ui.changelogTitle.toLowerCase(),
isVirtual: true isVirtual: true
@@ -1792,6 +2026,12 @@
const flatList = []; const flatList = [];
function traverse(data, currentPath = '') { function traverse(data, currentPath = '') {
for (const [key, value] of Object.entries(data)) { 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) { if (typeof value === 'object' && value !== null) {
const folderPath = currentPath ? `${currentPath}/${key}` : key; const folderPath = currentPath ? `${currentPath}/${key}` : key;
traverse(value, folderPath); traverse(value, folderPath);
@@ -1805,14 +2045,10 @@
} }
function loadDefault() { function loadDefault() {
if (CONFIG.features.showRootReadme) {
loadContent('', CONFIG.ui.rootReadmeTitle, 'README.md', true, true);
return;
}
const flatList = getFlatPageList(); const flatList = getFlatPageList();
if (flatList.length === 0) return; if (flatList.length === 0) return;
const first = flatList[0]; 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) { function showErrorState(msg) {
@@ -1866,6 +2102,9 @@
let currentPath = ""; let currentPath = "";
const folderParts = folder.split('/').filter(s => s); const folderParts = folder.split('/').filter(s => s);
if (folder === '' && (STATE.rootMdFiles || []).length > 0) {
breadcrumbParts.push(`<span class="hover:text-hack-heading cursor-pointer" onclick="loadDefault()">${CONFIG.ui.rootReadmeTitle || 'Project Home'}</span>`);
}
segments.forEach((seg, i) => { segments.forEach((seg, i) => {
const partKey = folderParts[i]; const partKey = folderParts[i];
currentPath = currentPath ? `${currentPath}/${partKey}` : partKey; currentPath = currentPath ? `${currentPath}/${partKey}` : partKey;
@@ -1920,8 +2159,8 @@
try { try {
let text; let text;
const cacheKey = `${folder}/${filename}`; const cacheKey = (isRoot && STATE.wikiId) ? `__root__${STATE.wikiId}/${filename}` : `${folder}/${filename}`;
const storageKey = `bjorn_content_${cacheKey}`; const storageKey = `bjorn_content_${cacheKey.replace(/\//g, '_')}`;
const now = Date.now(); const now = Date.now();
const TTL = 3600 * 1000 * 24; // 24 Hours Cache 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) { if (!text) {
try { try {
let path; let path;
if (isRoot) { if (isRoot) {
path = `./${filename}`; path = `https://raw.githubusercontent.com/${STATE.repo}/${STATE.branch}/${filename}`;
} else { } 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); const res = await fetch(path);
@@ -1961,18 +2202,7 @@
throw new Error("404"); throw new Error("404");
} }
} catch (e) { } catch (e) {
// Try fallback if not root throw new Error(`Content not found. Make sure the branch ${STATE.branch} exists and contains the file.`);
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;
}
} }
// Save to caches // Save to caches
@@ -1988,6 +2218,17 @@
const cleanHTML = DOMPurify.sanitize(marked.parse(text)); const cleanHTML = DOMPurify.sanitize(marked.parse(text));
viewer.innerHTML = cleanHTML; 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 // Update SEO Tags
const fullTitle = `${title} | ${CONFIG.projectName}`; const fullTitle = `${title} | ${CONFIG.projectName}`;
document.title = fullTitle; document.title = fullTitle;
@@ -2005,14 +2246,16 @@
renderPagination(folder, title); renderPagination(folder, title);
if (pushHistory) { 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); window.history.pushState({ folder, title, filename }, "", newUrl);
} }
if (CONFIG.features.autoCollapseSidebar) { if (CONFIG.features.autoCollapseSidebar) {
STATE.expandedSections = new Set([folder]); STATE.expandedSections = new Set([folder || '__root__']);
} else { } else {
STATE.expandedSections.add(folder); STATE.expandedSections.add(folder || '__root__');
} }
renderSidebar(); renderSidebar();
@@ -2098,7 +2341,7 @@
return; 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 now = Date.now();
const cached = localStorage.getItem(CACHE_KEY); const cached = localStorage.getItem(CACHE_KEY);
@@ -2129,18 +2372,23 @@
const idx = flatList.findIndex(item => item.folder === currentFolder && item.title === currentTitle); const idx = flatList.findIndex(item => item.folder === currentFolder && item.title === currentTitle);
[idx - 1, idx + 1].forEach(i => { [idx - 1, idx + 1].forEach(i => {
if (flatList[i]) { if (!flatList[i]) return;
const url = `./wiki/docs/${flatList[i].folder}/${flatList[i].file}`; const item = flatList[i];
const cacheKey = `${flatList[i].folder}/${flatList[i].file}`; 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, '_')}`;
// Check persistent cache first to avoid fetch // On vérifie le cache local pour éviter une requête inutile
const storageKey = `bjorn_content_${cacheKey}`;
if (localStorage.getItem(storageKey)) return; 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}`;
fetch(url).then(r => r.text()).then(t => { fetch(url).then(r => r.text()).then(t => {
STATE.contentCache[cacheKey] = t; STATE.contentCache[cacheKey] = t;
}).catch(() => { }); }).catch(() => { });
}
}); });
} }
@@ -2231,7 +2479,7 @@
const btn = document.createElement('button'); 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.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 = `<span class="text-[10px] text-gray-500 uppercase tracking-widest mb-1 group-hover:text-hack-green">Previous</span><span class="font-bold text-sm truncate text-hack-heading w-full">« ${prev.title}</span>`; btn.innerHTML = `<span class="text-[10px] text-gray-500 uppercase tracking-widest mb-1 group-hover:text-hack-green">Previous</span><span class="font-bold text-sm truncate text-hack-heading w-full">« ${prev.title}</span>`;
btn.onclick = () => loadContent(prev.folder, prev.title, prev.file); btn.onclick = () => loadContent(prev.folder, prev.title, prev.file, true, prev.folder === '');
navContainer.appendChild(btn); navContainer.appendChild(btn);
} else { navContainer.appendChild(document.createElement('div')); } } else { navContainer.appendChild(document.createElement('div')); }
@@ -2240,7 +2488,7 @@
const btn = document.createElement('button'); 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.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 = `<span class="text-[10px] text-gray-500 uppercase tracking-widest mb-1 group-hover:text-hack-green">Next</span><span class="font-bold text-sm truncate text-hack-heading w-full">${next.title} »</span>`; btn.innerHTML = `<span class="text-[10px] text-gray-500 uppercase tracking-widest mb-1 group-hover:text-hack-green">Next</span><span class="font-bold text-sm truncate text-hack-heading w-full">${next.title} »</span>`;
btn.onclick = () => loadContent(next.folder, next.title, next.file); btn.onclick = () => loadContent(next.folder, next.title, next.file, true, next.folder === '');
navContainer.appendChild(btn); navContainer.appendChild(btn);
} }
} }
@@ -2398,8 +2646,40 @@
function renderRecursive(data, parentContainer, currentPath = '', level = 0) { function renderRecursive(data, parentContainer, currentPath = '', level = 0) {
Object.keys(data).forEach(key => { Object.keys(data).forEach(key => {
const value = data[key]; const value = data[key];
const isFolder = typeof value === 'object' && value !== null; const isRootSection = key === '__root__';
const cleanName = key.replace(/^\d+_/, '').replace(/_/g, ' '); 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 = `
<span class="flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest font-mono text-hack-green"><i data-lucide="home" class="w-3 h-3"></i> ${cleanName}</span>
<i data-lucide="chevron-right" class="section-arrow w-3 h-3 transition-transform"></i>
`;
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) { if (isFolder) {
const folderPath = currentPath ? `${currentPath}/${key}` : key; const folderPath = currentPath ? `${currentPath}/${key}` : key;
@@ -2434,16 +2714,20 @@
hasContent = true; hasContent = true;
const filename = value; const filename = value;
const link = document.createElement('a'); 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}`; 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.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 = `<span class="w-1.5 h-1.5 rounded-full bg-hack-border group-hover:bg-hack-green flex-shrink-0 transition-colors"></span> ${key}`; link.innerHTML = `<span class="w-1.5 h-1.5 rounded-full bg-hack-border group-hover:bg-hack-green flex-shrink-0 transition-colors"></span> ${key}`;
link.onclick = (e) => { link.onclick = (e) => {
e.preventDefault(); e.preventDefault();
loadContent(currentPath, key, filename); loadContent(linkFolder, key, filename, true, isRootFile);
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
link.classList.add('active'); link.classList.add('active');
}; };
@@ -2543,7 +2827,7 @@
<div class="search-result-item p-3 rounded-xl cursor-pointer flex flex-col gap-1" onclick="selectSearchResult(${i})" data-index="${i}"> <div class="search-result-item p-3 rounded-xl cursor-pointer flex flex-col gap-1" onclick="selectSearchResult(${i})" data-index="${i}">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-hack-heading font-bold text-sm">${res.title}</span> <span class="text-hack-heading font-bold text-sm">${res.title}</span>
<span class="text-[10px] text-gray-500 font-mono opacity-50 uppercase">${res.folder.replace(/_/g, ' ')}</span> <span class="text-[10px] text-gray-500 font-mono opacity-50 uppercase">${(res.folder || (CONFIG.ui.rootReadmeTitle || 'Project Home')).replace(/_/g, ' ')}</span>
</div> </div>
${res.snippet ? `<div class="text-xs text-gray-400 line-clamp-2 font-sans leading-relaxed">${res.snippet}</div>` : ''} ${res.snippet ? `<div class="text-xs text-gray-400 line-clamp-2 font-sans leading-relaxed">${res.snippet}</div>` : ''}
</div> </div>
@@ -2576,9 +2860,8 @@
toggleVersionsPage(null, true); toggleVersionsPage(null, true);
} else { } else {
// Comportement normal avec HIGHLIGHT // 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(); 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(); closeSearch();
} }
@@ -2746,14 +3029,19 @@
const tocSidebar = document.getElementById('mobile-toc-sidebar'); const tocSidebar = document.getElementById('mobile-toc-sidebar');
const overlay = document.getElementById('overlay'); const overlay = document.getElementById('overlay');
const kbMobileToggleBtn = document.getElementById('kb-mobile-toggle');
const kbMobileTocToggleBtn = document.getElementById('kb-mobile-toc-toggle');
function openMenu() { function openMenu() {
sidebar.classList.remove('-translate-x-full'); sidebar.classList.remove('-translate-x-full');
closeTOC(); // Close TOC if open closeTOC(); // Close TOC if open
overlay.classList.remove('hidden'); overlay.classList.remove('hidden');
setTimeout(() => overlay.classList.remove('opacity-0'), 10); setTimeout(() => overlay.classList.remove('opacity-0'), 10);
kbMobileToggleBtn?.classList.add('kb-bottom-bar-btn-hidden');
} }
function closeMenu() { function closeMenu() {
sidebar.classList.add('-translate-x-full'); sidebar.classList.add('-translate-x-full');
kbMobileToggleBtn?.classList.remove('kb-bottom-bar-btn-hidden');
checkOverlay(); checkOverlay();
} }
@@ -2766,9 +3054,11 @@
closeMenu(); // Close Menu if open closeMenu(); // Close Menu if open
overlay.classList.remove('hidden'); overlay.classList.remove('hidden');
setTimeout(() => overlay.classList.remove('opacity-0'), 10); setTimeout(() => overlay.classList.remove('opacity-0'), 10);
kbMobileTocToggleBtn?.classList.add('kb-bottom-bar-btn-hidden');
} }
function closeTOC() { function closeTOC() {
tocSidebar.classList.add('translate-x-full'); tocSidebar.classList.add('translate-x-full');
kbMobileTocToggleBtn?.classList.remove('kb-bottom-bar-btn-hidden');
checkOverlay(); checkOverlay();
} }
@@ -2783,8 +3073,9 @@
document.getElementById('menu-btn').onclick = openMenu; document.getElementById('menu-btn').onclick = openMenu;
document.getElementById('close-sidebar-btn').onclick = closeMenu; document.getElementById('close-sidebar-btn').onclick = closeMenu;
document.getElementById('close-toc-btn').onclick = closeTOC; document.getElementById('close-toc-btn').onclick = closeTOC;
document.getElementById('toc-btn-mobile').onclick = openTOC; document.getElementById('toc-btn-desktop')?.addEventListener('click', openTOC);
document.getElementById('toc-btn-desktop').onclick = 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 // Unified overlay click closes whichever is open
overlay.onclick = () => { overlay.onclick = () => {
@@ -2814,32 +3105,27 @@
const progressBar = document.getElementById('reading-progress-bar'); const progressBar = document.getElementById('reading-progress-bar');
const scrollBtn = document.getElementById('scroll-top-btn'); const scrollBtn = document.getElementById('scroll-top-btn');
if (scrollContainer && progressBar) { function updateScrollUI() {
const breadcrumbContainer = document.getElementById('breadcrumb-sticky-container'); if (!scrollContainer) return;
scrollContainer.addEventListener('scroll', () => {
const scrollTop = scrollContainer.scrollTop; const scrollTop = scrollContainer.scrollTop;
const scrollHeight = scrollContainer.scrollHeight - scrollContainer.clientHeight; const scrollHeight = scrollContainer.scrollHeight - scrollContainer.clientHeight;
const scrollPercent = (scrollHeight > 0) ? (scrollTop / scrollHeight) * 100 : 0; const scrollPercent = (scrollHeight > 0) ? (scrollTop / scrollHeight) * 100 : 0;
progressBar.style.width = scrollPercent + '%'; if (progressBar) progressBar.style.width = scrollPercent + '%';
scrollBtn?.classList.toggle('visible', scrollTop > 200);
if (scrollTop > 300) scrollBtn.classList.remove('hidden'); const breadcrumbContainer = document.getElementById('breadcrumb-sticky-container');
else scrollBtn.classList.add('hidden'); if (breadcrumbContainer && typeof CONFIG !== 'undefined' && CONFIG.features.stickyBreadcrumbs) {
breadcrumbContainer.classList.toggle('stuck', scrollTop > 20);
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) { } else if (breadcrumbContainer) {
breadcrumbContainer.classList.remove('stuck'); breadcrumbContainer.classList.remove('stuck');
} }
if (typeof updateTOCActiveState === 'function') updateTOCActiveState();
updateTOCActiveState();
});
} }
if (scrollBtn) {
if (scrollContainer) {
scrollContainer.addEventListener('scroll', updateScrollUI);
updateScrollUI();
}
if (scrollBtn && scrollContainer) {
scrollBtn.onclick = () => scrollContainer.scrollTo({ top: 0, behavior: 'smooth' }); scrollBtn.onclick = () => scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
} }
} }
@@ -2945,7 +3231,7 @@
// Mouse tracking for glow effects // Mouse tracking for glow effects
document.addEventListener('mousemove', (e) => { 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) { if (target) {
const rect = target.getBoundingClientRect(); const rect = target.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100; const x = ((e.clientX - rect.left) / rect.width) * 100;

View File

@@ -18,7 +18,7 @@ const CONFIG = {
// GitHub Repository // GitHub Repository
repo: "infinition/Bjorn", repo: "infinition/Bjorn",
branch: "wiki", branch: "main", // <-- On cible la branche "main" pour charger le contenu (les .md)
// Theme Settings // Theme Settings
themes: [ themes: [
@@ -97,7 +97,7 @@ const CONFIG = {
social: { social: {
discord: "https://discord.gg/B3ZH9taVfT", discord: "https://discord.gg/B3ZH9taVfT",
reddit: "https://www.reddit.com/r/Bjorn_CyberViking/", reddit: "https://www.reddit.com/r/Bjorn_CyberViking/",
github: "https://github.com/infinition/Bjorn", // Virgule respectée par le script github: "https://github.com/infinition/Bjorn",
buyMeACoffee: "https://buymeacoffee.com/infinition" buyMeACoffee: "https://buymeacoffee.com/infinition"
}, },