function initMobileMenu() { if (!document.body.dataset.blogPage) return; const button = document.getElementById("mobile-menu-btn"); const menu = document.getElementById("mobile-menu"); if (!button || !menu) return; button.addEventListener("click", () => { menu.classList.toggle("hidden"); }); menu.querySelectorAll("a, button").forEach((item) => { item.addEventListener("click", () => menu.classList.add("hidden")); }); } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function getArticleHref(article) { const pageType = document.body.dataset.blogPage; return pageType === "archive" || pageType === "article" ? `./${article.slug}.html` : `./blog/${article.slug}.html`; } function getBlogPlaceholderSrc() { const pageType = document.body.dataset.blogPage; return pageType === "archive" || pageType === "article" ? "../images/shared/article-placeholder.svg" : "./images/shared/article-placeholder.svg"; } function getArticleImageSrc(article) { if (!article.image) { return getBlogPlaceholderSrc(); } const pageType = document.body.dataset.blogPage; return pageType === "archive" || pageType === "article" ? `../${article.image}` : `./${article.image}`; } function getArticleImageFallbackAttr() { return `this.onerror=null;this.src='${getBlogPlaceholderSrc()}'`; } function buildFooterSocialLinksMarkup() { return `
`; } function normalizeFooterContacts() { const footer = document.querySelector("footer"); footer?.querySelectorAll(".grid > div:last-child li").forEach((item) => { item.textContent = item.textContent.replace(/^[^+\p{L}\p{N}]+/u, "").trim(); }); } function initFooterSocialLinks() { const footer = document.querySelector("footer"); const footerColumns = footer?.querySelectorAll(".grid > div"); const contactColumn = footerColumns?.[footerColumns.length - 1]; if (!contactColumn || contactColumn.querySelector("[data-footer-socials]")) return; contactColumn.insertAdjacentHTML("beforeend", buildFooterSocialLinksMarkup()); } function articleCardMarkup(article) { const pageType = document.body.dataset.blogPage; const href = getArticleHref(article); const imageSrc = getArticleImageSrc(article); const imageFallback = getArticleImageFallbackAttr(); if (!pageType) { return `
${escapeHtml(article.title)}
${article.publishedAt} Блог

${escapeHtml(article.title)}

${escapeHtml(article.excerpt)}

Открыть статью
`; } return `
${escapeHtml(article.title)}
${article.publishedAt} Статья

${escapeHtml(article.title)}

${escapeHtml(article.excerpt)}

Читать статью →
`; } function renderBlogGrid(containerId, limit) { const container = document.getElementById(containerId); if (!container || !Array.isArray(window.BLOG_ARTICLES)) return; const items = limit ? window.BLOG_ARTICLES.slice(0, limit) : window.BLOG_ARTICLES; container.innerHTML = items.map(articleCardMarkup).join(""); } function initHomeBlogSection() { renderBlogGrid("blog-home-grid", 6); } function initBlogArchive() { renderBlogGrid("blog-index-grid"); } function buildHeadingId(index) { return `section-${index + 1}`; } function insertTocAfterIntro(tocCard, content) { const introParagraphs = Array.from(content.children).filter((node) => node.tagName === "P"); const insertionTarget = introParagraphs[1] || introParagraphs[0]; if (insertionTarget?.parentNode === content) { insertionTarget.insertAdjacentElement("afterend", tocCard); } else { content.prepend(tocCard); } } function initArticleToc() { const toc = document.getElementById("article-toc"); const tocCard = toc?.closest(".article-toc-card"); const content = document.querySelector("[data-article-content]"); if (!toc || !tocCard || !content) return; const headings = Array.from(content.querySelectorAll("h2, h3")); if (!headings.length) { toc.innerHTML = "

Содержание появится после добавления подзаголовков.

"; return; } const itemsMarkup = headings .map((heading, index) => { if (!heading.id) { heading.id = buildHeadingId(index); } const itemClass = heading.tagName === "H3" ? "article-toc__item article-toc__item--nested" : "article-toc__item"; return `
  • ${escapeHtml(heading.textContent.trim())}
  • `; }) .join(""); toc.innerHTML = ``; tocCard.classList.add("article-toc-card--inline"); insertTocAfterIntro(tocCard, content); const tocItems = Array.from(toc.querySelectorAll(".article-toc__item")); const collapsibleItems = tocItems.slice(6); if (!collapsibleItems.length) return; collapsibleItems.forEach((item) => { item.hidden = true; }); const toggleButton = document.createElement("button"); toggleButton.type = "button"; toggleButton.className = "article-toc-toggle"; toggleButton.textContent = "Показать полностью"; toggleButton.setAttribute("aria-expanded", "false"); toggleButton.addEventListener("click", () => { const expanded = toggleButton.getAttribute("aria-expanded") === "true"; collapsibleItems.forEach((item) => { item.hidden = expanded; }); toggleButton.setAttribute("aria-expanded", expanded ? "false" : "true"); toggleButton.textContent = expanded ? "Показать полностью" : "Свернуть содержание"; }); tocCard.append(toggleButton); } function initArticleViews() { const slug = document.body.dataset.articleSlug; if (!slug) return; const key = `ozimaya:blog:views:${slug}`; const currentViews = parseInt(localStorage.getItem(key) || "0", 10) + 1; localStorage.setItem(key, String(currentViews)); document.querySelectorAll("[data-blog-views]").forEach((node) => { node.textContent = new Intl.NumberFormat("ru-RU").format(currentViews); }); } function getRelatedArticles(currentSlug, count = 4) { if (!Array.isArray(window.BLOG_ARTICLES)) return []; const filtered = window.BLOG_ARTICLES.filter((article) => article.slug !== currentSlug); if (filtered.length <= count) return filtered; const startIndex = window.BLOG_ARTICLES.findIndex((article) => article.slug === currentSlug); const rotated = []; for (let i = 1; i < window.BLOG_ARTICLES.length; i += 1) { const article = window.BLOG_ARTICLES[(startIndex + i) % window.BLOG_ARTICLES.length]; if (article.slug !== currentSlug) { rotated.push(article); } } return rotated.slice(0, count); } function initRelatedArticles() { const track = document.getElementById("related-articles-track"); const currentSlug = document.body.dataset.articleSlug; if (!track || !currentSlug) return; const related = getRelatedArticles(currentSlug, 4); track.innerHTML = related .map((article) => { const href = getArticleHref(article); const imageSrc = getArticleImageSrc(article); const imageFallback = getArticleImageFallbackAttr(); return ` `; }) .join(""); } document.addEventListener("DOMContentLoaded", () => { initMobileMenu(); normalizeFooterContacts(); initFooterSocialLinks(); initHomeBlogSection(); initBlogArchive(); initArticleToc(); initArticleViews(); initRelatedArticles(); });