Mudanças entre as edições de "Widget:Item"
Ir para navegação
Ir para pesquisar
m |
m |
||
| (2 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 3: | Linha 3: | ||
Tooltip: payload codificado em data-tip (Módulo:Item). Corpo vazio no HTML. | 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. | 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. | Defesa leve: hover real + rate limit de decodes para automação. | ||
--> | --> | ||
| Linha 33: | Linha 35: | ||
overflow: visible; | overflow: visible; | ||
filter: none; | 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; | ||
| Linha 40: | Linha 61: | ||
filter: none; | filter: none; | ||
box-shadow: none; | box-shadow: none; | ||
-webkit-user-select: none; | |||
user-select: none; | |||
-webkit-user-drag: none; | |||
-webkit-touch-callout: none; | |||
pointer-events: none; | |||
} | } | ||
| Linha 267: | Linha 293: | ||
var MARGIN = 8; | var MARGIN = 8; | ||
var GAP = 6; | var GAP = 6; | ||
var XOR_KEY = 0x5A; | 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; | ||
| Linha 309: | Linha 335: | ||
} | } | ||
function | function bytesToUtf8String(bytes) { | ||
if (typeof TextDecoder !== "undefined") { | if (typeof TextDecoder !== "undefined") { | ||
return new TextDecoder("utf-8").decode(new Uint8Array(bytes)); | return new TextDecoder("utf-8").decode( | ||
bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes) | |||
); | |||
} | } | ||
var bin = ""; | var bin = ""; | ||
| Linha 321: | Linha 345: | ||
bin += String.fromCharCode(bytes[j]); | bin += String.fromCharCode(bytes[j]); | ||
} | } | ||
return decodeURIComponent(escape(bin)); | |||
return | } | ||
} | |||
return | 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( | function decodePayload(raw) { | ||
if (! | if (!raw) return null; | ||
if (decodeCache[ | if (decodeCache[raw]) return decodeCache[raw]; | ||
try { | try { | ||
var obj = JSON.parse( | var obj = JSON.parse(payloadToJson(raw)); | ||
decodeCache[ | decodeCache[raw] = obj; | ||
return obj; | return obj; | ||
} catch (e) { | } catch (e) { | ||
| Linha 339: | Linha 384: | ||
} | } | ||
} | } | ||
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) { | function appendText(el, text) { | ||