mirror of
https://github.com/infinition/Bjorn.git
synced 2025-12-13 16:14:57 +00:00
feat: Improve UI with early breadcrumbs, sidebar auto-expansion, and mobile swipe gestures, fix image and TOC rendering, and add an Introduction page.
This commit is contained in:
152
index.html
152
index.html
@@ -661,11 +661,21 @@
|
||||
<script>
|
||||
// --- 1. CONFIG & STATE ---
|
||||
const renderer = new marked.Renderer();
|
||||
// Custom Image Rendering for Lazy Loading and styling
|
||||
|
||||
// --- FIXED IMAGE RENDERER (Handles Marked 5.0+ Object Token) ---
|
||||
renderer.image = function (href, title, text) {
|
||||
// New Marked versions pass an object as the first argument
|
||||
if (typeof href === 'object' && href !== null) {
|
||||
const token = href;
|
||||
href = token.href;
|
||||
title = token.title;
|
||||
text = token.text;
|
||||
}
|
||||
|
||||
// Handle relative paths for GitHub Pages if needed, usually browser handles it.
|
||||
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 styling
|
||||
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>`;
|
||||
@@ -813,7 +823,6 @@
|
||||
toggleVersionsPage(null, false);
|
||||
} else if (pageParam) {
|
||||
// pageParam format: "Folder/Filename.md" or similar
|
||||
// We need to find the title for this file
|
||||
let found = false;
|
||||
for (const [folder, files] of Object.entries(STATE.wikiData)) {
|
||||
for (const [title, file] of Object.entries(files)) {
|
||||
@@ -901,6 +910,13 @@
|
||||
const pageNav = document.getElementById('page-nav');
|
||||
const scrollContainer = document.getElementById('scroll-container');
|
||||
|
||||
// Set Breadcrumbs early so they appear even if loading fails/takes time
|
||||
const cleanFolder = folder.replace(/^\d+_/, '').replace(/_/g, ' ');
|
||||
document.getElementById('breadcrumbs').innerHTML = `
|
||||
<span class="hover:text-hack-heading cursor-pointer" onclick="loadDefault()">wiki</span> <span>/</span>
|
||||
<span>${cleanFolder}</span> <span>/</span> <span class="text-hack-green font-bold">${title}</span>
|
||||
`;
|
||||
|
||||
// UI Reset
|
||||
if (!STATE.contentCache[`${folder}/${filename}`]) {
|
||||
viewer.innerHTML = `<div class="animate-pulse space-y-4 pt-10"><div class="h-8 bg-gray-800 rounded w-1/3 mb-6"></div><div class="h-4 bg-gray-800 rounded w-full"></div><div class="h-4 bg-gray-800 rounded w-5/6"></div><div class="h-4 bg-gray-800 rounded w-4/6"></div></div>`;
|
||||
@@ -955,13 +971,6 @@
|
||||
generateTOC();
|
||||
renderPagination(folder, title);
|
||||
|
||||
// 5. Breadcrumbs
|
||||
const cleanFolder = folder.replace(/^\d+_/, '').replace(/_/g, ' ');
|
||||
document.getElementById('breadcrumbs').innerHTML = `
|
||||
<span class="hover:text-hack-heading cursor-pointer" onclick="loadDefault()">wiki</span> <span>/</span>
|
||||
<span>${cleanFolder}</span> <span>/</span> <span class="text-hack-green font-bold">${title}</span>
|
||||
`;
|
||||
|
||||
// 6. Navigation / History
|
||||
if (pushHistory) {
|
||||
const newUrl = `?page=${folder}/${filename}`;
|
||||
@@ -969,6 +978,8 @@
|
||||
}
|
||||
|
||||
// 7. Scroll
|
||||
STATE.expandedSections = new Set([folder]);
|
||||
renderSidebar();
|
||||
if (window.location.hash) {
|
||||
const el = document.getElementById(window.location.hash.substring(1));
|
||||
if (el) el.scrollIntoView();
|
||||
@@ -995,6 +1006,9 @@
|
||||
2. Check capitalization (Linux/GitHub is case-sensitive).<br>
|
||||
3. Verify <code>.nojekyll</code> is at the root of your repo.
|
||||
</p>`;
|
||||
|
||||
// Breadcrumbs Fallback
|
||||
document.getElementById('breadcrumbs').innerHTML = `<span class="hover:text-hack-heading cursor-pointer" onclick="loadDefault()">wiki</span> <span>/</span> <span class="text-red-500">Error</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1049,7 +1063,7 @@
|
||||
}
|
||||
|
||||
function enhanceMarkdownContent() {
|
||||
// Anchor Links
|
||||
// Anchor Links Generation (Same ID logic as before)
|
||||
document.querySelectorAll('#markdown-viewer h2, #markdown-viewer h3').forEach(h => {
|
||||
if (!h.id) h.id = h.textContent.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
||||
|
||||
@@ -1066,6 +1080,34 @@
|
||||
h.appendChild(anchor);
|
||||
});
|
||||
|
||||
// NEW: Fix Manual TOC Links (Smart Handler)
|
||||
// This catches clicks on generated TOC links like [Introduction](#-introduction)
|
||||
document.querySelectorAll('#markdown-viewer a[href^="#"]').forEach(link => {
|
||||
// Ignore the anchor links we just created above (class anchor-link)
|
||||
if (link.classList.contains('anchor-link')) return;
|
||||
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const hash = link.getAttribute('href').substring(1); // remove #
|
||||
|
||||
// 1. Try exact match (e.g. if ID matches TOC exactly)
|
||||
let target = document.getElementById(hash);
|
||||
|
||||
// 2. Fallback: Try removing leading dashes (Fixes GitHub style TOC vs JS generated IDs)
|
||||
if (!target) {
|
||||
const cleanHash = hash.replace(/^-+/, '');
|
||||
target = document.getElementById(cleanHash);
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
history.pushState(null, null, `#${hash}`);
|
||||
} else {
|
||||
console.warn('Anchor target not found:', hash);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Syntax Highlight
|
||||
document.querySelectorAll('pre code').forEach((el) => hljs.highlightElement(el));
|
||||
|
||||
@@ -1091,13 +1133,29 @@
|
||||
|
||||
// External Links
|
||||
document.querySelectorAll('#markdown-viewer a').forEach(a => {
|
||||
if (a.hostname !== window.location.hostname && !a.className.includes('anchor-link')) {
|
||||
|
||||
// Skip if:
|
||||
// - link is internal
|
||||
// - it's an anchor-link
|
||||
// - it has no href
|
||||
// - href starts with "#"
|
||||
// - link contains an <img> → prevents icon under badges
|
||||
if (
|
||||
a.hostname !== window.location.hostname &&
|
||||
!a.classList.contains('anchor-link') &&
|
||||
a.getAttribute('href') &&
|
||||
!a.getAttribute('href').startsWith('#') &&
|
||||
!a.querySelector('img') // ⬅ NEW: do NOT add icon when link wraps an image
|
||||
) {
|
||||
a.target = '_blank';
|
||||
a.rel = 'noopener noreferrer';
|
||||
|
||||
// Add external icon
|
||||
a.innerHTML += ' <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="inline-block opacity-50"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>';
|
||||
a.innerHTML +=
|
||||
' <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="inline-block opacity-50"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>';
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function renderPagination(currentFolder, currentTitle) {
|
||||
@@ -1336,8 +1394,76 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize UI Logic (Scroll Bar) immediately
|
||||
function initUI() {
|
||||
const scrollContainer = document.getElementById('scroll-container');
|
||||
const progressBar = document.getElementById('reading-progress-bar');
|
||||
const scrollBtn = document.getElementById('scroll-top-btn');
|
||||
|
||||
if (scrollContainer && progressBar) {
|
||||
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 + '%';
|
||||
|
||||
// Show/Hide Scroll Top Button
|
||||
if (scrollTop > 300) scrollBtn.classList.remove('hidden');
|
||||
else scrollBtn.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
if (scrollBtn) {
|
||||
scrollBtn.onclick = () => scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
// --- MOBILE SWIPE GESTURES ---
|
||||
// Detect swipe from left edge → open menu
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
let touchEndX = 0;
|
||||
let touchEndY = 0;
|
||||
|
||||
const MIN_SWIPE_DISTANCE = 50; // minimal movement to count as a swipe
|
||||
const MAX_VERTICAL_DRIFT = 80; // tolerance to avoid accidental scroll/swipe mix
|
||||
const EDGE_ZONE = 40; // swipe must start within 40px from screen left
|
||||
|
||||
document.addEventListener("touchstart", (e) => {
|
||||
const t = e.changedTouches[0];
|
||||
touchStartX = t.clientX;
|
||||
touchStartY = t.clientY;
|
||||
});
|
||||
|
||||
document.addEventListener("touchmove", (e) => {
|
||||
const t = e.changedTouches[0];
|
||||
touchEndX = t.clientX;
|
||||
touchEndY = t.clientY;
|
||||
});
|
||||
|
||||
document.addEventListener("touchend", () => {
|
||||
const dx = touchEndX - touchStartX;
|
||||
const dy = Math.abs(touchEndY - touchStartY);
|
||||
|
||||
// 1. SWIPE → RIGHT = OPEN MENU
|
||||
if (
|
||||
touchStartX < EDGE_ZONE && // gesture started at screen left
|
||||
dx > MIN_SWIPE_DISTANCE && // swiped enough to the right
|
||||
dy < MAX_VERTICAL_DRIFT // not a vertical scroll
|
||||
) {
|
||||
openMenu();
|
||||
}
|
||||
|
||||
// 2. SWIPE → LEFT = CLOSE MENU
|
||||
if (
|
||||
dx < -MIN_SWIPE_DISTANCE && // swipe left
|
||||
dy < MAX_VERTICAL_DRIFT // again avoid scroll false positives
|
||||
) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// INIT
|
||||
window.onload = () => {
|
||||
initUI();
|
||||
fetchLatestVersion();
|
||||
initWiki();
|
||||
lucide.createIcons();
|
||||
|
||||
Reference in New Issue
Block a user