Mudanças entre as edições de "Widget:Item"
Ir para navegação
Ir para pesquisar
m |
m |
||
| Linha 1: | Linha 1: | ||
<includeonly> | <includeonly> | ||
<!-- | <!-- | ||
Tooltip: | Tooltip: payload codificado em data-tip (Módulo:Item). Corpo vazio no HTML. | ||
JS decodifica no hover (rápido, sem API) e limpa no mouseout. | |||
Defesa leve: hover real + rate limit de decodes para automação. | |||
--> | --> | ||
<style> | <style> | ||
| Linha 266: | Linha 267: | ||
var MARGIN = 8; | var MARGIN = 8; | ||
var GAP = 6; | var GAP = 6; | ||
var XOR_KEY = 0x5A; | |||
var activeTip = null; | var activeTip = null; | ||
var activeWrapper = null; | var activeWrapper = null; | ||
var decodeCache = Object.create(null); | |||
var decodeLog = []; | |||
var untrustedStrikes = 0; | |||
var decodeBlocked = false; | |||
var RATE_WINDOW = 120000; | |||
var RATE_MAX = 120; | |||
function | function isStaff() { | ||
try { | |||
if (!window.mw || !mw.config) return false; | |||
var groups = mw.config.get("wgUserGroups") || []; | |||
for (var i = 0; i < groups.length; i++) { | |||
var g = groups[i]; | |||
if (g === "sysop" || g === "bureaucrat" || g === "editor" || g === "staff") { | |||
return true; | |||
} | |||
} | |||
} catch (e) { /* ignore */ } | |||
return false; | |||
} | |||
function allowDecode() { | |||
if (decodeBlocked || !isStaff() && untrustedStrikes >= 4) { | |||
decodeBlocked = true; | |||
return false; | |||
} | |||
if (isStaff()) return true; | |||
var now = Date.now(); | |||
while (decodeLog.length && now - decodeLog[0] > RATE_WINDOW) { | |||
decodeLog.shift(); | |||
} | |||
if (decodeLog.length >= RATE_MAX) { | |||
decodeBlocked = true; | |||
return false; | |||
} | |||
decodeLog.push(now); | |||
return true; | |||
} | |||
function decodePayload(hex) { | |||
if (!hex) return null; | |||
if (decodeCache[hex]) return decodeCache[hex]; | |||
try { | |||
var json = ""; | |||
for (var i = 0; i < hex.length; i += 2) { | |||
json += String.fromCharCode(parseInt(hex.substr(i, 2), 16) ^ XOR_KEY); | |||
} | |||
var obj = JSON.parse(json); | |||
decodeCache[hex] = obj; | |||
return obj; | |||
} catch (e) { | |||
return null; | |||
} | |||
} | |||
function appendText(el, text) { | |||
if (!text) return; | |||
el.appendChild(document.createTextNode(text)); | |||
} | |||
function fillTooltipBody(tip) { | |||
if (tip.querySelector(".item-tooltip-name")) return true; | |||
var hex = tip.getAttribute("data-tip"); | |||
if (!hex) return true; | |||
if (!allowDecode()) return false; | |||
var p = decodePayload(hex); | |||
if (!p) return false; | |||
var body = tip.querySelector(".item-tooltip-body"); | |||
if (!body) return false; | |||
var nameEl = document.createElement("div"); | |||
nameEl.className = "item-tooltip-name"; | |||
appendText(nameEl, p.n); | |||
body.appendChild(nameEl); | |||
if (p.c) { | |||
var catEl = document.createElement("div"); | |||
catEl.className = "item-tooltip-cat"; | |||
appendText(catEl, p.c); | |||
body.appendChild(catEl); | |||
} | |||
var hasDetails = p.lv || p.sp || p.d || p.p1 || p.p2 || p.v; | |||
if (hasDetails) { | |||
var sep = document.createElement("div"); | |||
sep.className = "item-tooltip-sep"; | |||
body.appendChild(sep); | |||
} | |||
if (p.lv) { | |||
var lvEl = document.createElement("div"); | |||
lvEl.className = "item-tooltip-level"; | |||
appendText(lvEl, p.lv); | |||
body.appendChild(lvEl); | |||
} | |||
if (p.sp && (p.p1 || p.p2)) { | |||
var passWrap = document.createElement("div"); | |||
passWrap.className = "item-tooltip-passives"; | |||
if (p.p1) { | |||
var row1 = document.createElement("div"); | |||
row1.className = "item-tooltip-passive-row"; | |||
var b1 = document.createElement("span"); | |||
b1.className = "item-tooltip-passive-badge"; | |||
appendText(b1, "2"); | |||
var t1 = document.createElement("span"); | |||
t1.className = "item-tooltip-passive-text"; | |||
appendText(t1, p.p1); | |||
row1.appendChild(b1); | |||
row1.appendChild(t1); | |||
passWrap.appendChild(row1); | |||
} | |||
if (p.p2) { | |||
var row2 = document.createElement("div"); | |||
row2.className = "item-tooltip-passive-row"; | |||
var b2 = document.createElement("span"); | |||
b2.className = "item-tooltip-passive-badge"; | |||
appendText(b2, "5"); | |||
var t2 = document.createElement("span"); | |||
t2.className = "item-tooltip-passive-text"; | |||
appendText(t2, p.p2); | |||
row2.appendChild(b2); | |||
row2.appendChild(t2); | |||
passWrap.appendChild(row2); | |||
} | |||
body.appendChild(passWrap); | |||
} else if (p.d) { | |||
var descEl = document.createElement("div"); | |||
descEl.className = "item-tooltip-desc"; | |||
appendText(descEl, p.d); | |||
body.appendChild(descEl); | |||
} | |||
if (p.v) { | |||
var footer = document.createElement("div"); | |||
footer.className = "item-tooltip-footer"; | |||
var valEl = document.createElement("span"); | |||
valEl.className = "item-tooltip-val"; | |||
appendText(valEl, p.v); | |||
footer.appendChild(valEl); | |||
body.appendChild(footer); | |||
} | |||
return true; | |||
} | |||
function wipeTooltipBody(tip) { | |||
if (!tip || !tip.getAttribute("data-tip")) return; | |||
var body = tip.querySelector(".item-tooltip-body"); | |||
if (body) body.innerHTML = ""; | |||
} | |||
function positionTooltip(tip, wrapper) { | |||
tip.classList.add("tooltip-visible"); | tip.classList.add("tooltip-visible"); | ||
tip.classList.remove("tooltip-below", "tooltip-shift-right", "tooltip-shift-left"); | tip.classList.remove("tooltip-below", "tooltip-shift-right", "tooltip-shift-left"); | ||
tip.style.removeProperty("--arrow-offset"); | tip.style.removeProperty("--arrow-offset"); | ||
tip.style.visibility = "hidden"; | |||
var rect = wrapper.getBoundingClientRect(); | var rect = wrapper.getBoundingClientRect(); | ||
var tipH = tip.offsetHeight; | var tipH = tip.offsetHeight; | ||
| Linha 318: | Linha 465: | ||
tip.style.left = left + "px"; | tip.style.left = left + "px"; | ||
tip.style.visibility = ""; | tip.style.visibility = ""; | ||
} | |||
function showTooltip(wrapper) { | |||
hideTooltip(); | |||
var tip = wrapper.querySelector(".item-tooltip"); | |||
if (!tip) return; | |||
if (tip.getAttribute("data-tip") && !fillTooltipBody(tip)) return; | |||
activeWrapper = wrapper; | |||
activeTip = tip; | |||
document.body.appendChild(tip); | |||
positionTooltip(tip, wrapper); | |||
} | } | ||
| Linha 327: | Linha 488: | ||
activeTip.style.visibility = ""; | activeTip.style.visibility = ""; | ||
activeTip.style.removeProperty("--arrow-offset"); | activeTip.style.removeProperty("--arrow-offset"); | ||
wipeTooltipBody(activeTip); | |||
activeWrapper.appendChild(activeTip); | activeWrapper.appendChild(activeTip); | ||
activeTip = null; | activeTip = null; | ||
| Linha 334: | Linha 496: | ||
document.addEventListener("mouseover", function (e) { | document.addEventListener("mouseover", function (e) { | ||
if (e.isTrusted === false) { | |||
untrustedStrikes++; | |||
return; | |||
} | |||
var wrapper = e.target.closest ? e.target.closest(".item-wrapper") : null; | var wrapper = e.target.closest ? e.target.closest(".item-wrapper") : null; | ||
if (wrapper && wrapper !== activeWrapper) { | if (wrapper && wrapper !== activeWrapper) { | ||