Mudanças entre as edições de "Widget:Item"
Ir para navegação
Ir para pesquisar
m |
m |
||
| (7 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| 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. | |||
Ícone: span.item-icon + data-i codificado — sem <img> no HTML; JS hidrata | |||
background-image e remove data-i. Anti-arrastar/cópia/clique-direito leve. | |||
Defesa leve: hover real + rate limit de decodes para automação. | |||
--> | --> | ||
<style> | <style> | ||
| Linha 32: | Linha 34: | ||
text-align: center; | text-align: center; | ||
overflow: visible; | overflow: visible; | ||
filter: none; | |||
-webkit-user-select: none; | |||
user-select: none; | |||
-webkit-touch-callout: none; | |||
} | } | ||
.item-icon { | |||
display: block; | |||
margin: 0 auto; | |||
background-repeat: no-repeat; | |||
background-position: center; | |||
background-size: contain; | |||
filter: none; | |||
box-shadow: none; | |||
-webkit-user-select: none; | |||
user-select: none; | |||
-webkit-user-drag: none; | |||
-webkit-touch-callout: none; | |||
pointer-events: none; | |||
} | |||
/* Páginas em cache ainda com [[Arquivo:…]] → <img> legado */ | |||
.item-wrapper img { | .item-wrapper img { | ||
display: block; | display: block; | ||
margin: 0 auto; | margin: 0 auto; | ||
filter: none; | |||
box-shadow: none; | |||
-webkit-user-select: none; | |||
user-select: none; | |||
-webkit-user-drag: none; | |||
-webkit-touch-callout: none; | |||
pointer-events: none; | |||
} | } | ||
| Linha 64: | Linha 93: | ||
line-height: 1; | line-height: 1; | ||
white-space: nowrap; | white-space: nowrap; | ||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0. | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | ||
z-index: 10; | z-index: 10; | ||
} | } | ||
| Linha 74: | Linha 103: | ||
} | } | ||
.wikitable td .reward-items .item-wrapper { | .wikitable td .reward-items .item-wrapper { | ||
margin-bottom: 10px; | margin-bottom: 10px; | ||
} | } | ||
.item-tooltip { | .item-tooltip { | ||
| Linha 90: | Linha 114: | ||
flex-direction: column; | flex-direction: column; | ||
align-items: center; | align-items: center; | ||
filter: | filter: none; | ||
} | } | ||
| Linha 109: | Linha 133: | ||
box-sizing: border-box; | box-sizing: border-box; | ||
font-family: Tahoma, "Segoe UI", Arial, sans-serif; | font-family: Tahoma, "Segoe UI", Arial, sans-serif; | ||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22); | |||
box-shadow: | |||
} | } | ||
| Linha 138: | Linha 161: | ||
height: 1px; | height: 1px; | ||
background: #5c4a31; | background: #5c4a31; | ||
margin: 8px -14px 10px; | margin: 8px -14px 10px; | ||
width: calc(100% + 28px); | width: calc(100% + 28px); | ||
| Linha 229: | Linha 251: | ||
} | } | ||
.item-tooltip-arrow { | .item-tooltip-arrow { | ||
display: block; | display: block; | ||
| Linha 250: | Linha 271: | ||
} | } | ||
.item-tooltip.tooltip-shift-right { | .item-tooltip.tooltip-shift-right { | ||
align-items: flex-start; | align-items: flex-start; | ||
| Linha 273: | Linha 293: | ||
var MARGIN = 8; | var MARGIN = 8; | ||
var GAP = 6; | var GAP = 6; | ||
var XOR_KEY = 0x5A; /* legado hex — páginas em cache antes do b64 */ | |||
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 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 | function bytesToUtf8String(bytes) { | ||
if (typeof TextDecoder !== "undefined") { | |||
return new TextDecoder("utf-8").decode( | |||
bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes) | |||
); | |||
} | |||
var bin = ""; | |||
for (var j = 0; j < bytes.length; j++) { | |||
bin += String.fromCharCode(bytes[j]); | |||
} | |||
return decodeURIComponent(escape(bin)); | |||
} | |||
function base64ToUtf8String(b64) { | |||
var bin = atob(b64); | |||
var bytes = new Uint8Array(bin.length); | |||
for (var i = 0; i < bin.length; i++) { | |||
bytes[i] = bin.charCodeAt(i); | |||
} | |||
return bytesToUtf8String(bytes); | |||
} | |||
function hexToUtf8String(hex) { | |||
var bytes = []; | |||
for (var i = 0; i < hex.length; i += 2) { | |||
bytes.push(parseInt(hex.substr(i, 2), 16) ^ XOR_KEY); | |||
} | |||
return bytesToUtf8String(bytes); | |||
} | |||
function payloadToJson(raw) { | |||
if (!raw) return null; | |||
if (raw.indexOf("b64:") === 0) { | |||
return base64ToUtf8String(raw.slice(4)); | |||
} | |||
return hexToUtf8String(raw); | |||
} | |||
function decodePayload(raw) { | |||
if (!raw) return null; | |||
if (decodeCache[raw]) return decodeCache[raw]; | |||
try { | |||
var obj = JSON.parse(payloadToJson(raw)); | |||
decodeCache[raw] = obj; | |||
return obj; | |||
} catch (e) { | |||
return null; | |||
} | |||
} | |||
function getThumbUrl(filename, width) { | |||
var title = "Special:Redirect/file/" + String(filename).replace(/ /g, "_"); | |||
var scriptPath = ""; | |||
try { | |||
if (window.mw && mw.config) { | |||
scriptPath = mw.config.get("wgScriptPath") || ""; | |||
} | |||
} catch (e) { /* ignore */ } | |||
/* Caminho relativo ao origin da página (HTTPS). mw.util.getUrl | |||
pode gerar http://IP/… e o browser bloqueia Mixed Content. */ | |||
return scriptPath + "/index.php?title=" + encodeURIComponent(title) + "&width=" + width; | |||
} | |||
function hydrateIcon(el) { | |||
if (!el || el.getAttribute("data-ready") === "1") return; | |||
var raw = el.getAttribute("data-i"); | |||
if (!raw) return; | |||
var p = decodePayload(raw); | |||
if (!p || !p.i) return; | |||
var w = p.w || 32; | |||
var h = p.h || 32; | |||
el.style.width = w + "px"; | |||
el.style.height = h + "px"; | |||
el.style.backgroundImage = 'url("' + getThumbUrl(p.i, w) + '")'; | |||
el.setAttribute("data-ready", "1"); | |||
el.removeAttribute("data-i"); | |||
} | |||
function hydrateAllIcons(root) { | |||
var scope = root || document; | |||
var icons = scope.querySelectorAll ? scope.querySelectorAll(".item-icon[data-i]") : []; | |||
for (var i = 0; i < icons.length; i++) { | |||
hydrateIcon(icons[i]); | |||
} | |||
} | |||
function watchIconHydration() { | |||
if (!window.MutationObserver) return; | |||
var pending = null; | |||
var observer = new MutationObserver(function (mutations) { | |||
var need = false; | |||
for (var i = 0; i < mutations.length; i++) { | |||
var added = mutations[i].addedNodes; | |||
for (var j = 0; j < added.length; j++) { | |||
var node = added[j]; | |||
if (node.nodeType !== 1) continue; | |||
if (node.matches && node.matches(".item-icon[data-i]")) { | |||
need = true; | |||
break; | |||
} | |||
if (node.querySelector && node.querySelector(".item-icon[data-i]")) { | |||
need = true; | |||
break; | |||
} | |||
} | |||
if (need) break; | |||
} | |||
if (!need) return; | |||
if (pending) clearTimeout(pending); | |||
pending = setTimeout(function () { | |||
pending = null; | |||
hydrateAllIcons(); | |||
}, 0); | |||
}); | |||
observer.observe(document.documentElement, { childList: true, subtree: true }); | |||
} | |||
window.glaItemHydrateIcons = hydrateAllIcons; | |||
function protectItemWrapperEvents() { | |||
document.addEventListener("contextmenu", function (e) { | |||
if (e.target.closest && e.target.closest(".item-wrapper")) { | |||
e.preventDefault(); | |||
} | |||
}, true); | |||
document.addEventListener("dragstart", function (e) { | |||
if (e.target.closest && e.target.closest(".item-wrapper")) { | |||
e.preventDefault(); | |||
} | |||
}, true); | |||
document.addEventListener("copy", function (e) { | |||
if (e.target.closest && e.target.closest(".item-wrapper")) { | |||
e.preventDefault(); | |||
} | |||
}, true); | |||
document.addEventListener("selectstart", function (e) { | |||
if (e.target.closest && e.target.closest(".item-wrapper")) { | |||
e.preventDefault(); | |||
} | |||
}, true); | |||
} | |||
if (document.readyState === "loading") { | |||
document.addEventListener("DOMContentLoaded", function () { | |||
hydrateAllIcons(); | |||
watchIconHydration(); | |||
}); | |||
} else { | |||
hydrateAllIcons(); | |||
watchIconHydration(); | |||
} | |||
protectItemWrapperEvents(); | |||
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( | tip.classList.add("tooltip-visible"); | ||
tip.classList.remove( | tip.classList.remove("tooltip-below", "tooltip-shift-right", "tooltip-shift-left"); | ||
tip.style.removeProperty( | 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 303: | Linha 611: | ||
} else if (below + tipH <= window.innerHeight - MARGIN) { | } else if (below + tipH <= window.innerHeight - MARGIN) { | ||
top = below; | top = below; | ||
tip.classList.add( | tip.classList.add("tooltip-below"); | ||
} else { | } else { | ||
top = Math.max(MARGIN, Math.min(above, window.innerHeight - MARGIN - tipH)); | top = Math.max(MARGIN, Math.min(above, window.innerHeight - MARGIN - tipH)); | ||
| Linha 312: | Linha 620: | ||
if (left < MARGIN) { | if (left < MARGIN) { | ||
left = Math.max(MARGIN, rect.left); | left = Math.max(MARGIN, rect.left); | ||
tip.classList.add( | tip.classList.add("tooltip-shift-right"); | ||
tip.style.setProperty( | tip.style.setProperty("--arrow-offset", Math.max(12, centerX - left) + "px"); | ||
} else if (left + tipW > window.innerWidth - MARGIN) { | } else if (left + tipW > window.innerWidth - MARGIN) { | ||
left = Math.min(window.innerWidth - MARGIN - tipW, rect.right - tipW); | left = Math.min(window.innerWidth - MARGIN - tipW, rect.right - tipW); | ||
tip.classList.add( | tip.classList.add("tooltip-shift-left"); | ||
tip.style.setProperty( | tip.style.setProperty("--arrow-offset", Math.max(12, (left + tipW) - centerX) + "px"); | ||
} | } | ||
tip.style.top = top + | tip.style.top = top + "px"; | ||
tip.style.left = left + | tip.style.left = left + "px"; | ||
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); | |||
} | } | ||
function hideTooltip() { | function hideTooltip() { | ||
if (activeTip && activeWrapper) { | if (activeTip && activeWrapper) { | ||
activeTip.classList.remove( | activeTip.classList.remove("tooltip-visible", "tooltip-below", "tooltip-shift-right", "tooltip-shift-left"); | ||
activeTip.style.top = | activeTip.style.top = ""; | ||
activeTip.style.left = | activeTip.style.left = ""; | ||
activeTip.style.removeProperty( | activeTip.style.visibility = ""; | ||
activeTip.style.removeProperty("--arrow-offset"); | |||
wipeTooltipBody(activeTip); | |||
activeWrapper.appendChild(activeTip); | activeWrapper.appendChild(activeTip); | ||
activeTip = null; | activeTip = null; | ||
| Linha 336: | Linha 661: | ||
} | } | ||
document.addEventListener( | document.addEventListener("mouseover", function (e) { | ||
if (e.isTrusted === false) { | |||
var wrapper = | untrustedStrikes++; | ||
return; | |||
} | |||
var wrapper = e.target.closest ? e.target.closest(".item-wrapper") : null; | |||
if (wrapper && wrapper !== activeWrapper) { | if (wrapper && wrapper !== activeWrapper) { | ||
showTooltip(wrapper); | showTooltip(wrapper); | ||
| Linha 344: | Linha 672: | ||
}); | }); | ||
document.addEventListener( | document.addEventListener("mouseout", function (e) { | ||
if (!activeWrapper) return; | if (!activeWrapper) return; | ||
var wrapper = e.target.closest ? e.target.closest( | var wrapper = e.target.closest ? e.target.closest(".item-wrapper") : null; | ||
if (wrapper === activeWrapper && !activeWrapper.contains(e.relatedTarget)) { | if (wrapper === activeWrapper && !activeWrapper.contains(e.relatedTarget)) { | ||
hideTooltip(); | hideTooltip(); | ||
} | } | ||
}); | }); | ||
document.addEventListener("scroll", hideTooltip, true); | |||
})(); | })(); | ||
</script> | </script> | ||
</includeonly> | </includeonly> | ||