MediaWiki:Common.js: Difference between revisions
Aparência da Noite
Restaurando exatamente rev 403 original Tag: Manual revert |
v23 fix: restaura polling com auto-limpeza para botoes de censura (VE + codigo-fonte) |
||
| (4 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* ========================================================================== | /* ========================================================================== | ||
VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, | VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, VIEWPORT RESPONSIVO & SEGREDO DINÂMICO | ||
========================================================================== */ | ========================================================================== */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
/* ------------------------------------------------------------------ | |||
UTILITÁRIO: POLLING LIMITADO (substitui setInterval infinito) | |||
------------------------------------------------------------------ */ | |||
function pollUntilFound(checkFn, callback, maxAttempts) { | |||
var attempts = 0; | |||
var max = maxAttempts || 50; | |||
var intervalId = setInterval(function () { | |||
attempts++; | |||
var result = checkFn(); | |||
if (result) { | |||
clearInterval(intervalId); | |||
callback(result); | |||
} else if (attempts >= max) { | |||
clearInterval(intervalId); | |||
} | |||
}, 400); | |||
return intervalId; | |||
} | |||
/* ------------------------------------------------------------------ | |||
0. VIEWPORT RESPONSIVO REAL PARA MOBILE (SUBSTITUI WIDTH=1120) | |||
------------------------------------------------------------------ */ | |||
function fixMobileViewport() { | |||
var meta = document.querySelector('meta[name="viewport"]'); | |||
if (meta) { | |||
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover'); | |||
} else { | |||
meta = document.createElement('meta'); | |||
meta.name = 'viewport'; | |||
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover'; | |||
document.head.appendChild(meta); | |||
} | |||
} | |||
fixMobileViewport(); | |||
/* ------------------------------------------------------------------ | /* ------------------------------------------------------------------ | ||
| Line 56: | Line 91: | ||
/* ------------------------------------------------------------------ | /* ------------------------------------------------------------------ | ||
2. INJEÇÃO DO BOTÃO "🔒 CENSURAR" | 2. INJEÇÃO DO BOTÃO "🔒 CENSURAR" NA BARRA DO VISUALEDITOR | ||
------------------------------------------------------------------ */ | ------------------------------------------------------------------ */ | ||
function injectVisualEditorButton() { | function injectVisualEditorButton() { | ||
var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools'); | var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools'); | ||
if (!toolbar) return; | if (!toolbar) return; | ||
| Line 90: | Line 124: | ||
try { | try { | ||
var target = ve.init.target; | var target = ve.init.target; | ||
if (target && target.getSurface()) { | if (target && target.getSurface()) { | ||
| Line 125: | Line 158: | ||
} | } | ||
var tb = document.getElementById('wpTextbox1'); | var tb = document.getElementById('wpTextbox1'); | ||
if (tb) { | if (tb) { | ||
| Line 140: | Line 172: | ||
btnGroup.appendChild(btn); | btnGroup.appendChild(btn); | ||
var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child'); | var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child'); | ||
if (insertGroup && insertGroup.parentNode) { | if (insertGroup && insertGroup.parentNode) { | ||
| Line 148: | Line 179: | ||
} | } | ||
} | } | ||
/* ------------------------------------------------------------------ | /* ------------------------------------------------------------------ | ||
| Line 205: | Line 233: | ||
var b = document.createElement('button'); | var b = document.createElement('button'); | ||
b.type = 'button'; | b.type = 'button'; | ||
b.textContent = (label === 'CENSURADO' ? ' | b.textContent = (label === 'CENSURADO' ? '⬛ ' : '🔓 ') + label; | ||
b.style.cssText = 'flex:1;padding:10px 16px;border-radius:4px;font-weight:800;font-size:.9em;cursor:pointer;font-family:"Cinzel",Georgia,serif;letter-spacing:1px;transition:all .25s ease;outline:none;'; | b.style.cssText = 'flex:1;padding:10px 16px;border-radius:4px;font-weight:800;font-size:.9em;cursor:pointer;font-family:"Cinzel",Georgia,serif;letter-spacing:1px;transition:all .25s ease;outline:none;'; | ||
return b; | return b; | ||
| Line 217: | Line 245: | ||
} | } | ||
setInterval(transformLiberadoInputs, 300); | /* --- MutationObserver para transformLiberadoInputs (substitui setInterval) --- */ | ||
function setupTransformObserver() { | |||
if (!document.body) return; | |||
var lastRun = 0; | |||
var pending = false; | |||
function throttledTransform() { | |||
var now = Date.now(); | |||
if (now - lastRun < 300) { | |||
if (!pending) { | |||
pending = true; | |||
setTimeout(function () { | |||
pending = false; | |||
lastRun = Date.now(); | |||
transformLiberadoInputs(); | |||
}, 300 - (now - lastRun)); | |||
} | |||
return; | |||
} | |||
lastRun = now; | |||
transformLiberadoInputs(); | |||
} | |||
var observer = new MutationObserver(function (mutations) { | |||
for (var i = 0; i < mutations.length; i++) { | |||
var addedNodes = mutations[i].addedNodes; | |||
for (var j = 0; j < addedNodes.length; j++) { | |||
var node = addedNodes[j]; | |||
if (node.nodeType === 1) { | |||
if (node.classList && (node.classList.contains('oo-ui-windowManager') || node.classList.contains('oo-ui-window-frame'))) { | |||
throttledTransform(); | |||
return; | |||
} | |||
if (node.querySelector && (node.querySelector('.oo-ui-windowManager') || node.querySelector('.oo-ui-window-frame'))) { | |||
throttledTransform(); | |||
return; | |||
} | |||
} | |||
} | |||
} | |||
}); | |||
observer.observe(document.body, { childList: true, subtree: true }); | |||
} | |||
/* ------------------------------------------------------------------ | /* ------------------------------------------------------------------ | ||
| Line 232: | Line 304: | ||
var lbl = document.createElement('span'); | var lbl = document.createElement('span'); | ||
lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;'; | lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;'; | ||
lbl.textContent = '🩸 Ferramentas:'; | lbl.textContent = '🩸 Ferramentas de Censura:'; | ||
bar.appendChild(lbl); | bar.appendChild(lbl); | ||
bar.appendChild(mkBtn(' | bar.appendChild(mkBtn('⬛ Censurar Trecho', '#240c12', '#ff2a3f', function () { | ||
wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado'); | wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado'); | ||
})); | })); | ||
bar.appendChild(mkBtn(' | bar.appendChild(mkBtn('🔓 Inserir Liberado', '#0d2915', '#2e7d32', function () { | ||
wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado'); | wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado'); | ||
})); | })); | ||
bar.appendChild(mkBtn(' | bar.appendChild(mkBtn('📁 Dossie Bloco', '#181822', '#3d0a12', function () { | ||
wrapSel(tb, '\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\n', '\n}}\n', 'Relatorio confidencial...'); | wrapSel(tb, '\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\n', '\n}}\n', 'Relatorio confidencial...'); | ||
})); | })); | ||
bar.appendChild(mkBtn(' | bar.appendChild(mkBtn('🗑 Remover Censura', '#1a1a22', '#4a4a58', function () { | ||
removeSecret(tb); | removeSecret(tb); | ||
})); | })); | ||
| Line 326: | Line 398: | ||
/* ------------------------------------------------------------------ | /* ------------------------------------------------------------------ | ||
INICIALIZAÇÃO | 6. SUPORTE A TOQUE PARA MENU HAMBÚRGUER NO MOBILE | ||
------------------------------------------------------------------ */ | |||
function initMobileMenuHelper() { | |||
var dropdown = document.getElementById('vector-main-menu-dropdown'); | |||
var checkbox = document.getElementById('vector-main-menu-dropdown-checkbox'); | |||
var label = document.getElementById('vector-main-menu-dropdown-label'); | |||
if (!dropdown || !checkbox) return; | |||
if (label && !label.dataset.vtmBound) { | |||
label.dataset.vtmBound = 'true'; | |||
label.addEventListener('click', function () { | |||
setTimeout(function () { | |||
if (checkbox.checked) { | |||
dropdown.classList.add('vector-dropdown--open'); | |||
} else { | |||
dropdown.classList.remove('vector-dropdown--open'); | |||
} | |||
}, 10); | |||
}); | |||
} | |||
document.addEventListener('click', function (e) { | |||
if (checkbox.checked && !dropdown.contains(e.target)) { | |||
checkbox.checked = false; | |||
dropdown.classList.remove('vector-dropdown--open'); | |||
} | |||
}); | |||
} | |||
/* ------------------------------------------------------------------ | |||
INICIALIZAÇÃO GERAL | |||
------------------------------------------------------------------ */ | ------------------------------------------------------------------ */ | ||
function initAll() { | function initAll() { | ||
fixMobileViewport(); | |||
initVtmSecrets(); | initVtmSecrets(); | ||
initSectionAnimation(); | initSectionAnimation(); | ||
initVtmQuickToolbar(); | initVtmQuickToolbar(); | ||
injectVisualEditorButton(); | injectVisualEditorButton(); | ||
initMobileMenuHelper(); | |||
setupTransformObserver(); | |||
} | } | ||
| Line 341: | Line 446: | ||
} | } | ||
if (window.mw | /* --- Polling com auto-limpeza para botões de censura nos editores --- */ | ||
var veButtonInterval = setInterval(function () { | |||
if (!document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools')) return; | |||
injectVisualEditorButton(); | |||
if (document.getElementById('vtm-ve-censor-button')) { | |||
clearInterval(veButtonInterval); | |||
} | |||
}, 400); | |||
var quickbarInterval = setInterval(function () { | |||
if (!document.getElementById('wpTextbox1')) return; | |||
initVtmQuickToolbar(); | |||
if (document.getElementById('vtm-editor-quickbar')) { | |||
clearInterval(quickbarInterval); | |||
} | |||
}, 400); | |||
/* --- Hooks do MediaWiki para recarregamento de conteúdo --- */ | |||
function registerHooks() { | |||
if (!window.mw || !window.mw.hook) return; | |||
window.mw.hook('wikipage.content').add(function () { | window.mw.hook('wikipage.content').add(function () { | ||
initVtmSecrets(); | initVtmSecrets(); | ||
initSectionAnimation(); | initSectionAnimation(); | ||
initMobileMenuHelper(); | |||
}); | |||
window.mw.hook('ve.activationComplete').add(function () { | |||
pollUntilFound(function () { | |||
return document.querySelector('.ve-ui-toolbar-tools'); | |||
}, function () { | |||
injectVisualEditorButton(); | |||
}); | |||
}); | |||
window.mw.hook('wikipage.editform').add(function () { | |||
initVtmQuickToolbar(); | |||
}); | |||
} | |||
if (window.mw && window.mw.hook) { | |||
registerHooks(); | |||
} else { | |||
pollUntilFound(function () { | |||
return window.mw && window.mw.hook; | |||
}, function () { | |||
registerHooks(); | |||
}); | }); | ||
} | } | ||
})(); | })(); | ||
Latest revision as of 22:52, 1 September 2026
/* ==========================================================================
VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, VIEWPORT RESPONSIVO & SEGREDO DINÂMICO
========================================================================== */
(function () {
'use strict';
/* ------------------------------------------------------------------
UTILITÁRIO: POLLING LIMITADO (substitui setInterval infinito)
------------------------------------------------------------------ */
function pollUntilFound(checkFn, callback, maxAttempts) {
var attempts = 0;
var max = maxAttempts || 50;
var intervalId = setInterval(function () {
attempts++;
var result = checkFn();
if (result) {
clearInterval(intervalId);
callback(result);
} else if (attempts >= max) {
clearInterval(intervalId);
}
}, 400);
return intervalId;
}
/* ------------------------------------------------------------------
0. VIEWPORT RESPONSIVO REAL PARA MOBILE (SUBSTITUI WIDTH=1120)
------------------------------------------------------------------ */
function fixMobileViewport() {
var meta = document.querySelector('meta[name="viewport"]');
if (meta) {
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover');
} else {
meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover';
document.head.appendChild(meta);
}
}
fixMobileViewport();
/* ------------------------------------------------------------------
1. CENSURA & REVELAÇÃO DINÂMICA (LEITURA)
------------------------------------------------------------------ */
function initVtmSecrets() {
var els = document.querySelectorAll('[data-vtm-secret="true"]');
if (!els || !els.length) return;
var now = new Date();
els.forEach(function (el) {
var lib = (el.getAttribute('data-liberado') || '').toLowerCase().trim();
var ds = (el.getAttribute('data-reveal-date') || '').trim();
var dica = (el.getAttribute('data-dica') || '').trim();
var revealed = false;
var tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA]';
if (lib === 'sim' || lib === 'true' || lib === '1') {
revealed = true;
} else if (lib === 'nao' || lib === 'false' || lib === '0') {
tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
} else if (ds) {
var td = null;
if (ds.indexOf('/') !== -1) {
var p = ds.split('/');
if (p.length === 3) td = new Date(p[2], parseInt(p[1], 10) - 1, p[0]);
} else {
td = new Date(ds);
}
if (td && !isNaN(td.getTime())) {
if (now >= td) {
revealed = true;
} else {
var f = ('0' + td.getDate()).slice(-2) + '/' + ('0' + (td.getMonth() + 1)).slice(-2) + '/' + td.getFullYear();
tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ' - ' + f + ']';
}
}
}
if (revealed) {
el.classList.add('is-revealed');
el.classList.remove('is-redacted');
el.title = '[DESCLASSIFICADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';
} else {
el.classList.add('is-redacted');
el.classList.remove('is-revealed');
el.title = tip;
}
});
}
/* ------------------------------------------------------------------
2. INJEÇÃO DO BOTÃO "🔒 CENSURAR" NA BARRA DO VISUALEDITOR
------------------------------------------------------------------ */
function injectVisualEditorButton() {
var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools');
if (!toolbar) return;
if (document.getElementById('vtm-ve-censor-button')) return;
var btnGroup = document.createElement('div');
btnGroup.id = 'vtm-ve-censor-button';
btnGroup.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-toolGroup';
btnGroup.style.cssText = 'display:inline-flex; align-items:center; margin:0 6px;';
var btn = document.createElement('button');
btn.type = 'button';
btn.innerHTML = '🔒 Censurar Trecho';
btn.title = 'Aplicar tarja de censura {{Segredo}} no texto selecionado';
btn.style.cssText = 'background:linear-gradient(180deg, #450810 0%, #1a0306 100%); color:#ffffff; border:1px solid #ff2a3f; border-radius:4px; padding:4px 10px; font-family:"Cinzel", Georgia, serif; font-size:0.82em; font-weight:700; cursor:pointer; box-shadow:0 0 8px rgba(255,42,63,0.4); display:flex; align-items:center; gap:4px; transition:all 0.2s ease; height:32px; line-height:1; outline:none;';
btn.onmouseover = function () {
btn.style.background = 'linear-gradient(180deg, #6b0c19 0%, #30050c 100%)';
btn.style.boxShadow = '0 0 14px rgba(255,42,63,0.85)';
};
btn.onmouseout = function () {
btn.style.background = 'linear-gradient(180deg, #450810 0%, #1a0306 100%)';
btn.style.boxShadow = '0 0 8px rgba(255,42,63,0.4)';
};
btn.onclick = function (e) {
e.preventDefault();
e.stopPropagation();
try {
var target = ve.init.target;
if (target && target.getSurface()) {
var surfaceModel = target.getSurface().getModel();
var fragment = surfaceModel.getFragment();
var selectedText = fragment.getText();
var contentToCensor = selectedText || 'Texto confidencial a ser censurado';
var transclusionNode = {
type: 'mwTransclusionInline',
attributes: {
mw: {
parts: [
{
template: {
target: { href: 'Template:Segredo', wt: 'Segredo' },
params: {
'1': { wt: contentToCensor },
'liberado': { wt: 'nao' }
}
}
}
]
}
}
};
fragment.insertContent([transclusionNode, { type: '/mwTransclusionInline' }]);
return;
}
} catch (err) {
if (window.console) console.warn('[VtM] Fallback de inserção:', err);
}
var tb = document.getElementById('wpTextbox1');
if (tb) {
var s = tb.selectionStart, end = tb.selectionEnd;
var t = tb.value, sel = t.substring(s, end) || 'Texto confidencial a ser censurado';
var rep = '{{Segredo|' + sel + '|liberado=nao}}';
tb.value = t.substring(0, s) + rep + t.substring(end);
tb.focus();
tb.setSelectionRange(s + 10, s + 10 + sel.length);
if (window.$) { $(tb).trigger('input').trigger('change'); }
}
};
btnGroup.appendChild(btn);
var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child');
if (insertGroup && insertGroup.parentNode) {
insertGroup.parentNode.insertBefore(btnGroup, insertGroup.nextSibling);
} else {
toolbar.appendChild(btnGroup);
}
}
/* ------------------------------------------------------------------
3. BOTÕES CENSURADO/LIBERADO NO MODAL DO VISUALEDITOR
------------------------------------------------------------------ */
function transformLiberadoInputs() {
var inputs = document.querySelectorAll(
'.oo-ui-windowManager input, .oo-ui-windowManager textarea, ' +
'.oo-ui-fieldLayout input, .oo-ui-fieldLayout textarea'
);
inputs.forEach(function (input) {
var container = input.closest('.oo-ui-fieldLayout') ||
input.closest('.ve-ui-mwParameterPage') ||
input.closest('.oo-ui-pageLayout') ||
input.parentElement;
if (!container) return;
var txt = (container.textContent || '').toLowerCase();
if (txt.indexOf('liberado') === -1) return;
if (container.querySelector('.vtm-toggle-box')) return;
var box = document.createElement('div');
box.className = 'vtm-toggle-box';
box.style.cssText = 'display:flex;gap:12px;margin:10px 0 16px;width:100%;max-width:460px;';
var btnC = mkToggle('CENSURADO');
var btnL = mkToggle('LIBERADO');
function paint(v) {
var isLib = (v === 'sim' || v === '1' || v === 'true');
btnC.style.background = isLib ? '#121218' : 'linear-gradient(180deg,#5c0f18,#2b060a)';
btnC.style.color = isLib ? '#7a7a88' : '#fff';
btnC.style.border = isLib ? '1px solid #2d2d38' : '2px solid #ff2a3f';
btnC.style.boxShadow = isLib ? 'none' : '0 0 12px rgba(255,42,63,.7)';
btnL.style.background = isLib ? 'linear-gradient(180deg,#1b5e20,#0d3814)' : '#121218';
btnL.style.color = isLib ? '#fff' : '#7a7a88';
btnL.style.border = isLib ? '2px solid #4caf50' : '1px solid #2d2d38';
btnL.style.boxShadow = isLib ? '0 0 12px rgba(76,175,80,.7)' : 'none';
}
btnC.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'nao'); paint('nao'); };
btnL.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'sim'); paint('sim'); };
input.addEventListener('input', function () { paint((input.value || '').toLowerCase().trim()); });
paint((input.value || 'nao').toLowerCase().trim());
box.appendChild(btnC);
box.appendChild(btnL);
var wrap = input.closest('.oo-ui-textInputWidget') || input.closest('.oo-ui-inputWidget') || input;
wrap.parentNode.insertBefore(box, wrap);
wrap.style.display = 'none';
});
}
function mkToggle(label) {
var b = document.createElement('button');
b.type = 'button';
b.textContent = (label === 'CENSURADO' ? '⬛ ' : '🔓 ') + label;
b.style.cssText = 'flex:1;padding:10px 16px;border-radius:4px;font-weight:800;font-size:.9em;cursor:pointer;font-family:"Cinzel",Georgia,serif;letter-spacing:1px;transition:all .25s ease;outline:none;';
return b;
}
function setVal(input, v) {
input.value = v;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
if (window.$) { $(input).trigger('input').trigger('change'); }
}
/* --- MutationObserver para transformLiberadoInputs (substitui setInterval) --- */
function setupTransformObserver() {
if (!document.body) return;
var lastRun = 0;
var pending = false;
function throttledTransform() {
var now = Date.now();
if (now - lastRun < 300) {
if (!pending) {
pending = true;
setTimeout(function () {
pending = false;
lastRun = Date.now();
transformLiberadoInputs();
}, 300 - (now - lastRun));
}
return;
}
lastRun = now;
transformLiberadoInputs();
}
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var addedNodes = mutations[i].addedNodes;
for (var j = 0; j < addedNodes.length; j++) {
var node = addedNodes[j];
if (node.nodeType === 1) {
if (node.classList && (node.classList.contains('oo-ui-windowManager') || node.classList.contains('oo-ui-window-frame'))) {
throttledTransform();
return;
}
if (node.querySelector && (node.querySelector('.oo-ui-windowManager') || node.querySelector('.oo-ui-window-frame'))) {
throttledTransform();
return;
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
/* ------------------------------------------------------------------
4. BARRA DE FERRAMENTAS NO EDITOR DE CÓDIGO-FONTE (#wpTextbox1)
------------------------------------------------------------------ */
function initVtmQuickToolbar() {
var tb = document.getElementById('wpTextbox1');
if (!tb || document.getElementById('vtm-editor-quickbar')) return;
var bar = document.createElement('div');
bar.id = 'vtm-editor-quickbar';
bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:10px 14px;margin:10px 0;background:linear-gradient(90deg,#181014,#0d0d12);border:1px solid #3d0a12;border-left:4px solid #8b0000;border-radius:4px;font-family:"Cinzel",Georgia,serif;box-shadow:0 4px 14px rgba(0,0,0,.6);z-index:99;';
var lbl = document.createElement('span');
lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;';
lbl.textContent = '🩸 Ferramentas de Censura:';
bar.appendChild(lbl);
bar.appendChild(mkBtn('⬛ Censurar Trecho', '#240c12', '#ff2a3f', function () {
wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado');
}));
bar.appendChild(mkBtn('🔓 Inserir Liberado', '#0d2915', '#2e7d32', function () {
wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado');
}));
bar.appendChild(mkBtn('📁 Dossie Bloco', '#181822', '#3d0a12', function () {
wrapSel(tb, '\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\n', '\n}}\n', 'Relatorio confidencial...');
}));
bar.appendChild(mkBtn('🗑 Remover Censura', '#1a1a22', '#4a4a58', function () {
removeSecret(tb);
}));
tb.parentNode.insertBefore(bar, tb);
}
function mkBtn(text, bg, border, fn) {
var b = document.createElement('button');
b.type = 'button'; b.textContent = text;
b.style.cssText = 'background:' + bg + ';color:#fff;border:1px solid ' + border + ';border-radius:3px;padding:6px 12px;font-size:.82em;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;gap:4px;transition:all .2s ease;';
b.onmouseover = function () { b.style.opacity = '.85'; };
b.onmouseout = function () { b.style.opacity = '1'; };
b.onclick = function (e) { e.preventDefault(); fn(); };
return b;
}
function wrapSel(tb, pre, post, ph) {
var s = tb.selectionStart, e = tb.selectionEnd;
var t = tb.value, sel = t.substring(s, e) || ph;
tb.value = t.substring(0, s) + pre + sel + post + t.substring(e);
tb.focus(); tb.setSelectionRange(s + pre.length, s + pre.length + sel.length);
if (window.$) { $(tb).trigger('input').trigger('change'); }
}
function removeSecret(tb) {
var s = tb.selectionStart, text = tb.value;
var i = text.lastIndexOf('{{Segredo|', s);
if (i === -1) { alert('Nenhum {{Segredo|...}} encontrado.\nPosicione o cursor dentro do trecho.'); return; }
var depth = 0, j, end = -1;
for (j = i; j < text.length - 1; j++) {
if (text[j] === '{' && text[j + 1] === '{') { depth++; j++; }
else if (text[j] === '}' && text[j + 1] === '}') { depth--; if (depth === 0) { end = j + 2; break; } j++; }
}
if (end === -1) { alert('Fechamento }} nao encontrado.'); return; }
var inner = text.substring(i + '{{Segredo|'.length, end - 2);
var parts = [], cur = '', d = 0;
for (j = 0; j < inner.length; j++) {
if (inner[j] === '{' && j + 1 < inner.length && inner[j + 1] === '{') { d++; cur += '{{'; j++; }
else if (inner[j] === '}' && j + 1 < inner.length && inner[j + 1] === '}') { d--; cur += '}}'; j++; }
else if (inner[j] === '|' && d === 0) { parts.push(cur); cur = ''; }
else { cur += inner[j]; }
}
parts.push(cur);
var content = '';
for (var k = 0; k < parts.length; k++) {
if (parts[k].trim().indexOf('=') === -1) { content = parts[k].trim(); break; }
}
tb.value = text.substring(0, i) + content + text.substring(end);
tb.focus(); tb.setSelectionRange(i, i + content.length);
if (window.$) { $(tb).trigger('input').trigger('change'); }
}
/* ------------------------------------------------------------------
5. ANIMAÇÃO DE CASCATA DAS SEÇÕES
------------------------------------------------------------------ */
function initSectionAnimation() {
var c = document.querySelector('.mw-parser-output');
if (!c || c.dataset.vtmAnimated === 'true') return;
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
c.dataset.vtmAnimated = 'true';
var kids = Array.from(c.children), secs = [], cur = [];
kids.forEach(function (ch) {
if (ch.classList.contains('catlinks') || ch.classList.contains('mw-category-generated') ||
ch.tagName === 'SCRIPT' || ch.tagName === 'STYLE' || ch.id === 'vtm-editor-quickbar') {
if (cur.length) { secs.push(cur); cur = []; }
return;
}
if ((ch.tagName === 'H2' || ch.classList.contains('mw-heading2')) && cur.length) { secs.push(cur); cur = []; }
cur.push(ch);
});
if (cur.length) secs.push(cur);
secs.forEach(function (items, idx) {
var w = document.createElement('div');
w.className = 'vtm-section-block';
w.style.animationDelay = (0.12 + idx * 0.42).toFixed(2) + 's';
var f = items[0];
if (f && f.parentNode) { f.parentNode.insertBefore(w, f); items.forEach(function (el) { w.appendChild(el); }); }
});
}
/* ------------------------------------------------------------------
6. SUPORTE A TOQUE PARA MENU HAMBÚRGUER NO MOBILE
------------------------------------------------------------------ */
function initMobileMenuHelper() {
var dropdown = document.getElementById('vector-main-menu-dropdown');
var checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
var label = document.getElementById('vector-main-menu-dropdown-label');
if (!dropdown || !checkbox) return;
if (label && !label.dataset.vtmBound) {
label.dataset.vtmBound = 'true';
label.addEventListener('click', function () {
setTimeout(function () {
if (checkbox.checked) {
dropdown.classList.add('vector-dropdown--open');
} else {
dropdown.classList.remove('vector-dropdown--open');
}
}, 10);
});
}
document.addEventListener('click', function (e) {
if (checkbox.checked && !dropdown.contains(e.target)) {
checkbox.checked = false;
dropdown.classList.remove('vector-dropdown--open');
}
});
}
/* ------------------------------------------------------------------
INICIALIZAÇÃO GERAL
------------------------------------------------------------------ */
function initAll() {
fixMobileViewport();
initVtmSecrets();
initSectionAnimation();
initVtmQuickToolbar();
injectVisualEditorButton();
initMobileMenuHelper();
setupTransformObserver();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAll);
} else {
initAll();
}
/* --- Polling com auto-limpeza para botões de censura nos editores --- */
var veButtonInterval = setInterval(function () {
if (!document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools')) return;
injectVisualEditorButton();
if (document.getElementById('vtm-ve-censor-button')) {
clearInterval(veButtonInterval);
}
}, 400);
var quickbarInterval = setInterval(function () {
if (!document.getElementById('wpTextbox1')) return;
initVtmQuickToolbar();
if (document.getElementById('vtm-editor-quickbar')) {
clearInterval(quickbarInterval);
}
}, 400);
/* --- Hooks do MediaWiki para recarregamento de conteúdo --- */
function registerHooks() {
if (!window.mw || !window.mw.hook) return;
window.mw.hook('wikipage.content').add(function () {
initVtmSecrets();
initSectionAnimation();
initMobileMenuHelper();
});
window.mw.hook('ve.activationComplete').add(function () {
pollUntilFound(function () {
return document.querySelector('.ve-ui-toolbar-tools');
}, function () {
injectVisualEditorButton();
});
});
window.mw.hook('wikipage.editform').add(function () {
initVtmQuickToolbar();
});
}
if (window.mw && window.mw.hook) {
registerHooks();
} else {
pollUntilFound(function () {
return window.mw && window.mw.hook;
}, function () {
registerHooks();
});
}
})();