Files
2026-08-27 14:28:09 +03:00

183 lines
7.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Вбудовує графіки у сторінку робіт.
*
* Цей файл правити не треба. Роботи ви описуєте у файлах теки _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 = "<b></b><span></span>";
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();
}
})();