115 lines
3.6 KiB
JavaScript
115 lines
3.6 KiB
JavaScript
// Basic Markdown renderer
|
|
function renderMarkdown(md) {
|
|
let html = md
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/^### (.*$)/gim, "<h3>$1</h3>")
|
|
.replace(/^## (.*$)/gim, "<h2>$1</h2>")
|
|
.replace(/^# (.*$)/gim, "<h1>$1</h1>")
|
|
.replace(/\*\*\*(.*?)\*\*\*/g, "<strong><em>$1</em></strong>")
|
|
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
|
|
.replace(/\*(.*?)\*/g, "<em>$1</em>")
|
|
.replace(/___(.*?)___/g, "<strong><em>$1</em></strong>")
|
|
.replace(/__(.*?)__/g, "<strong>$1</strong>")
|
|
.replace(/_(.*?)_/g, "<em>$1</em>")
|
|
.replace(/```([\s\S]*?)```/g, "<pre><code>$1</code></pre>")
|
|
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
.replace(/^> (.*$)/gim, "<blockquote>$1</blockquote>")
|
|
.replace(/^\s*-\s+(.*$)/gim, "<li>$1</li>")
|
|
.replace(/^\s*\d+\.\s+(.*$)/gim, "<li>$1</li>")
|
|
.replace(
|
|
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
'<a href="$2" target="_blank" rel="noopener">$1</a>',
|
|
)
|
|
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">')
|
|
.replace(/^---$/gim, "<hr>")
|
|
.replace(/\n/g, "<br>");
|
|
|
|
html = html.replace(/(<li>.*?<\/li>(?:<br><li>.*?<\/li>)*)/gs, (match) => {
|
|
const clean = match.replace(/<br>/g, "");
|
|
return `<ul>${clean}</ul>`;
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
// Parallax for post hero
|
|
function initPostParallax() {
|
|
const hero = document.getElementById("post-hero");
|
|
if (!hero) return;
|
|
|
|
function update() {
|
|
const rect = hero.getBoundingClientRect();
|
|
const yPos = -(rect.top * 0.4);
|
|
hero.style.backgroundPositionY = `${yPos}px`;
|
|
}
|
|
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
update();
|
|
}
|
|
|
|
// Load post hero image from posts.json head_img, fallback to random backdrop
|
|
async function loadPostHero(filename) {
|
|
try {
|
|
// Try to find the post metadata
|
|
const response = await fetch("/api/posts.json");
|
|
const posts = await response.json();
|
|
const post = posts.find((p) => p.file === filename);
|
|
|
|
const hero = document.getElementById("post-hero");
|
|
if (!hero) return;
|
|
|
|
if (post && post.head_img) {
|
|
hero.style.backgroundImage = `url('/assets/${post.head_img}')`;
|
|
} else {
|
|
// Fallback to random backdrop
|
|
const bdResponse = await fetch("/api/backdrops.json");
|
|
const backdrops = await response.json();
|
|
const random = backdrops[Math.floor(Math.random() * backdrops.length)];
|
|
hero.style.backgroundImage = `url('/assets/${random.name}.png')`;
|
|
}
|
|
|
|
initPostParallax();
|
|
} catch (err) {
|
|
console.error("Failed to load post hero:", err);
|
|
}
|
|
}
|
|
|
|
// Load post content
|
|
async function loadPostContent() {
|
|
const pathParts = window.location.pathname.split("/");
|
|
const filename = pathParts[pathParts.length - 1];
|
|
if (!filename) return;
|
|
|
|
loadPostHero(filename);
|
|
|
|
try {
|
|
const response = await fetch(`/api/post/${filename}`);
|
|
const md = await response.text();
|
|
|
|
let contentMd = md;
|
|
const titleMatch = md.match(/^#\s+(.+)$/m);
|
|
if (titleMatch) {
|
|
document.title = `${titleMatch[1]} - Anarchy Phase`;
|
|
const titleEl = document.getElementById("post-title");
|
|
if (titleEl) titleEl.textContent = titleMatch[1];
|
|
contentMd = md.replace(/^#\s+.+$/m, "").trim();
|
|
}
|
|
|
|
const container = document.getElementById("post-body");
|
|
if (container) {
|
|
container.innerHTML = renderMarkdown(contentMd);
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to load post:", err);
|
|
const container = document.getElementById("post-body");
|
|
if (container)
|
|
container.innerHTML =
|
|
'<p style="color:#d48a8a;">Failed to load post content.</p>';
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
loadPostContent();
|
|
});
|