Files
lit.ruv.wtf/website/scripts/blogPage.js

113 lines
3.2 KiB
JavaScript

import { MarkdownRenderer } from "./MarkdownRenderer.js";
/**
* Decodes markdown payload from a JSON script element.
*
* @param {HTMLScriptElement | null} payloadEl
* @returns {string}
*/
function readMarkdownPayload(payloadEl) {
if (!payloadEl) return "";
try {
const parsed = JSON.parse(payloadEl.textContent || "\"\"");
return typeof parsed === "string" ? parsed : "";
} catch (error) {
console.error("Failed to parse blog markdown payload", error);
return "";
}
}
/**
* Wires copy buttons rendered by MarkdownRenderer.
*
* @param {ParentNode} container
* @returns {void}
*/
function wireCopyButtons(container) {
container.querySelectorAll(".md-copy-btn").forEach((button) => {
button.addEventListener("click", (event) => {
event.stopPropagation();
const code = button.getAttribute("data-code");
if (!code) return;
navigator.clipboard.writeText(code).then(() => {
const originalText = button.textContent;
button.textContent = "✓";
button.classList.add("copied");
setTimeout(() => {
button.textContent = originalText;
button.classList.remove("copied");
}, 2000);
}).catch((error) => {
console.error("Failed to copy code", error);
});
});
});
}
/**
* Renders blog markdown on static post pages using the shared renderer.
*
* @returns {void}
*/
function renderStaticBlogPost() {
/** @type {HTMLElement | null} */
const target = document.querySelector("[data-blog-post-content]");
if (!target) return;
/** @type {HTMLScriptElement | null} */
const payload = document.querySelector("#blogPostMarkdown");
const markdown = readMarkdownPayload(payload);
target.innerHTML = MarkdownRenderer.render(markdown);
MarkdownRenderer.applyImageScale(target);
wireCopyButtons(target);
}
/**
* Toggles the compact top bar when the hero logo scrolls out of view.
*
* @returns {void}
*/
function setupScrollTopBar() {
const body = document.body;
const bar = document.querySelector("[data-blog-scroll-topbar]");
const heroLogo = document.querySelector(".blog-logo-link");
if (!bar) return;
if (heroLogo && "IntersectionObserver" in window) {
const observer = new IntersectionObserver(
([entry]) => {
body.classList.toggle("blog-scrolled", !entry.isIntersecting);
},
{ threshold: 0 }
);
observer.observe(heroLogo);
} else {
// Fallback: fixed pixel threshold
const applyState = () => {
body.classList.toggle("blog-scrolled", window.scrollY >= 80);
};
window.addEventListener("scroll", applyState, { passive: true });
applyState();
}
}
/**
* Bootstraps static blog page behaviors.
*
* @returns {void}
*/
function initializeBlogPage() {
renderStaticBlogPost();
setupScrollTopBar();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initializeBlogPage, { once: true });
} else {
initializeBlogPage();
}