MediaWiki:Common.js
Aparência da Noite
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* ==========================================================================\n VAMPIRO: A MÁSCARA — SCRIPTS GLOBAIS, BOTÃO DE CENSURA & SEGREDO DINÂMICO\n ========================================================================== */\n\n(function () {\n 'use strict';\n\n /* ------------------------------------------------------------------\n 1. CENSURA & REVELAÇÃO DINÂMICA (LEITURA)\n ------------------------------------------------------------------ */\n function initVtmSecrets() {\n var els = document.querySelectorAll('[data-vtm-secret=\"true\"]');\n if (!els || !els.length) return;\n var now = new Date();\n\n els.forEach(function (el) {\n var lib = (el.getAttribute('data-liberado') || '').toLowerCase().trim();\n var ds = (el.getAttribute('data-reveal-date') || '').trim();\n var dica = (el.getAttribute('data-dica') || '').trim();\n var revealed = false;\n var tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA]';\n\n if (lib === 'sim' || lib === 'true' || lib === '1') {\n revealed = true;\n } else if (lib === 'nao' || lib === 'false' || lib === '0') {\n tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';\n } else if (ds) {\n var td = null;\n if (ds.indexOf('/') !== -1) {\n var p = ds.split('/');\n if (p.length === 3) td = new Date(p[2], parseInt(p[1], 10) - 1, p[0]);\n } else {\n td = new Date(ds);\n }\n if (td && !isNaN(td.getTime())) {\n if (now >= td) {\n revealed = true;\n } else {\n var f = ('0' + td.getDate()).slice(-2) + '/' + ('0' + (td.getMonth() + 1)).slice(-2) + '/' + td.getFullYear();\n tip = '[INFORMAÇÃO SENSÍVEL - BLOQUEADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ' - ' + f + ']';\n }\n }\n }\n\n if (revealed) {\n el.classList.add('is-revealed');\n el.classList.remove('is-redacted');\n el.title = '[DESCLASSIFICADO PELA CAMARILLA' + (dica ? ' // ' + dica : '') + ']';\n } else {\n el.classList.add('is-redacted');\n el.classList.remove('is-revealed');\n el.title = tip;\n }\n });\n }\n\n /* ------------------------------------------------------------------\n 2. INJEÇÃO DO BOTÃO \"🔒 CENSURAR\" DIRETAMENTE NA BARRA DO VISUALEDITOR\n ------------------------------------------------------------------ */\n function injectVisualEditorButton() {\n // Procura a barra de ferramentas ativa do VisualEditor\n var toolbar = document.querySelector('.ve-ui-toolbar-tools, .ve-init-mw-desktopArticleTarget-toolbar .oo-ui-toolbar-tools');\n if (!toolbar) return;\n\n if (document.getElementById('vtm-ve-censor-button')) return;\n\n var btnGroup = document.createElement('div');\n btnGroup.id = 'vtm-ve-censor-button';\n btnGroup.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-toolGroup';\n btnGroup.style.cssText = 'display:inline-flex; align-items:center; margin:0 6px;';\n\n var btn = document.createElement('button');\n btn.type = 'button';\n btn.innerHTML = '🔒 Censurar Trecho';\n btn.title = 'Aplicar tarja de censura {{Segredo}} no texto selecionado';\n 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;';\n\n btn.onmouseover = function () {\n btn.style.background = 'linear-gradient(180deg, #6b0c19 0%, #30050c 100%)';\n btn.style.boxShadow = '0 0 14px rgba(255,42,63,0.85)';\n };\n btn.onmouseout = function () {\n btn.style.background = 'linear-gradient(180deg, #450810 0%, #1a0306 100%)';\n btn.style.boxShadow = '0 0 8px rgba(255,42,63,0.4)';\n };\n\n btn.onclick = function (e) {\n e.preventDefault();\n e.stopPropagation();\n\n try {\n // Tenta usar a API nativa do VisualEditor para inserir a predefinição\n var target = ve.init.target;\n if (target && target.getSurface()) {\n var surfaceModel = target.getSurface().getModel();\n var fragment = surfaceModel.getFragment();\n var selectedText = fragment.getText();\n\n var contentToCensor = selectedText || 'Texto confidencial a ser censurado';\n\n var transclusionNode = {\n type: 'mwTransclusionInline',\n attributes: {\n mw: {\n parts: [\n {\n template: {\n target: { href: 'Template:Segredo', wt: 'Segredo' },\n params: {\n '1': { wt: contentToCensor },\n 'liberado': { wt: 'nao' }\n }\n }\n }\n ]\n }\n }\n };\n\n fragment.insertContent([transclusionNode, { type: '/mwTransclusionInline' }]);\n return;\n }\n } catch (err) {\n if (window.console) console.warn('[VtM] Fallback de inserção:', err);\n }\n\n // Fallback: se estiver no modo código-fonte do VE ou textarea\n var tb = document.getElementById('wpTextbox1');\n if (tb) {\n var s = tb.selectionStart, end = tb.selectionEnd;\n var t = tb.value, sel = t.substring(s, end) || 'Texto confidencial a ser censurado';\n var rep = '{{Segredo|' + sel + '|liberado=nao}}';\n tb.value = t.substring(0, s) + rep + t.substring(end);\n tb.focus();\n tb.setSelectionRange(s + 10, s + 10 + sel.length);\n if (window.$) { $(tb).trigger('input').trigger('change'); }\n }\n };\n\n btnGroup.appendChild(btn);\n\n // Injeta após o grupo de inserção ou no final da barra de ferramentas\n var insertGroup = toolbar.querySelector('.ve-ui-toolbar-group-insert, .oo-ui-toolGroup:last-child');\n if (insertGroup && insertGroup.parentNode) {\n insertGroup.parentNode.insertBefore(btnGroup, insertGroup.nextSibling);\n } else {\n toolbar.appendChild(btnGroup);\n }\n }\n\n // Monitora a abertura do VisualEditor para injetar o botão no topo\n setInterval(injectVisualEditorButton, 300);\n\n /* ------------------------------------------------------------------\n 3. BOTÕES CENSURADO/LIBERADO NO MODAL DO VISUALEDITOR\n ------------------------------------------------------------------ */\n function transformLiberadoInputs() {\n var inputs = document.querySelectorAll(\n '.oo-ui-windowManager input, .oo-ui-windowManager textarea, ' +\n '.oo-ui-fieldLayout input, .oo-ui-fieldLayout textarea'\n );\n inputs.forEach(function (input) {\n var container = input.closest('.oo-ui-fieldLayout') ||\n input.closest('.ve-ui-mwParameterPage') ||\n input.closest('.oo-ui-pageLayout') ||\n input.parentElement;\n if (!container) return;\n var txt = (container.textContent || '').toLowerCase();\n if (txt.indexOf('liberado') === -1) return;\n if (container.querySelector('.vtm-toggle-box')) return;\n\n var box = document.createElement('div');\n box.className = 'vtm-toggle-box';\n box.style.cssText = 'display:flex;gap:12px;margin:10px 0 16px;width:100%;max-width:460px;';\n\n var btnC = mkToggle('CENSURADO');\n var btnL = mkToggle('LIBERADO');\n\n function paint(v) {\n var isLib = (v === 'sim' || v === '1' || v === 'true');\n btnC.style.background = isLib ? '#121218' : 'linear-gradient(180deg,#5c0f18,#2b060a)';\n btnC.style.color = isLib ? '#7a7a88' : '#fff';\n btnC.style.border = isLib ? '1px solid #2d2d38' : '2px solid #ff2a3f';\n btnC.style.boxShadow = isLib ? 'none' : '0 0 12px rgba(255,42,63,.7)';\n btnL.style.background = isLib ? 'linear-gradient(180deg,#1b5e20,#0d3814)' : '#121218';\n btnL.style.color = isLib ? '#fff' : '#7a7a88';\n btnL.style.border = isLib ? '2px solid #4caf50' : '1px solid #2d2d38';\n btnL.style.boxShadow = isLib ? '0 0 12px rgba(76,175,80,.7)' : 'none';\n }\n\n btnC.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'nao'); paint('nao'); };\n btnL.onclick = function (e) { e.preventDefault(); e.stopPropagation(); setVal(input, 'sim'); paint('sim'); };\n input.addEventListener('input', function () { paint((input.value || '').toLowerCase().trim()); });\n paint((input.value || 'nao').toLowerCase().trim());\n\n box.appendChild(btnC);\n box.appendChild(btnL);\n var wrap = input.closest('.oo-ui-textInputWidget') || input.closest('.oo-ui-inputWidget') || input;\n wrap.parentNode.insertBefore(box, wrap);\n wrap.style.display = 'none';\n });\n }\n\n function mkToggle(label) {\n var b = document.createElement('button');\n b.type = 'button';\n b.textContent = (label === 'CENSURADO' ? '\\u2B1B ' : '\\uD83D\\uDD13 ') + label;\n 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;';\n return b;\n }\n\n function setVal(input, v) {\n input.value = v;\n input.dispatchEvent(new Event('input', { bubbles: true }));\n input.dispatchEvent(new Event('change', { bubbles: true }));\n if (window.$) { $(input).trigger('input').trigger('change'); }\n }\n\n setInterval(transformLiberadoInputs, 300);\n\n /* ------------------------------------------------------------------\n 4. BARRA DE FERRAMENTAS NO EDITOR DE CÓDIGO-FONTE (#wpTextbox1)\n ------------------------------------------------------------------ */\n function initVtmQuickToolbar() {\n var tb = document.getElementById('wpTextbox1');\n if (!tb || document.getElementById('vtm-editor-quickbar')) return;\n\n var bar = document.createElement('div');\n bar.id = 'vtm-editor-quickbar';\n 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;';\n\n var lbl = document.createElement('span');\n lbl.style.cssText = 'color:#c5a059;font-size:.85em;font-weight:700;text-transform:uppercase;letter-spacing:1px;margin-right:6px;';\n lbl.textContent = '🩸 Ferramentas:';\n bar.appendChild(lbl);\n\n bar.appendChild(mkBtn('\\u2B1B Censurar Trecho', '#240c12', '#ff2a3f', function () {\n wrapSel(tb, '{{Segredo|', '|liberado=nao}}', 'Texto censurado');\n }));\n bar.appendChild(mkBtn('\\uD83D\\uDD13 Inserir Liberado', '#0d2915', '#2e7d32', function () {\n wrapSel(tb, '{{Segredo|', '|liberado=sim}}', 'Texto revelado');\n }));\n bar.appendChild(mkBtn('\\uD83D\\uDCC1 Dossie Bloco', '#181822', '#3d0a12', function () {\n wrapSel(tb, '\\n{{Segredo|tipo=bloco|nivel=CONFIDENCIAL|data=AAAA-MM-DD|dica=Operacao|\\n', '\\n}}\\n', 'Relatorio confidencial...');\n }));\n bar.appendChild(mkBtn('\\uD83D\\uDDD1 Remover Censura', '#1a1a22', '#4a4a58', function () {\n removeSecret(tb);\n }));\n\n tb.parentNode.insertBefore(bar, tb);\n }\n\n function mkBtn(text, bg, border, fn) {\n var b = document.createElement('button');\n b.type = 'button'; b.textContent = text;\n 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;';\n b.onmouseover = function () { b.style.opacity = '.85'; };\n b.onmouseout = function () { b.style.opacity = '1'; };\n b.onclick = function (e) { e.preventDefault(); fn(); };\n return b;\n }\n\n function wrapSel(tb, pre, post, ph) {\n var s = tb.selectionStart, e = tb.selectionEnd;\n var t = tb.value, sel = t.substring(s, e) || ph;\n tb.value = t.substring(0, s) + pre + sel + post + t.substring(e);\n tb.focus(); tb.setSelectionRange(s + pre.length, s + pre.length + sel.length);\n if (window.$) { $(tb).trigger('input').trigger('change'); }\n }\n\n function removeSecret(tb) {\n var s = tb.selectionStart, text = tb.value;\n var i = text.lastIndexOf('{{Segredo|', s);\n if (i === -1) { alert('Nenhum {{Segredo|...}} encontrado.\\nPosicione o cursor dentro do trecho.'); return; }\n var depth = 0, j, end = -1;\n for (j = i; j < text.length - 1; j++) {\n if (text[j] === '{' && text[j + 1] === '{') { depth++; j++; }\n else if (text[j] === '}' && text[j + 1] === '}') { depth--; if (depth === 0) { end = j + 2; break; } j++; }\n }\n if (end === -1) { alert('Fechamento }} nao encontrado.'); return; }\n var inner = text.substring(i + '{{Segredo|'.length, end - 2);\n var parts = [], cur = '', d = 0;\n for (j = 0; j < inner.length; j++) {\n if (inner[j] === '{' && j + 1 < inner.length && inner[j + 1] === '{') { d++; cur += '{{'; j++; }\n else if (inner[j] === '}' && j + 1 < inner.length && inner[j + 1] === '}') { d--; cur += '}}'; j++; }\n else if (inner[j] === '|' && d === 0) { parts.push(cur); cur = ''; }\n else { cur += inner[j]; }\n }\n parts.push(cur);\n var content = '';\n for (var k = 0; k < parts.length; k++) {\n if (parts[k].trim().indexOf('=') === -1) { content = parts[k].trim(); break; }\n }\n tb.value = text.substring(0, i) + content + text.substring(end);\n tb.focus(); tb.setSelectionRange(i, i + content.length);\n if (window.$) { $(tb).trigger('input').trigger('change'); }\n }\n\n /* ------------------------------------------------------------------\n 5. ANIMAÇÃO DE CASCATA DAS SEÇÕES\n ------------------------------------------------------------------ */\n function initSectionAnimation() {\n var c = document.querySelector('.mw-parser-output');\n if (!c || c.dataset.vtmAnimated === 'true') return;\n if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n c.dataset.vtmAnimated = 'true';\n var kids = Array.from(c.children), secs = [], cur = [];\n kids.forEach(function (ch) {\n if (ch.classList.contains('catlinks') || ch.classList.contains('mw-category-generated') ||\n ch.tagName === 'SCRIPT' || ch.tagName === 'STYLE' || ch.id === 'vtm-editor-quickbar') {\n if (cur.length) { secs.push(cur); cur = []; }\n return;\n }\n if ((ch.tagName === 'H2' || ch.classList.contains('mw-heading2')) && cur.length) { secs.push(cur); cur = []; }\n cur.push(ch);\n });\n if (cur.length) secs.push(cur);\n secs.forEach(function (items, idx) {\n var w = document.createElement('div');\n w.className = 'vtm-section-block';\n w.style.animationDelay = (0.12 + idx * 0.42).toFixed(2) + 's';\n var f = items[0];\n if (f && f.parentNode) { f.parentNode.insertBefore(w, f); items.forEach(function (el) { w.appendChild(el); }); }\n });\n }\n\n /* ------------------------------------------------------------------\n INICIALIZAÇÃO\n ------------------------------------------------------------------ */\n function initAll() {\n initVtmSecrets();\n initSectionAnimation();\n initVtmQuickToolbar();\n injectVisualEditorButton();\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initAll);\n } else {\n initAll();\n }\n\n if (window.mw && window.mw.hook) {\n window.mw.hook('wikipage.content').add(function () {\n initVtmSecrets();\n initSectionAnimation();\n });\n window.mw.hook('ve.activationComplete').add(injectVisualEditorButton);\n }\n})();\n