/* Вбудовує графіки у сторінку робіт. * * Цей файл правити не треба. Роботи ви описуєте у файлах теки _works/, * а звідки брати графік, каже їхня «шапка»: * tableau: "адреса з адресного рядка Tableau Public" * spec: "specs/файл.json" * * Блок із помилкою показує підказку і не ламає решту сторінки. */ (function () { "use strict"; function box(el, kind, title, hint) { var d = document.createElement("div"); d.className = "viz-msg viz-msg--" + kind; d.innerHTML = ""; d.querySelector("b").textContent = title; d.querySelector("span").textContent = hint || ""; el.replaceChildren(d); } /* Адреса графіка Tableau Public → адреса, яку можна вбудувати. Приймає і те, що видно в адресному рядку, і готову адресу /views/. */ function tableauEmbedUrl(raw) { var url = String(raw).trim().split("?")[0]; var m = url.match(/[#!/]*\/(?:viz|views|vizhome)\/([^/]+)\/([^/?#]+)/); if (!m) return null; /* tabs=no обов'язковий: без нього рамка показує всі аркуші книги, разом із чернетками, і будь-хто може в них клацнути. */ return "https://public.tableau.com/views/" + m[1] + "/" + m[2] + "?:showVizHome=no&:embed=y&:tabs=no&:display_count=no&:toolbar=no"; } function mountTableau(el) { var raw = el.getAttribute("data-tableau"); var src = tableauEmbedUrl(raw); if (!src) { box(el, "warn", "Не вдалося прочитати адресу Tableau", "У полі tableau: має бути адреса вигляду " + "public.tableau.com/app/profile/…/viz/Книга/Аркуш. " + "Відкрийте графік на Tableau Public і скопіюйте адресу з адресного рядка."); return; } var frame = document.createElement("iframe"); frame.className = "viz-frame"; frame.src = src; frame.loading = "lazy"; frame.title = "Графік Tableau Public"; frame.setAttribute("allowfullscreen", ""); frame.style.height = (el.getAttribute("data-height") || 620) + "px"; /* Пряме посилання під рамкою. Усередині рамки Tableau малює власну сторінку помилки, якої ми звідси не бачимо. Посилання дає читачеві шлях до графіка, а автору показує, чи жива адреса. */ var link = document.createElement("p"); link.className = "viz-link"; var a = document.createElement("a"); a.href = String(raw).trim(); a.target = "_blank"; a.rel = "noopener"; a.textContent = "Відкрити на Tableau Public"; link.appendChild(a); el.replaceChildren(frame, link); } function mountSpec(el) { var path = el.getAttribute("data-spec"); if (typeof vegaEmbed !== "function") { box(el, "warn", "Бібліотека Vega-Lite не завантажилась", "Перевірте інтернет і оновіть сторінку."); return; } vegaEmbed(el, path, { actions: false, renderer: "svg" }) .catch(function (err) { var msg = String(err && err.message || err); var hint = /fetch|load|404|Not Found/i.test(msg) ? "Файл " + path + " не знайдено. Перевірте, що він лежить у теці specs/ " + "і що ім'я в полі spec: збігається. Великі й малі літери мають значення." : "Специфікація не читається як JSON. Найчастіша причина — зайва або " + "пропущена кома. Вставте специфікацію у Vega Editor: він покаже, де саме."; box(el, "warn", "Графік не вдалося показати", hint); }); } /* Підсвічує в бічній панелі ту роботу, яка зараз на екрані. */ function followScroll() { var links = document.querySelectorAll(".side-works a"); if (!links.length || !("IntersectionObserver" in window)) return; var pairs = []; links.forEach(function (a) { var el = document.getElementById(a.getAttribute("href").slice(1)); if (el) pairs.push({ a: a, el: el }); }); var seen = new Set(); var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) seen.add(e.target); else seen.delete(e.target); }); pairs.forEach(function (p) { p.a.classList.remove("here"); }); for (var i = 0; i < pairs.length; i++) { if (seen.has(pairs[i].el)) { pairs[i].a.classList.add("here"); break; } } }, { rootMargin: "-20% 0px -70% 0px" }); pairs.forEach(function (p) { io.observe(p.el); }); } /* Клік у бічній панелі або адреса з #якорем мають розгортати роботу, навіть якщо читач її згорнув. */ function openOnJump() { function openHash() { var id = location.hash.slice(1); if (!id) return; var el = document.getElementById(id); if (el && el.tagName === "DETAILS") el.open = true; } openHash(); window.addEventListener("hashchange", openHash); document.querySelectorAll('.side-works a').forEach(function (a) { a.addEventListener("click", function () { var el = document.getElementById(a.getAttribute("href").slice(1)); if (el && el.tagName === "DETAILS") el.open = true; }); }); } /* Одна кнопка: згорнути всі роботи або розгорнути назад. */ function foldAll() { var works = document.querySelectorAll("details.work"); var side = document.querySelector(".side"); if (!works.length || !side) return; var btn = document.createElement("button"); btn.className = "side-fold"; btn.type = "button"; side.appendChild(btn); function label() { var open = document.querySelectorAll("details.work[open]").length; btn.textContent = open ? "Згорнути всі" : "Розгорнути всі"; } btn.addEventListener("click", function () { var open = document.querySelectorAll("details.work[open]").length; works.forEach(function (w) { w.open = open === 0; }); label(); }); works.forEach(function (w) { w.addEventListener("toggle", label); }); label(); } function mountAll() { document.querySelectorAll(".viz").forEach(function (el) { try { if (el.hasAttribute("data-tableau")) mountTableau(el); else if (el.hasAttribute("data-spec")) mountSpec(el); else box(el, "todo", "Тут буде графік", "У шапці роботи напишіть tableau: з адресою або spec: з іменем файлу."); } catch (e) { box(el, "warn", "Цей блок не вдалося показати", String(e.message || e)); } }); followScroll(); openOnJump(); foldAll(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", mountAll); } else { mountAll(); } })();