2206
редагувань
Admin (обговорення | внесок) (Hero для міст: cityHero + page-has-hero, тулбар у hero) |
Admin (обговорення | внесок) (Аналітика: Matomo-трекер замість простого beacon (cookieless, DNT)) |
||
| (Не показано 11 проміжних версій цього користувача) | |||
| Рядок 102: | Рядок 102: | ||
document.body.style.setProperty("--page-hero-bg", 'url("' + cfg.img + '")'); | document.body.style.setProperty("--page-hero-bg", 'url("' + cfg.img + '")'); | ||
document.body.style.setProperty("--page-hero-fg", "#ffffff"); | document.body.style.setProperty("--page-hero-fg", "#ffffff"); | ||
var heading = document.querySelector(".mw-first-heading, .firstHeading, #firstHeading"); | |||
if (heading) { | |||
if ( | if (cfg.title) heading.textContent = cfg.title; | ||
// Кикер «← Країна» над заголовком міста — повернення на сторінку країни. | |||
var parent = pageName.split("/")[0]; | |||
if (parent && parent !== pageName && !document.querySelector(".hero-kicker")) { | |||
var href = (mw.util && mw.util.getUrl) | |||
? mw.util.getUrl(parent) | |||
: (mw.config.get("wgArticlePath") || "/wiki/$1").replace("$1", encodeURIComponent(parent)); | |||
var wrap = document.createElement("div"); | |||
wrap.className = "hero-titlewrap"; | |||
var kicker = document.createElement("a"); | |||
kicker.className = "hero-kicker"; | |||
kicker.href = href; | |||
kicker.textContent = "\u2190 " + parent.replace(/_/g, " "); | |||
heading.parentNode.insertBefore(wrap, heading); | |||
wrap.appendChild(kicker); | |||
wrap.appendChild(heading); | |||
} | |||
} | } | ||
}); | |||
// «← Назад» на звичайних внутрішніх сторінках (не головна, не міста, не | |||
// службові). Повертає в історію браузера; якщо зайшли ззовні — на головну | |||
// (беремо href лого). Гейт по cityHero, щоб не було гонки з page-has-hero. | |||
$(function () { | |||
if (mw.config.get("wgNamespaceNumber") !== 0) return; | |||
if (mw.config.get("wgIsMainPage")) return; | |||
if (mw.config.get("wgAction") !== "view") return; | |||
if (cityHero[mw.config.get("wgPageName") || ""]) return; | |||
var heading = document.querySelector(".mw-first-heading, .firstHeading, #firstHeading"); | |||
if (!heading || document.querySelector(".page-back-link")) return; | |||
var logo = document.querySelector(".mw-logo, .mw-logo-link, #p-logo a, .branding-box a"); | |||
var fallback = (logo && logo.getAttribute("href")) || "/"; | |||
var back = document.createElement("a"); | |||
back.className = "page-back-link"; | |||
back.href = fallback; | |||
back.textContent = "\u2190 Назад"; | |||
back.addEventListener("click", function (e) { | |||
var ref = document.referrer || ""; | |||
if (ref.indexOf(location.origin) === 0 && history.length > 1) { | |||
e.preventDefault(); | |||
history.back(); | |||
} | |||
}); | |||
var container = heading.closest(".vector-page-titlebar, .mw-body-header, .pre-content") || heading; | |||
if (container.parentNode) container.parentNode.insertBefore(back, container); | |||
else heading.parentNode.insertBefore(back, heading); | |||
}); | }); | ||
| Рядок 312: | Рядок 358: | ||
}); | }); | ||
}); | }); | ||
/* === Країнопедія: віджет «Повідомити про правку» (krai-feedback) === */ | |||
mw.loader.using(["mediawiki.util"], function () { | |||
$(function () { | |||
if (mw.config.get("wgNamespaceNumber") !== 0) return; // тільки статті | |||
if (mw.config.get("wgAction") !== "view") return; // тільки читання | |||
if (document.getElementById("krai-fb-btn")) return; | |||
var ENDPOINT = "/feedback.php"; | |||
var loadedAt = Math.floor(Date.now() / 1000); // ts фіксуємо на завантаженні (>3с завжди) | |||
var overlay = null; | |||
var content = document.getElementById("mw-content-text") || document.body; | |||
var lastSelText = ""; | |||
// --- плаваюча кнопка (коли нічого не виділено) --- | |||
var btn = document.createElement("button"); | |||
btn.id = "krai-fb-btn"; | |||
btn.type = "button"; | |||
btn.className = "k-btn k-btn--quiet krai-fb-btn"; | |||
btn.textContent = "✏️ Знайшли неточність?"; | |||
document.body.appendChild(btn); | |||
btn.addEventListener("click", function () { openModal(); }); | |||
// --- мікроменю над виділеним текстом --- | |||
var tip = document.createElement("button"); | |||
tip.id = "krai-fb-tip"; | |||
tip.type = "button"; | |||
tip.className = "k-btn k-btn--quiet krai-fb-tip"; | |||
tip.textContent = "✏️ Запропонувати правку"; | |||
tip.style.display = "none"; | |||
document.body.appendChild(tip); | |||
function selectionInContent(sel) { | |||
if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return false; | |||
return sel.anchorNode && content.contains(sel.anchorNode); | |||
} | |||
function hideTip() { tip.style.display = "none"; } | |||
function showTip() { | |||
var sel = window.getSelection(); | |||
var text = ((sel && sel.toString()) || "").trim(); | |||
if (!selectionInContent(sel) || text.length < 8) { hideTip(); return; } | |||
lastSelText = text.slice(0, 1000); | |||
var rect = sel.getRangeAt(0).getBoundingClientRect(); | |||
if (!rect || (!rect.width && !rect.height)) { hideTip(); return; } | |||
tip.style.display = "block"; | |||
var vw = document.documentElement.clientWidth; | |||
var top = rect.top + window.scrollY - tip.offsetHeight - 8; | |||
var left = rect.left + window.scrollX + (rect.width / 2) - (tip.offsetWidth / 2); | |||
left = Math.max(window.scrollX + 8, Math.min(left, window.scrollX + vw - tip.offsetWidth - 8)); | |||
if (top < window.scrollY + 4) top = rect.bottom + window.scrollY + 8; // немає місця зверху → знизу | |||
tip.style.top = top + "px"; | |||
tip.style.left = left + "px"; | |||
} | |||
document.addEventListener("mouseup", function () { setTimeout(showTip, 10); }); | |||
document.addEventListener("touchend", function () { setTimeout(showTip, 10); }); | |||
document.addEventListener("selectionchange", function () { | |||
var sel = window.getSelection(); | |||
if (!sel || sel.isCollapsed || !((sel.toString() || "").trim())) hideTip(); | |||
}); | |||
window.addEventListener("scroll", hideTip, true); | |||
window.addEventListener("resize", hideTip); | |||
tip.addEventListener("mousedown", function (e) { e.preventDefault(); }); // не втратити виділення | |||
tip.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
hideTip(); | |||
openModal(lastSelText); | |||
}); | |||
function buildModal() { | |||
overlay = document.createElement("div"); | |||
overlay.className = "krai-fb-overlay"; | |||
overlay.innerHTML = | |||
'<div class="k-dialog krai-fb-modal" role="dialog" aria-modal="true" aria-labelledby="krai-fb-title">' + | |||
'<button type="button" class="krai-fb-close" aria-label="Закрити">×</button>' + | |||
'<h2 id="krai-fb-title" class="krai-fb-title">Запропонувати правку</h2>' + | |||
'<p class="krai-fb-sub">Помітили неточність або застарілі дані? Напишіть — ми перевіримо й оновимо. Дякуємо 💛</p>' + | |||
'<form class="krai-fb-form k-form">' + | |||
'<div class="k-field">' + | |||
'<label class="k-label">Цитата / фрагмент <span>(необов’язково)</span></label>' + | |||
'<textarea class="k-textarea" name="fragment" rows="2" placeholder="Місце, якого стосується правка"></textarea>' + | |||
'</div>' + | |||
'<div class="k-field">' + | |||
'<label class="k-label">Що не так або що виправити?</label>' + | |||
'<textarea class="k-textarea" name="comment" rows="4" placeholder="Опишіть коротко"></textarea>' + | |||
'</div>' + | |||
'<div class="k-field">' + | |||
'<label class="k-label">Контакт <span>(необов’язково, якщо треба відповідь)</span></label>' + | |||
'<input type="text" class="k-input" name="contact" placeholder="Email або @Telegram">' + | |||
'</div>' + | |||
'<input type="text" name="website" class="krai-fb-hp" tabindex="-1" autocomplete="off" aria-hidden="true">' + | |||
'<div class="krai-fb-actions">' + | |||
'<button type="button" class="k-btn k-btn--quiet krai-fb-cancel">Скасувати</button>' + | |||
'<button type="submit" class="k-btn k-btn--primary krai-fb-submit">Надіслати</button>' + | |||
'</div>' + | |||
'<div class="krai-fb-msg" aria-live="polite"></div>' + | |||
'</form>' + | |||
'</div>'; | |||
document.body.appendChild(overlay); | |||
overlay.addEventListener("click", function (e) { if (e.target === overlay) closeModal(); }); | |||
overlay.querySelector(".krai-fb-close").addEventListener("click", closeModal); | |||
overlay.querySelector(".krai-fb-cancel").addEventListener("click", closeModal); | |||
overlay.querySelector(".krai-fb-form").addEventListener("submit", onSubmit); | |||
} | |||
function openModal(presetFragment) { | |||
if (!overlay) buildModal(); | |||
var f = overlay.querySelector('textarea[name="fragment"]'); | |||
var sel = presetFragment || ((window.getSelection && window.getSelection().toString()) || "").trim(); | |||
if (sel && f && !f.value) f.value = sel.slice(0, 1000); | |||
overlay.classList.add("is-open"); | |||
document.addEventListener("keydown", onEsc); | |||
var c = overlay.querySelector('textarea[name="comment"]'); | |||
if (c) c.focus(); | |||
} | |||
function closeModal() { | |||
if (overlay) overlay.classList.remove("is-open"); | |||
document.removeEventListener("keydown", onEsc); | |||
} | |||
function onEsc(e) { if (e.key === "Escape") closeModal(); } | |||
function onSubmit(e) { | |||
e.preventDefault(); | |||
var form = e.target; | |||
var msg = form.querySelector(".krai-fb-msg"); | |||
var comment = form.comment.value.trim(); | |||
var fragment = form.fragment.value.trim(); | |||
if (comment.length < 5 && !fragment) { | |||
msg.className = "krai-fb-msg is-err"; | |||
msg.textContent = "Напишіть, будь ласка, кілька слів про правку."; | |||
return; | |||
} | |||
var submitBtn = form.querySelector(".krai-fb-submit"); | |||
submitBtn.disabled = true; | |||
msg.className = "krai-fb-msg"; | |||
msg.textContent = "Надсилаю…"; | |||
var data = new FormData(); | |||
data.append("page", mw.config.get("wgPageName") || ""); | |||
data.append("url", location.href); | |||
data.append("kind", "fix"); | |||
data.append("fragment", fragment); | |||
data.append("comment", comment); | |||
data.append("contact", form.contact.value.trim()); | |||
data.append("website", form.website.value); | |||
data.append("ts", String(loadedAt)); | |||
fetch(ENDPOINT, { method: "POST", body: data, credentials: "same-origin" }) | |||
.then(function (r) { return r.json().catch(function () { return { ok: r.ok }; }); }) | |||
.then(function (res) { | |||
if (res && res.ok) { | |||
form.reset(); | |||
msg.className = "krai-fb-msg is-ok"; | |||
msg.textContent = "Дякуємо! Ми перевіримо й оновимо. 💛"; | |||
setTimeout(closeModal, 1800); | |||
} else { | |||
throw new Error((res && res.error) || "err"); | |||
} | |||
}) | |||
.catch(function () { | |||
msg.className = "krai-fb-msg is-err"; | |||
msg.textContent = "Не вдалося надіслати. Спробуйте ще раз трохи згодом."; | |||
}) | |||
.then(function () { submitBtn.disabled = false; }); | |||
} | |||
}); | |||
}); | |||
/* === /krai-feedback === */ | |||
/* === Країнопедія: Matomo (self-host /matomo/, cookieless, поважає DNT) === */ | |||
var _paq = window._paq = window._paq || []; | |||
_paq.push(["disableCookies"]); | |||
_paq.push(["setDoNotTrack", true]); | |||
_paq.push(["trackPageView"]); | |||
_paq.push(["enableLinkTracking"]); | |||
(function () { | |||
var u = "//nomadica.uno/matomo/"; | |||
_paq.push(["setTrackerUrl", u + "matomo.php"]); | |||
_paq.push(["setSiteId", "1"]); | |||
var d = document, g = d.createElement("script"), s = d.getElementsByTagName("script")[0]; | |||
g.async = true; g.src = u + "matomo.js"; s.parentNode.insertBefore(g, s); | |||
})(); | |||
/* === /Matomo === */ | |||