2206
редагувань
Admin (обговорення | внесок) (Віджет «Повідомити про правку» (krai-feedback)) |
Admin (обговорення | внесок) (Аналітика: Matomo-трекер замість простого beacon (cookieless, DNT)) |
||
| (Не показано 7 проміжних версій цього користувача) | |||
| Рядок 368: | Рядок 368: | ||
var loadedAt = Math.floor(Date.now() / 1000); // ts фіксуємо на завантаженні (>3с завжди) | var loadedAt = Math.floor(Date.now() / 1000); // ts фіксуємо на завантаженні (>3с завжди) | ||
var overlay = null; | var overlay = null; | ||
var content = document.getElementById("mw-content-text") || document.body; | |||
var lastSelText = ""; | |||
// --- плаваюча кнопка (коли нічого не виділено) --- | |||
var btn = document.createElement("button"); | var btn = document.createElement("button"); | ||
btn.id = "krai-fb-btn"; | btn.id = "krai-fb-btn"; | ||
btn.type = "button"; | btn.type = "button"; | ||
btn.className = "krai-fb-btn"; | btn.className = "k-btn k-btn--quiet krai-fb-btn"; | ||
btn.textContent = "✏️ | btn.textContent = "✏️ Знайшли неточність?"; | ||
document.body.appendChild(btn); | 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() { | function buildModal() { | ||
| Рядок 380: | Рядок 432: | ||
overlay.className = "krai-fb-overlay"; | overlay.className = "krai-fb-overlay"; | ||
overlay.innerHTML = | overlay.innerHTML = | ||
'<div class="krai-fb-modal" role="dialog" aria-modal="true" aria-labelledby="krai-fb-title">' + | '<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>' + | '<button type="button" class="krai-fb-close" aria-label="Закрити">×</button>' + | ||
'<h2 id="krai-fb-title" class="krai-fb-title"> | '<h2 id="krai-fb-title" class="krai-fb-title">Запропонувати правку</h2>' + | ||
'<p class="krai-fb-sub">Помітили неточність або застарілі дані? Напишіть — ми перевіримо й оновимо. Дякуємо 💛</p>' + | '<p class="krai-fb-sub">Помітили неточність або застарілі дані? Напишіть — ми перевіримо й оновимо. Дякуємо 💛</p>' + | ||
'<form class="krai-fb-form">' + | '<form class="krai-fb-form k-form">' + | ||
'<div class=" | '<div class="k-field">' + | ||
'<label>< | '<label class="k-label">Цитата / фрагмент <span>(необов’язково)</span></label>' + | ||
'<label><input type=" | '<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>' + | '</div>' + | ||
'<input type="text" name="website" class="krai-fb-hp" tabindex="-1" autocomplete="off" aria-hidden="true">' + | '<input type="text" name="website" class="krai-fb-hp" tabindex="-1" autocomplete="off" aria-hidden="true">' + | ||
'<div class="krai-fb-actions">' + | '<div class="krai-fb-actions">' + | ||
'<button type="button" class="krai-fb-cancel">Скасувати</button>' + | '<button type="button" class="k-btn k-btn--quiet krai-fb-cancel">Скасувати</button>' + | ||
'<button type="submit" class="krai-fb-submit">Надіслати</button>' + | '<button type="submit" class="k-btn k-btn--primary krai-fb-submit">Надіслати</button>' + | ||
'</div>' + | '</div>' + | ||
'<div class="krai-fb-msg" aria-live="polite"></div>' + | '<div class="krai-fb-msg" aria-live="polite"></div>' + | ||
| Рядок 412: | Рядок 464: | ||
} | } | ||
function openModal() { | function openModal(presetFragment) { | ||
if (!overlay) buildModal(); | if (!overlay) buildModal(); | ||
var sel = ((window.getSelection && window.getSelection().toString()) || "").trim(); | var f = overlay.querySelector('textarea[name="fragment"]'); | ||
if (sel | 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"); | overlay.classList.add("is-open"); | ||
document.addEventListener("keydown", onEsc); | document.addEventListener("keydown", onEsc); | ||
| Рядок 451: | Рядок 501: | ||
data.append("page", mw.config.get("wgPageName") || ""); | data.append("page", mw.config.get("wgPageName") || ""); | ||
data.append("url", location.href); | data.append("url", location.href); | ||
data.append("kind", | data.append("kind", "fix"); | ||
data.append("fragment", fragment); | data.append("fragment", fragment); | ||
data.append("comment", comment); | data.append("comment", comment); | ||
| Рядок 476: | Рядок 526: | ||
.then(function () { submitBtn.disabled = false; }); | .then(function () { submitBtn.disabled = false; }); | ||
} | } | ||
}); | }); | ||
}); | }); | ||
/* === /krai-feedback === */ | /* === /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 === */ | |||